diff --git a/.gitignore b/.gitignore index 8efb9c54f..1612499f6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ xs/assertlib* .init_bundle.ini local-lib build* +deps/deps-build diff --git a/Build.PL b/Build.PL index 4c7fa412a..3f1cf2633 100644 --- a/Build.PL +++ b/Build.PL @@ -1,5 +1,10 @@ #!/usr/bin/perl +print "This script is currently used for installing Perl dependenices for running\n"; +print "the libslic3r unit / integration tests through Perl prove.\n"; +print "If you don't plan to run the unit / integration tests, you don't need to\n"; +print "install these dependencies to build and run Slic3r.\n"; + use strict; use warnings; @@ -30,20 +35,7 @@ my %recommends = qw( ); my $sudo = grep { $_ eq '--sudo' } @ARGV; -my $gui = grep { $_ eq '--gui' } @ARGV; my $nolocal = grep { $_ eq '--nolocal' } @ARGV; -if ($gui) { - %prereqs = qw( - Class::Accessor 0 - Wx 0.9918 - ); - %recommends = qw( - Wx::GLCanvas 0 - ); - if ($^O eq 'MSWin32') { - $recommends{"Win32::TieRegistry"} = 0; - } -} my @missing_prereqs = (); if ($ENV{SLIC3R_NO_AUTO}) { @@ -129,13 +121,6 @@ EOF } } -print "\n"; -if ($gui) { - print "Perl dependencies for the Slic3r GUI were installed.\n"; -} else { - print "Perl dependencies for Slic3r were installed.\n"; - print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n"; -} print "\n"; print "In the next step, you need to build the Slic3r C++ library.\n"; print "1) Create a build directory and change to it\n"; diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e1817f54..625ebb334 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ project(Slic3r) cmake_minimum_required(VERSION 3.2) +include("version.inc") +set(SLIC3R_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources") +file(TO_NATIVE_PATH "${SLIC3R_RESOURCES_DIR}" SLIC3R_RESOURCES_DIR_WIN) + if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No build type selected, default to Release") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) @@ -18,42 +22,27 @@ endif() option(SLIC3R_STATIC "Compile Slic3r with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL}) option(SLIC3R_GUI "Compile Slic3r with GUI components (OpenGL, wxWidgets)" 1) -option(SLIC3R_PRUSACONTROL "Compile Slic3r with the PrusaControl prject file format (requires wxWidgets base library)" 1) option(SLIC3R_PROFILE "Compile Slic3r with an invasive Shiny profiler" 0) option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) +option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) +option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) +option(SLIC3R_SYNTAXONLY "Only perform source code correctness checking, no binary output (UNIX only)" 0) -if (MSVC AND SLIC3R_MSVC_COMPILE_PARALLEL) - add_compile_options(/MP) +# Proposal for C++ unit tests and sandboxes +option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF) +option(SLIC3R_BUILD_TESTS "Build unit tests" OFF) + +if (MSVC) + if (SLIC3R_MSVC_COMPILE_PARALLEL) + add_compile_options(/MP) + endif () + # /bigobj (Increase Number of Sections in .Obj file) + # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater + add_compile_options(-bigobj -Zm316) endif () -# Find the Perl interpreter, add local-lib to PATH and PERL5LIB environment variables, -# so the locally installed modules (mainly the Alien::wxPerl) will be reached. -if (WIN32) - set(ENV_PATH_SEPARATOR ";") -else() - set(ENV_PATH_SEPARATOR ":") -endif() -set(ENV{PATH} "${PROJECT_SOURCE_DIR}/local-lib/bin${ENV_PATH_SEPARATOR}$ENV{PATH}") -set(PERL_INCLUDE "${PROJECT_SOURCE_DIR}/local-lib/lib/perl5${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}") -message("PATH: $ENV{PATH}") -message("PERL_INCLUDE: ${PERL_INCLUDE}") -find_package(Perl REQUIRED) -if (WIN32) - # On Windows passing the PERL5LIB variable causes various problems (such as with MAX_PATH and others), - # basically I've found no good way to do it on Windows. - set(PERL5LIB_ENV_CMD "") -else() - set(PERL5LIB_ENV_CMD ${CMAKE_COMMAND} -E env PERL5LIB=${PERL_INCLUDE}) -endif() - -# CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...) -# We pick it from environment if it is not defined in another way -if(NOT DEFINED CMAKE_PREFIX_PATH) - if(DEFINED ENV{CMAKE_PREFIX_PATH}) - set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}") - endif() -endif() +message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") # Add our own cmake module path. list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) @@ -63,11 +52,13 @@ enable_testing () # Enable C++11 language standard. set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Enable C11 language standard. set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) +# To be able to link libslic3r with the Perl XS module. +# Once we get rid of Perl and libslic3r is linked statically, we can get rid of -fPIC +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way if(WIN32) @@ -82,17 +73,59 @@ if(WIN32) message("STL fixing by the Netfabb service will not be compiled") unset(WIN10SDK_PATH) endif() + if(WIN10SDK_PATH) + message("Building with Win10 Netfabb STL fixing service support") + add_definitions(-DHAS_WIN10SDK) + include_directories("${WIN10SDK_PATH}/Include") + else() + message("Building without Win10 Netfabb STL fixing service support") + endif() endif() +if (APPLE) + if (NOT CMAKE_OSX_DEPLOYMENT_TARGET) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "OS X Deployment target (SDK version)" FORCE) + endif () + message(STATUS "Mac OS deployment target (SDK version): ${CMAKE_OSX_DEPLOYMENT_TARGET}") +endif () + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-reorder" ) + add_compile_options(-std=c++11 -Wall -Wno-reorder) find_package(PkgConfig REQUIRED) endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) # Adding -fext-numeric-literals to enable GCC extensions on definitions of quad float literals, which are required by Boost. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals" ) + add_compile_options(-fext-numeric-literals) + + if (SLIC3R_SYNTAXONLY) + set(CMAKE_CXX_ARCHIVE_CREATE "true") + set(CMAKE_C_ARCHIVE_CREATE "true") + set(CMAKE_CXX_ARCHIVE_APPEND "true") + set(CMAKE_C_ARCHIVE_APPEND "true") + set(CMAKE_RANLIB "true") + set(CMAKE_C_LINK_EXECUTABLE "true") + set(CMAKE_CXX_LINK_EXECUTABLE "true") + + set(CMAKE_C_COMPILE_OBJECT " -fsyntax-only -c && touch ") + set(CMAKE_CXX_COMPILE_OBJECT " -fsyntax-only -c && touch ") + endif () +endif() + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. + add_compile_options(-Werror=return-type) + + if (SLIC3R_ASAN) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan") + endif () + endif () endif() # Where all the bundled libraries reside? @@ -100,12 +133,12 @@ set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/) # For the bundled boost libraries (boost::nowide) include_directories(${LIBDIR}) # For libslic3r.h -include_directories(${LIBDIR}/libslic3r ${LIBDIR}/clipper ${LIBDIR}/polypartition) +include_directories(${LIBDIR}/clipper ${LIBDIR}/polypartition) #set(CMAKE_INCLUDE_CURRENT_DIR ON) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601) + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) endif() add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO) @@ -115,25 +148,19 @@ if (SLIC3R_PROFILE) add_definitions(-DSLIC3R_PROFILE) endif () -# Perl specific stuff -find_package(PerlLibs REQUIRED) -set(PerlEmbed_DEBUG 1) -find_package(PerlEmbed REQUIRED) -# If the Perl is compiled with optimization off, disable optimization over the whole project. -if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;") +# Disable optimization even with debugging on. +if (0) message(STATUS "Perl compiled without optimization. Disabling optimization for the Slic3r build.") message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") - set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") - set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DWIN32 /DTBB_USE_ASSERT") + set(CMAKE_C_FLAGS "/MD /Od /Zi /DWIN32 /DTBB_USE_ASSERT") endif() -# The following line will add -fPIC on Linux to make the XS.so rellocable. -add_definitions(${PerlEmbed_CCCDLFLAGS}) # Find and configure boost if(SLIC3R_STATIC) @@ -143,6 +170,7 @@ if(SLIC3R_STATIC) # set(Boost_USE_STATIC_RUNTIME ON) endif() #set(Boost_DEBUG ON) +# set(Boost_COMPILER "-vc120") find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) @@ -170,33 +198,7 @@ endif() # The Intel TBB library will use the std::exception_ptr feature of C++11. add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) -# Find and configure wxWidgets -if (SLIC3R_PRUSACONTROL) - set(wxWidgets_UseAlienWx 1) - if (wxWidgets_UseAlienWx) - set(AlienWx_DEBUG 1) - find_package(AlienWx REQUIRED COMPONENTS base core adv html gl) - include_directories(${AlienWx_INCLUDE_DIRS}) - #add_compile_options(${AlienWx_CXX_FLAGS}) - add_definitions(${AlienWx_DEFINITIONS}) - set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES}) - # On Linux / gtk, we need to have a direct access to gtk+ for some workarounds. - if (AlienWx_GUI_TOOLKIT STREQUAL "gtk2") - pkg_check_modules(GTK2 gtk+-2.0) - include_directories(${GTK2_INCLUDE_DIRS}) - endif() - if (AlienWx_GUI_TOOLKIT STREQUAL "gtk3") - pkg_check_modules(GTK3 gtk+-3.0) - include_directories(${GTK3_INCLUDE_DIRS}) - endif() - else () - find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl) - include(${wxWidgets_USE_FILE}) - endif () -#FIXME rewrite the PRUS format to miniz! -# add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS) -endif() - +#set(CURL_DEBUG 1) find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) @@ -259,32 +261,34 @@ endif () include_directories(${GLEW_INCLUDE_DIRS}) # l10n -add_subdirectory(resources/localization) +set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") +add_custom_target(pot + # FIXME: file list stale + COMMAND xgettext --keyword=L --from-code=UTF-8 --debug + -f "${L10N_DIR}/list.txt" + -o "${L10N_DIR}/Slic3rPE.pot" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Generate pot file from strings in the source tree" +) # libslic3r, Slic3r GUI and the slic3r executable. add_subdirectory(src) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT slic3r_app_console) # Perl bindings, currently only used for the unit / integration tests of libslic3r. -add_subdirectory(xs) - -get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) -if (MSVC) - # By default the startup project in MSVC is the 'ALL_BUILD' cmake-created project, - # but we want 'slic3r' as the startup one because debugging run command is associated with it. - # (Unfortunatelly it cannot be associated with ALL_BUILD using CMake.) - # Note: For some reason this needs to be set in the top-level CMakeLists.txt - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT XS) - set(PERL_PROVE "${PERL_BIN_PATH}/prove.bat") -else () - set(PERL_PROVE "${PERL_BIN_PATH}/prove") +# Also runs the unit / integration tests. +#FIXME Port the tests into C++ to finally get rid of the Perl! +if (SLIC3R_PERL_XS) + add_subdirectory(xs) endif () -add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs) -add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +if(SLIC3R_BUILD_SANDBOXES) + add_subdirectory(sandboxes) +endif() -#install(PROGRAMS slic3r.pl DESTINATION bin RENAME slic3r-prusa3d) +if(SLIC3R_BUILD_TESTS) + add_subdirectory(tests) +endif() file(GLOB MyVar var/*.png) install(FILES ${MyVar} DESTINATION share/slic3r-prusa3d) -install(FILES lib/Slic3r.pm DESTINATION lib/slic3r-prusa3d) -install(DIRECTORY lib/Slic3r DESTINATION lib/slic3r-prusa3d) diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake index b8724858c..e0deafa45 100644 --- a/cmake/modules/FindCURL.cmake +++ b/cmake/modules/FindCURL.cmake @@ -5,34 +5,62 @@ # FindCURL # -------- # -# Find curl -# # Find the native CURL headers and libraries. # -# :: +# IMPORTED Targets +# ^^^^^^^^^^^^^^^^ # -# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc. -# CURL_LIBRARIES - List of libraries when using curl. -# CURL_FOUND - True if curl found. -# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8) +# This module defines :prop_tgt:`IMPORTED` target ``CURL::libcurl``, if +# curl has been found. +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following variables: +# +# ``CURL_FOUND`` +# True if curl found. +# +# ``CURL_INCLUDE_DIRS`` +# where to find curl/curl.h, etc. +# +# ``CURL_LIBRARIES`` +# List of libraries when using curl. +# +# ``CURL_VERSION_STRING`` +# The version of curl found. # Look for the header file. find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) mark_as_advanced(CURL_INCLUDE_DIR) -# Look for the library (sorted from most current/relevant entry to least). -find_library(CURL_LIBRARY NAMES - curl - # Windows MSVC Makefile: - libcurl_a - # Windows MSVC prebuilts: - curllib - libcurl_imp - curllib_static - # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): - libcurl -) -mark_as_advanced(CURL_LIBRARY) +if(NOT CURL_LIBRARY) + # Look for the library (sorted from most current/relevant entry to least). + find_library(CURL_LIBRARY_RELEASE NAMES + curl + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl + # Static library on Windows + libcurl_a + ) + mark_as_advanced(CURL_LIBRARY_RELEASE) + + find_library(CURL_LIBRARY_DEBUG NAMES + # Windows MSVC CMake builds in debug configuration on vcpkg: + libcurl-d_imp + libcurl-d + # Static library on Windows, compiled in debug mode + libcurl_a_debug + ) + mark_as_advanced(CURL_LIBRARY_DEBUG) + + include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake) + select_library_configurations_SLIC3R(CURL) +endif() if(CURL_INCLUDE_DIR) foreach(_curl_version_header curlver.h curl.h) @@ -46,7 +74,8 @@ if(CURL_INCLUDE_DIR) endforeach() endif() -find_package_handle_standard_args(CURL +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) +FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR VERSION_VAR CURL_VERSION_STRING) @@ -54,6 +83,29 @@ if(CURL_FOUND) set(CURL_LIBRARIES ${CURL_LIBRARY}) set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) - message(STATUS " Curl libraries: = ${CURL_LIBRARIES}") - message(STATUS " Curl include dirs: = ${CURL_INCLUDE_DIRS}") + if(NOT TARGET CURL::libcurl) + add_library(CURL::libcurl UNKNOWN IMPORTED) + set_target_properties(CURL::libcurl PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + + if(EXISTS "${CURL_LIBRARY}") + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${CURL_LIBRARY}") + endif() + if(CURL_LIBRARY_RELEASE) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}") + endif() + if(CURL_LIBRARY_DEBUG) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + endif() + endif() endif() diff --git a/cmake/modules/FindFlann.cmake b/cmake/modules/FindFlann.cmake deleted file mode 100644 index 98674d230..000000000 --- a/cmake/modules/FindFlann.cmake +++ /dev/null @@ -1,28 +0,0 @@ -############################################################################### -# Find Flann -# -# This sets the following variables: -# FLANN_FOUND - True if FLANN was found. -# FLANN_INCLUDE_DIRS - Directories containing the FLANN include files. -# FLANN_LIBRARIES - Libraries needed to use FLANN. -# FLANN_DEFINITIONS - Compiler flags for FLANN. - -find_package(PkgConfig) -pkg_check_modules(PC_FLANN flann) -set(FLANN_DEFINITIONS ${PC_FLANN_CFLAGS_OTHER}) - -find_path(FLANN_INCLUDE_DIR flann/flann.hpp - HINTS ${PC_FLANN_INCLUDEDIR} ${PC_FLANN_INCLUDE_DIRS}) - -find_library(FLANN_LIBRARY flann_cpp - HINTS ${PC_FLANN_LIBDIR} ${PC_FLANN_LIBRARY_DIRS}) - -set(FLANN_INCLUDE_DIRS ${FLANN_INCLUDE_DIR}) -set(FLANN_LIBRARIES ${FLANN_LIBRARY}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Flann DEFAULT_MSG - FLANN_LIBRARY FLANN_INCLUDE_DIR) - -mark_as_advanced(FLANN_LIBRARY FLANN_INCLUDE_DIR) - diff --git a/cmake/modules/FindPackageHandleStandardArgs_SLIC3R.cmake b/cmake/modules/FindPackageHandleStandardArgs_SLIC3R.cmake new file mode 100644 index 000000000..eddfd001e --- /dev/null +++ b/cmake/modules/FindPackageHandleStandardArgs_SLIC3R.cmake @@ -0,0 +1,392 @@ +# Modified from the CMake github master, +# required by the bundled FindCURL.cmake + + + + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindPackageHandleStandardArgs +----------------------------- + +This module provides a function intended to be used in :ref:`Find Modules` +implementing :command:`find_package()` calls. It handles the +``REQUIRED``, ``QUIET`` and version-related arguments of ``find_package``. +It also sets the ``_FOUND`` variable. The package is +considered found if all variables listed contain valid results, e.g. +valid filepaths. + +.. command:: find_package_handle_standard_args + + There are two signatures:: + + find_package_handle_standard_args( + (DEFAULT_MSG|) + ... + ) + + find_package_handle_standard_args( + [FOUND_VAR ] + [REQUIRED_VARS ...] + [VERSION_VAR ] + [HANDLE_COMPONENTS] + [CONFIG_MODE] + [FAIL_MESSAGE ] + ) + + The ``_FOUND`` variable will be set to ``TRUE`` if all + the variables ``...`` are valid and any optional + constraints are satisfied, and ``FALSE`` otherwise. A success or + failure message may be displayed based on the results and on + whether the ``REQUIRED`` and/or ``QUIET`` option was given to + the :command:`find_package` call. + + The options are: + + ``(DEFAULT_MSG|)`` + In the simple signature this specifies the failure message. + Use ``DEFAULT_MSG`` to ask for a default message to be computed + (recommended). Not valid in the full signature. + + ``FOUND_VAR `` + Obsolete. Specifies either ``_FOUND`` or + ``_FOUND`` as the result variable. This exists only + for compatibility with older versions of CMake and is now ignored. + Result variables of both names are always set for compatibility. + + ``REQUIRED_VARS ...`` + Specify the variables which are required for this package. + These may be named in the generated failure message asking the + user to set the missing variable values. Therefore these should + typically be cache entries such as ``FOO_LIBRARY`` and not output + variables like ``FOO_LIBRARIES``. + + ``VERSION_VAR `` + Specify the name of a variable that holds the version of the package + that has been found. This version will be checked against the + (potentially) specified required version given to the + :command:`find_package` call, including its ``EXACT`` option. + The default messages include information about the required + version and the version which has been actually found, both + if the version is ok or not. + + ``HANDLE_COMPONENTS`` + Enable handling of package components. In this case, the command + will report which components have been found and which are missing, + and the ``_FOUND`` variable will be set to ``FALSE`` + if any of the required components (i.e. not the ones listed after + the ``OPTIONAL_COMPONENTS`` option of :command:`find_package`) are + missing. + + ``CONFIG_MODE`` + Specify that the calling find module is a wrapper around a + call to ``find_package( NO_MODULE)``. This implies + a ``VERSION_VAR`` value of ``_VERSION``. The command + will automatically check whether the package configuration file + was found. + + ``FAIL_MESSAGE `` + Specify a custom failure message instead of using the default + generated message. Not recommended. + +Example for the simple signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibXml2 DEFAULT_MSG + LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) + +The ``LibXml2`` package is considered to be found if both +``LIBXML2_LIBRARY`` and ``LIBXML2_INCLUDE_DIR`` are valid. +Then also ``LibXml2_FOUND`` is set to ``TRUE``. If it is not found +and ``REQUIRED`` was used, it fails with a +:command:`message(FATAL_ERROR)`, independent whether ``QUIET`` was +used or not. If it is found, success will be reported, including +the content of the first ````. On repeated CMake runs, +the same message will not be printed again. + +Example for the full signature: + +.. code-block:: cmake + + find_package_handle_standard_args(LibArchive + REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR + VERSION_VAR LibArchive_VERSION) + +In this case, the ``LibArchive`` package is considered to be found if +both ``LibArchive_LIBRARY`` and ``LibArchive_INCLUDE_DIR`` are valid. +Also the version of ``LibArchive`` will be checked by using the version +contained in ``LibArchive_VERSION``. Since no ``FAIL_MESSAGE`` is given, +the default messages will be printed. + +Another example for the full signature: + +.. code-block:: cmake + + find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) + find_package_handle_standard_args(Automoc4 CONFIG_MODE) + +In this case, a ``FindAutmoc4.cmake`` module wraps a call to +``find_package(Automoc4 NO_MODULE)`` and adds an additional search +directory for ``automoc4``. Then the call to +``find_package_handle_standard_args`` produces a proper success/failure +message. +#]=======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage_SLIC3R.cmake) + +# internal helper macro +macro(_FPHSA_FAILURE_MESSAGE _msg) + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${_msg}") + else () + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${_msg}") + endif () + endif () +endmacro() + + +# internal helper macro to generate the failure message when used in CONFIG_MODE: +macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) + # _CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: + if(${_NAME}_CONFIG) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing:${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") + else() + # If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. + # List them all in the error message: + if(${_NAME}_CONSIDERED_CONFIGS) + set(configsText "") + list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) + math(EXPR configsCount "${configsCount} - 1") + foreach(currentConfigIndex RANGE ${configsCount}) + list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) + list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) + string(APPEND configsText " ${filename} (version ${version})\n") + endforeach() + if (${_NAME}_NOT_FOUND_MESSAGE) + string(APPEND configsText " Reason given by package: ${${_NAME}_NOT_FOUND_MESSAGE}\n") + endif() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:\n${configsText}") + + else() + # Simple case: No Config-file was found at all: + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") + endif() + endif() +endmacro() + + +function(FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R _NAME _FIRST_ARG) + +# Set up the arguments for `cmake_parse_arguments`. + set(options CONFIG_MODE HANDLE_COMPONENTS) + set(oneValueArgs FAIL_MESSAGE VERSION_VAR FOUND_VAR) + set(multiValueArgs REQUIRED_VARS) + +# Check whether we are in 'simple' or 'extended' mode: + set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) + list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) + + if(${INDEX} EQUAL -1) + set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) + set(FPHSA_REQUIRED_VARS ${ARGN}) + set(FPHSA_VERSION_VAR) + else() + cmake_parse_arguments(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) + + if(FPHSA_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") + endif() + + if(NOT FPHSA_FAIL_MESSAGE) + set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") + endif() + + # In config-mode, we rely on the variable _CONFIG, which is set by find_package() + # when it successfully found the config-file, including version checking: + if(FPHSA_CONFIG_MODE) + list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) + list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) + set(FPHSA_VERSION_VAR ${_NAME}_VERSION) + endif() + + if(NOT FPHSA_REQUIRED_VARS) + message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") + endif() + endif() + +# now that we collected all arguments, process them + + if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") + set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") + endif() + + list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) + + string(TOUPPER ${_NAME} _NAME_UPPER) + string(TOLOWER ${_NAME} _NAME_LOWER) + + if(FPHSA_FOUND_VAR) + if(FPHSA_FOUND_VAR MATCHES "^${_NAME}_FOUND$" OR FPHSA_FOUND_VAR MATCHES "^${_NAME_UPPER}_FOUND$") + set(_FOUND_VAR ${FPHSA_FOUND_VAR}) + else() + message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_NAME}_FOUND\" and \"${_NAME_UPPER}_FOUND\" are valid names.") + endif() + else() + set(_FOUND_VAR ${_NAME_UPPER}_FOUND) + endif() + + # collect all variables which were not found, so they can be printed, so the + # user knows better what went wrong (#6375) + set(MISSING_VARS "") + set(DETAILS "") + # check if all passed variables are valid + set(FPHSA_FOUND_${_NAME} TRUE) + foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) + if(NOT ${_CURRENT_VAR}) + set(FPHSA_FOUND_${_NAME} FALSE) + string(APPEND MISSING_VARS " ${_CURRENT_VAR}") + else() + string(APPEND DETAILS "[${${_CURRENT_VAR}}]") + endif() + endforeach() + if(FPHSA_FOUND_${_NAME}) + set(${_NAME}_FOUND TRUE) + set(${_NAME_UPPER}_FOUND TRUE) + else() + set(${_NAME}_FOUND FALSE) + set(${_NAME_UPPER}_FOUND FALSE) + endif() + + # component handling + unset(FOUND_COMPONENTS_MSG) + unset(MISSING_COMPONENTS_MSG) + + if(FPHSA_HANDLE_COMPONENTS) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(${_NAME}_${comp}_FOUND) + + if(NOT DEFINED FOUND_COMPONENTS_MSG) + set(FOUND_COMPONENTS_MSG "found components: ") + endif() + string(APPEND FOUND_COMPONENTS_MSG " ${comp}") + + else() + + if(NOT DEFINED MISSING_COMPONENTS_MSG) + set(MISSING_COMPONENTS_MSG "missing components: ") + endif() + string(APPEND MISSING_COMPONENTS_MSG " ${comp}") + + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + string(APPEND MISSING_VARS " ${comp}") + endif() + + endif() + endforeach() + set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") + string(APPEND DETAILS "[c${COMPONENT_MSG}]") + endif() + + # version handling: + set(VERSION_MSG "") + set(VERSION_OK TRUE) + + # check with DEFINED here as the requested or found version may be "0" + if (DEFINED ${_NAME}_FIND_VERSION) + if(DEFINED ${FPHSA_VERSION_VAR}) + set(_FOUND_VERSION ${${FPHSA_VERSION_VAR}}) + + if(${_NAME}_FIND_VERSION_EXACT) # exact version required + # count the dots in the version string + string(REGEX REPLACE "[^.]" "" _VERSION_DOTS "${_FOUND_VERSION}") + # add one dot because there is one dot more than there are components + string(LENGTH "${_VERSION_DOTS}." _VERSION_DOTS) + if (_VERSION_DOTS GREATER ${_NAME}_FIND_VERSION_COUNT) + # Because of the C++ implementation of find_package() ${_NAME}_FIND_VERSION_COUNT + # is at most 4 here. Therefore a simple lookup table is used. + if (${_NAME}_FIND_VERSION_COUNT EQUAL 1) + set(_VERSION_REGEX "[^.]*") + elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 2) + set(_VERSION_REGEX "[^.]*\\.[^.]*") + elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 3) + set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*") + else () + set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") + endif () + string(REGEX REPLACE "^(${_VERSION_REGEX})\\..*" "\\1" _VERSION_HEAD "${_FOUND_VERSION}") + unset(_VERSION_REGEX) + if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _VERSION_HEAD) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + unset(_VERSION_HEAD) + else () + if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _FOUND_VERSION) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable exact version \"${_FOUND_VERSION}\")") + endif () + endif () + unset(_VERSION_DOTS) + + else() # minimum version specified: + if (${_NAME}_FIND_VERSION VERSION_GREATER _FOUND_VERSION) + set(VERSION_MSG "Found unsuitable version \"${_FOUND_VERSION}\", but required is at least \"${${_NAME}_FIND_VERSION}\"") + set(VERSION_OK FALSE) + else () + set(VERSION_MSG "(found suitable version \"${_FOUND_VERSION}\", minimum required is \"${${_NAME}_FIND_VERSION}\")") + endif () + endif() + + else() + + # if the package was not found, but a version was given, add that to the output: + if(${_NAME}_FIND_VERSION_EXACT) + set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") + else() + set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") + endif() + + endif() + else () + # Check with DEFINED as the found version may be 0. + if(DEFINED ${FPHSA_VERSION_VAR}) + set(VERSION_MSG "(found version \"${${FPHSA_VERSION_VAR}}\")") + endif() + endif () + + if(VERSION_OK) + string(APPEND DETAILS "[v${${FPHSA_VERSION_VAR}}(${${_NAME}_FIND_VERSION})]") + else() + set(${_NAME}_FOUND FALSE) + endif() + + + # print the result: + if (${_NAME}_FOUND) + FIND_PACKAGE_MESSAGE_SLIC3R(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") + else () + + if(FPHSA_CONFIG_MODE) + _FPHSA_HANDLE_FAILURE_CONFIG_MODE() + else() + if(NOT VERSION_OK) + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (found ${${_FIRST_REQUIRED_VAR}})") + else() + _FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing:${MISSING_VARS}) ${VERSION_MSG}") + endif() + endif() + + endif () + + set(${_NAME}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) + set(${_NAME_UPPER}_FOUND ${${_NAME}_FOUND} PARENT_SCOPE) +endfunction() diff --git a/cmake/modules/FindPackageMessage_SLIC3R.cmake b/cmake/modules/FindPackageMessage_SLIC3R.cmake new file mode 100644 index 000000000..a7a5eb98f --- /dev/null +++ b/cmake/modules/FindPackageMessage_SLIC3R.cmake @@ -0,0 +1,54 @@ +# Modified from the CMake github master. +# required by the bundled FindCURL.cmake + + + + + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindPackageMessage +# ------------------ +# +# +# +# FIND_PACKAGE_MESSAGE( "message for user" "find result details") +# +# This macro is intended to be used in FindXXX.cmake modules files. It +# will print a message once for each unique find result. This is useful +# for telling the user where a package was found. The first argument +# specifies the name (XXX) of the package. The second argument +# specifies the message to display. The third argument lists details +# about the find result so that if they change the message will be +# displayed again. The macro also obeys the QUIET argument to the +# find_package command. +# +# Example: +# +# :: +# +# if(X11_FOUND) +# FIND_PACKAGE_MESSAGE(X11 "Found X11: ${X11_X11_LIB}" +# "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") +# else() +# ... +# endif() + +function(FIND_PACKAGE_MESSAGE_SLIC3R pkg msg details) + # Avoid printing a message repeatedly for the same find result. + if(NOT ${pkg}_FIND_QUIETLY) + string(REPLACE "\n" "" details "${details}") + set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) + if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") + # The message has not yet been printed. + message(STATUS "${msg}") + + # Save the find details in the cache to avoid printing the same + # message again. + set("${DETAILS_VAR}" "${details}" + CACHE INTERNAL "Details about finding ${pkg}") + endif() + endif() +endfunction() diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index 8b498d3ab..e5115ab44 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -280,7 +280,7 @@ if(NOT TBB_FOUND) ################################## if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) - add_library(tbb SHARED IMPORTED) + add_library(tbb UNKNOWN IMPORTED) set_target_properties(tbb PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} IMPORTED_LOCATION ${TBB_LIBRARIES}) @@ -288,7 +288,7 @@ if(NOT TBB_FOUND) set_target_properties(tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} - IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE} IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} ) @@ -310,6 +310,7 @@ if(NOT TBB_FOUND) unset(TBB_DEFAULT_SEARCH_DIR) if(TBB_DEBUG) + message(STATUS " TBB_FOUND = ${TBB_FOUND}") message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") diff --git a/cmake/modules/PrecompiledHeader.cmake b/cmake/modules/PrecompiledHeader.cmake new file mode 100644 index 000000000..2880da9f2 --- /dev/null +++ b/cmake/modules/PrecompiledHeader.cmake @@ -0,0 +1,214 @@ +# Function for setting up precompiled headers. Usage: +# +# add_library/executable(target +# pchheader.c pchheader.cpp pchheader.h) +# +# add_precompiled_header(target pchheader.h +# [FORCEINCLUDE] +# [SOURCE_C pchheader.c] +# [SOURCE_CXX pchheader.cpp]) +# +# Options: +# +# FORCEINCLUDE: Add compiler flags to automatically include the +# pchheader.h from every source file. Works with both GCC and +# MSVC. This is recommended. +# +# SOURCE_C/CXX: Specifies the .c/.cpp source file that includes +# pchheader.h for generating the pre-compiled header +# output. Defaults to pchheader.c. Only required for MSVC. +# +# Caveats: +# +# * Its not currently possible to use the same precompiled-header in +# more than a single target in the same directory (No way to set +# the source file properties differently for each target). +# +# * MSVC: A source file with the same name as the header must exist +# and be included in the target (E.g. header.cpp). Name of file +# can be changed using the SOURCE_CXX/SOURCE_C options. +# +# License: +# +# Copyright (C) 2009-2017 Lars Christensen +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the 'Software') deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +include(CMakeParseArguments) + +macro(combine_arguments _variable) + set(_result "") + foreach(_element ${${_variable}}) + set(_result "${_result} \"${_element}\"") + endforeach() + string(STRIP "${_result}" _result) + set(${_variable} "${_result}") +endmacro() + +function(export_all_flags _filename) + set(_include_directories "$") + set(_compile_definitions "$") + set(_compile_flags "$") + set(_compile_options "$") + set(_include_directories "$<$:-I$\n>") + set(_compile_definitions "$<$:-D$\n>") + set(_compile_flags "$<$:$\n>") + set(_compile_options "$<$:$\n>") + file(GENERATE OUTPUT "${_filename}" CONTENT "${_compile_definitions}${_include_directories}${_compile_flags}${_compile_options}\n") +endfunction() + +function(add_precompiled_header _target _input) + cmake_parse_arguments(_PCH "FORCEINCLUDE" "SOURCE_CXX;SOURCE_C" "" ${ARGN}) + + get_filename_component(_input_we ${_input} NAME_WE) + if(NOT _PCH_SOURCE_CXX) + set(_PCH_SOURCE_CXX "${_input_we}.cpp") + endif() + if(NOT _PCH_SOURCE_C) + set(_PCH_SOURCE_C "${_input_we}.c") + endif() + + if(MSVC) + set(_pch_cxx_pch "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/cxx_${_input_we}.pch") + set(_pch_c_pch "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/c_${_input_we}.pch") + + get_target_property(sources ${_target} SOURCES) + foreach(_source ${sources}) + set(_pch_compile_flags "") + if(_source MATCHES \\.\(cc|cxx|cpp|c\)$) + if(_source MATCHES \\.\(cpp|cxx|cc\)$) + set(_pch_header "${_input}") + set(_pch "${_pch_cxx_pch}") + else() + set(_pch_header "${_input}") + set(_pch "${_pch_c_pch}") + endif() + + if(_source STREQUAL "${_PCH_SOURCE_CXX}") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yc${_input}\"") + set(_pch_source_cxx_found TRUE) + set_source_files_properties("${_source}" PROPERTIES OBJECT_OUTPUTS "${_pch_cxx_pch}") + elseif(_source STREQUAL "${_PCH_SOURCE_C}") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yc${_input}\"") + set(_pch_source_c_found TRUE) + set_source_files_properties("${_source}" PROPERTIES OBJECT_OUTPUTS "${_pch_c_pch}") + else() + if(_source MATCHES \\.\(cpp|cxx|cc\)$) + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yu${_input}\"") + set(_pch_source_cxx_needed TRUE) + set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_cxx_pch}") + else() + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yu${_input}\"") + set(_pch_source_c_needed TRUE) + set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_c_pch}") + endif() + if(_PCH_FORCEINCLUDE) + set(_pch_compile_flags "${_pch_compile_flags} /FI${_input}") + endif(_PCH_FORCEINCLUDE) + endif() + + get_source_file_property(_object_depends "${_source}" OBJECT_DEPENDS) + if(NOT _object_depends) + set(_object_depends) + endif() + if(_PCH_FORCEINCLUDE) + list(APPEND _object_depends "${CMAKE_CURRENT_SOURCE_DIR}/${_pch_header}") + endif() + + set_source_files_properties(${_source} PROPERTIES + COMPILE_FLAGS "${_pch_compile_flags}" + OBJECT_DEPENDS "${_object_depends}") + endif() + endforeach() + + if(_pch_source_cxx_needed AND NOT _pch_source_cxx_found) + message(FATAL_ERROR "A source file ${_PCH_SOURCE_CXX} for ${_input} is required for MSVC builds. Can be set with the SOURCE_CXX option.") + endif() + if(_pch_source_c_needed AND NOT _pch_source_c_found) + message(FATAL_ERROR "A source file ${_PCH_SOURCE_C} for ${_input} is required for MSVC builds. Can be set with the SOURCE_C option.") + endif() + endif(MSVC) + + if(CMAKE_COMPILER_IS_GNUCXX) + get_filename_component(_name ${_input} NAME) + set(_pch_header "${CMAKE_CURRENT_SOURCE_DIR}/${_input}") + set(_pch_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${_target}_pch") + set(_pchfile "${_pch_binary_dir}/${_input}") + set(_outdir "${CMAKE_CURRENT_BINARY_DIR}/${_target}_pch/${_name}.gch") + file(MAKE_DIRECTORY "${_outdir}") + set(_output_cxx "${_outdir}/.c++") + set(_output_c "${_outdir}/.c") + + set(_pch_flags_file "${_pch_binary_dir}/compile_flags.rsp") + export_all_flags("${_pch_flags_file}") + set(_compiler_FLAGS "@${_pch_flags_file}") + add_custom_command( + OUTPUT "${_pchfile}" + COMMAND "${CMAKE_COMMAND}" -E copy "${_pch_header}" "${_pchfile}" + DEPENDS "${_pch_header}" + COMMENT "Updating ${_name}") + add_custom_command( + OUTPUT "${_output_cxx}" + COMMAND "${CMAKE_CXX_COMPILER}" ${_compiler_FLAGS} -x c++-header -o "${_output_cxx}" "${_pchfile}" + DEPENDS "${_pchfile}" "${_pch_flags_file}" + COMMENT "Precompiling ${_name} for ${_target} (C++)") + add_custom_command( + OUTPUT "${_output_c}" + COMMAND "${CMAKE_C_COMPILER}" ${_compiler_FLAGS} -x c-header -o "${_output_c}" "${_pchfile}" + DEPENDS "${_pchfile}" "${_pch_flags_file}" + COMMENT "Precompiling ${_name} for ${_target} (C)") + + get_property(_sources TARGET ${_target} PROPERTY SOURCES) + foreach(_source ${_sources}) + set(_pch_compile_flags "") + + if(_source MATCHES \\.\(cc|cxx|cpp|c\)$) + get_source_file_property(_pch_compile_flags "${_source}" COMPILE_FLAGS) + if(NOT _pch_compile_flags) + set(_pch_compile_flags) + endif() + separate_arguments(_pch_compile_flags) + list(APPEND _pch_compile_flags -Winvalid-pch) + if(_PCH_FORCEINCLUDE) + list(APPEND _pch_compile_flags -include "${_pchfile}") + else(_PCH_FORCEINCLUDE) + list(APPEND _pch_compile_flags "-I${_pch_binary_dir}") + endif(_PCH_FORCEINCLUDE) + + get_source_file_property(_object_depends "${_source}" OBJECT_DEPENDS) + if(NOT _object_depends) + set(_object_depends) + endif() + list(APPEND _object_depends "${_pchfile}") + if(_source MATCHES \\.\(cc|cxx|cpp\)$) + list(APPEND _object_depends "${_output_cxx}") + else() + list(APPEND _object_depends "${_output_c}") + endif() + + combine_arguments(_pch_compile_flags) + set_source_files_properties(${_source} PROPERTIES + COMPILE_FLAGS "${_pch_compile_flags}" + OBJECT_DEPENDS "${_object_depends}") + endif() + endforeach() + endif(CMAKE_COMPILER_IS_GNUCXX) +endfunction() diff --git a/cmake/modules/SelectLibraryConfigurations_SLIC3R.cmake b/cmake/modules/SelectLibraryConfigurations_SLIC3R.cmake new file mode 100644 index 000000000..d25ac8640 --- /dev/null +++ b/cmake/modules/SelectLibraryConfigurations_SLIC3R.cmake @@ -0,0 +1,77 @@ +# Modified from the CMake github master. +# required by the bundled FindCURL.cmake + + + + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# SelectLibraryConfigurations +# --------------------------- +# +# +# +# select_library_configurations( basename ) +# +# This macro takes a library base name as an argument, and will choose +# good values for basename_LIBRARY, basename_LIBRARIES, +# basename_LIBRARY_DEBUG, and basename_LIBRARY_RELEASE depending on what +# has been found and set. If only basename_LIBRARY_RELEASE is defined, +# basename_LIBRARY will be set to the release value, and +# basename_LIBRARY_DEBUG will be set to basename_LIBRARY_DEBUG-NOTFOUND. +# If only basename_LIBRARY_DEBUG is defined, then basename_LIBRARY will +# take the debug value, and basename_LIBRARY_RELEASE will be set to +# basename_LIBRARY_RELEASE-NOTFOUND. +# +# If the generator supports configuration types, then basename_LIBRARY +# and basename_LIBRARIES will be set with debug and optimized flags +# specifying the library to be used for the given configuration. If no +# build type has been set or the generator in use does not support +# configuration types, then basename_LIBRARY and basename_LIBRARIES will +# take only the release value, or the debug value if the release one is +# not set. + +# This macro was adapted from the FindQt4 CMake module and is maintained by Will +# Dicharry . + +macro( select_library_configurations_SLIC3R basename ) + if(NOT ${basename}_LIBRARY_RELEASE) + set(${basename}_LIBRARY_RELEASE "${basename}_LIBRARY_RELEASE-NOTFOUND" CACHE FILEPATH "Path to a library.") + endif() + if(NOT ${basename}_LIBRARY_DEBUG) + set(${basename}_LIBRARY_DEBUG "${basename}_LIBRARY_DEBUG-NOTFOUND" CACHE FILEPATH "Path to a library.") + endif() + + get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if( ${basename}_LIBRARY_DEBUG AND ${basename}_LIBRARY_RELEASE AND + NOT ${basename}_LIBRARY_DEBUG STREQUAL ${basename}_LIBRARY_RELEASE AND + ( _isMultiConfig OR CMAKE_BUILD_TYPE ) ) + # if the generator is multi-config or if CMAKE_BUILD_TYPE is set for + # single-config generators, set optimized and debug libraries + set( ${basename}_LIBRARY "" ) + foreach( _libname IN LISTS ${basename}_LIBRARY_RELEASE ) + list( APPEND ${basename}_LIBRARY optimized "${_libname}" ) + endforeach() + foreach( _libname IN LISTS ${basename}_LIBRARY_DEBUG ) + list( APPEND ${basename}_LIBRARY debug "${_libname}" ) + endforeach() + elseif( ${basename}_LIBRARY_RELEASE ) + set( ${basename}_LIBRARY ${${basename}_LIBRARY_RELEASE} ) + elseif( ${basename}_LIBRARY_DEBUG ) + set( ${basename}_LIBRARY ${${basename}_LIBRARY_DEBUG} ) + else() + set( ${basename}_LIBRARY "${basename}_LIBRARY-NOTFOUND") + endif() + + set( ${basename}_LIBRARIES "${${basename}_LIBRARY}" ) + + if( ${basename}_LIBRARY ) + set( ${basename}_FOUND TRUE ) + endif() + + mark_as_advanced( ${basename}_LIBRARY_RELEASE + ${basename}_LIBRARY_DEBUG + ) +endmacro() diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt new file mode 100644 index 000000000..8f98e0bda --- /dev/null +++ b/deps/CMakeLists.txt @@ -0,0 +1,75 @@ +# +# This CMake project downloads, configures and builds Slic3r PE dependencies on Unix and Windows. +# +# When using this script, it's recommended to perform an out-of-source build using CMake. +# +# All the dependencies are installed in a `destdir` directory in the root of the build directory, +# in a traditional Unix-style prefix structure. The destdir can be used directly by CMake +# when building Slic3r - to do this, set the CMAKE_PREFIX_PATH to ${destdir}/usr/local. +# +# For better clarity of console output, it's recommended to _not_ use a parallelized build +# for the top-level command, ie. use `make -j 1` or `ninja -j 1` to force single-threaded top-level +# build. This doesn't degrade performance as individual dependencies are built in parallel fashion +# if supported by the dependency. +# +# On Windows, architecture (64 vs 32 bits) is judged based on the compiler variant. +# To build dependencies for either 64 or 32 bit OS, use the respective compiler command line. +# +# WARNING: On UNIX platforms wxWidgets hardcode the destdir path into its `wx-conffig` utility, +# therefore, unfortunatelly, the installation cannot be copied/moved elsewhere without re-installing wxWidgets. +# + +project(Slic3r-deps) +cmake_minimum_required(VERSION 3.2) + +include(ExternalProject) +include(ProcessorCount) + +ProcessorCount(NPROC) +if (NPROC EQUAL 0) + set(NPROC 1) +endif () + +set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination directory") +option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) + +message(STATUS "Slic3r deps DESTDIR: ${DESTDIR}") +message(STATUS "Slic3r deps debug build: ${DEP_DEBUG}") + +if (MSVC) + if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + message(STATUS "\nDetected 64-bit compiler => building 64-bit deps bundle\n") + set(DEPS_BITS 64) + include("deps-windows.cmake") + elseif ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + message(STATUS "\nDetected 32-bit compiler => building 32-bit deps bundle\n") + set(DEPS_BITS 32) + include("deps-windows.cmake") + else () + message(FATAL_ERROR "Unable to detect architecture") + endif () +elseif (APPLE) + set(DEPS_OSX_TARGET "10.9" CACHE STRING "OS X SDK version to build against") + set(DEPS_OSX_SYSROOT + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${DEPS_OSX_TARGET}.sdk" + CACHE PATH "OS X SDK directory" + ) + + include("deps-macos.cmake") +else () + include("deps-linux.cmake") +endif() + +add_custom_target(deps ALL + DEPENDS + dep_boost + dep_tbb + dep_libcurl + dep_wxwidgets + dep_gtest + dep_nlopt + dep_libpng +) + +# Note: I'm not using any of the LOG_xxx options in ExternalProject_Add() commands +# because they seem to generate bogus build files (possibly a bug in ExternalProject). diff --git a/deps/deps-linux.cmake b/deps/deps-linux.cmake new file mode 100644 index 000000000..3f504b400 --- /dev/null +++ b/deps/deps-linux.cmake @@ -0,0 +1,118 @@ + +set(DEP_CMAKE_OPTS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON") + +include("deps-unix-common.cmake") + + +ExternalProject_Add(dep_boost + EXCLUDE_FROM_ALL 1 + URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" + URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./bootstrap.sh + --with-libraries=system,filesystem,thread,log,locale,regex + "--prefix=${DESTDIR}/usr/local" + BUILD_COMMAND ./b2 + -j ${NPROC} + --reconfigure + link=static + variant=release + threading=multi + boost.locale.icu=off + cflags=-fPIC + cxxflags=-fPIC + install + INSTALL_COMMAND "" # b2 does that already +) + +ExternalProject_Add(dep_libopenssl + EXCLUDE_FROM_ALL 1 + URL "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz" + URL_HASH SHA256=8e9516b8635bb9113c51a7b5b27f9027692a56b104e75b709e588c3ffd6a0422 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./config + "--prefix=${DESTDIR}/usr/local" + no-shared + no-ssl3-method + no-dynamic-engine + -Wa,--noexecstack + BUILD_COMMAND make depend && make "-j${NPROC}" + INSTALL_COMMAND make install_sw +) + +ExternalProject_Add(dep_libcurl + EXCLUDE_FROM_ALL 1 + DEPENDS dep_libopenssl + URL "https://curl.haxx.se/download/curl-7.58.0.tar.gz" + URL_HASH SHA256=cc245bf9a1a42a45df491501d97d5593392a03f7b4f07b952793518d97666115 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./configure + --enable-static + --disable-shared + "--with-ssl=${DESTDIR}/usr/local" + --with-pic + --enable-ipv6 + --enable-versioned-symbols + --enable-threaded-resolver + --with-random=/dev/urandom + --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt + --disable-ldap + --disable-ldaps + --disable-manual + --disable-rtsp + --disable-dict + --disable-telnet + --disable-pop3 + --disable-imap + --disable-smb + --disable-smtp + --disable-gopher + --disable-crypto-auth + --without-gssapi + --without-libpsl + --without-libidn2 + --without-gnutls + --without-polarssl + --without-mbedtls + --without-cyassl + --without-nss + --without-axtls + --without-brotli + --without-libmetalink + --without-libssh + --without-libssh2 + --without-librtmp + --without-nghttp2 + --without-zsh-functions-dir + BUILD_COMMAND make "-j${NPROC}" + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" +) + +ExternalProject_Add(dep_wxwidgets + EXCLUDE_FROM_ALL 1 + URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.tar.bz2" + URL_HASH SHA256=c925dfe17e8f8b09eb7ea9bfdcfcc13696a3e14e92750effd839f5e10726159e + BUILD_IN_SOURCE 1 + PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/wxwidgets-pngprefix.h" src/png/pngprefix.h + CONFIGURE_COMMAND ./configure + "--prefix=${DESTDIR}/usr/local" + --disable-shared + --with-gtk=2 + --with-opengl + --enable-unicode + --enable-graphics_ctx + --with-regex=builtin + --with-libpng=builtin + --with-libxpm=builtin + --with-libjpeg=builtin + --with-libtiff=builtin + --with-zlib=builtin + --with-expat=builtin + --disable-precomp-headers + --enable-debug_info + --enable-debug_gdb + --disable-debug + --disable-debug_flag + BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo + INSTALL_COMMAND make install +) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake new file mode 100644 index 000000000..720ec50d0 --- /dev/null +++ b/deps/deps-macos.cmake @@ -0,0 +1,103 @@ + +set(DEP_CMAKE_OPTS + "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" + "-DCMAKE_OSX_SYSROOT=${DEPS_OSX_SYSROOT}" + "-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPS_OSX_TARGET}" +) + +include("deps-unix-common.cmake") + + +ExternalProject_Add(dep_boost + EXCLUDE_FROM_ALL 1 + URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" + URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./bootstrap.sh + --with-libraries=system,filesystem,thread,log,locale,regex + "--prefix=${DESTDIR}/usr/local" + BUILD_COMMAND ./b2 + -j ${NPROC} + --reconfigure + link=static + variant=release + threading=multi + boost.locale.icu=off + "cflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" + "cxxflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" + install + INSTALL_COMMAND "" # b2 does that already +) + +ExternalProject_Add(dep_libcurl + EXCLUDE_FROM_ALL 1 + URL "https://curl.haxx.se/download/curl-7.58.0.tar.gz" + URL_HASH SHA256=cc245bf9a1a42a45df491501d97d5593392a03f7b4f07b952793518d97666115 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./configure + --enable-static + --disable-shared + "--with-ssl=${DESTDIR}/usr/local" + --with-pic + --enable-ipv6 + --enable-versioned-symbols + --enable-threaded-resolver + --with-darwinssl + --without-ssl # disables OpenSSL + --disable-ldap + --disable-ldaps + --disable-manual + --disable-rtsp + --disable-dict + --disable-telnet + --disable-pop3 + --disable-imap + --disable-smb + --disable-smtp + --disable-gopher + --disable-crypto-auth + --without-gssapi + --without-libpsl + --without-libidn2 + --without-gnutls + --without-polarssl + --without-mbedtls + --without-cyassl + --without-nss + --without-axtls + --without-brotli + --without-libmetalink + --without-libssh + --without-libssh2 + --without-librtmp + --without-nghttp2 + --without-zsh-functions-dir + BUILD_COMMAND make "-j${NPROC}" + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" +) + +ExternalProject_Add(dep_wxwidgets + EXCLUDE_FROM_ALL 1 + URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.tar.bz2" + URL_HASH SHA256=c925dfe17e8f8b09eb7ea9bfdcfcc13696a3e14e92750effd839f5e10726159e + BUILD_IN_SOURCE 1 + PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/wxwidgets-pngprefix.h" src/png/pngprefix.h + CONFIGURE_COMMAND ./configure + "--prefix=${DESTDIR}/usr/local" + --disable-shared + --with-osx_cocoa + "--with-macosx-version-min=${DEPS_OSX_TARGET}" + "--with-macosx-sdk=${DEPS_OSX_SYSROOT}" + --with-opengl + --with-regex=builtin + --with-libpng=builtin + --with-libxpm=builtin + --with-libjpeg=builtin + --with-libtiff=builtin + --with-zlib=builtin + --with-expat=builtin + --disable-debug + --disable-debug_flag + BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo + INSTALL_COMMAND make install +) diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake new file mode 100644 index 000000000..e4c718d21 --- /dev/null +++ b/deps/deps-unix-common.cmake @@ -0,0 +1,50 @@ + +# The unix common part expects DEP_CMAKE_OPTS to be set + +ExternalProject_Add(dep_tbb + EXCLUDE_FROM_ALL 1 + URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" + URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe + CMAKE_ARGS + -DTBB_BUILD_SHARED=OFF + -DTBB_BUILD_TESTS=OFF + ${DEP_CMAKE_OPTS} + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" +) + +ExternalProject_Add(dep_gtest + EXCLUDE_FROM_ALL 1 + URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" + URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c + CMAKE_ARGS -DBUILD_GMOCK=OFF ${DEP_CMAKE_OPTS} + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" +) + +ExternalProject_Add(dep_nlopt + EXCLUDE_FROM_ALL 1 + URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" + URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DNLOPT_PYTHON=OFF + -DNLOPT_OCTAVE=OFF + -DNLOPT_MATLAB=OFF + -DNLOPT_GUILE=OFF + ${DEP_CMAKE_OPTS} + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" + INSTALL_COMMAND "" +) + +ExternalProject_Add(dep_libpng + EXCLUDE_FROM_ALL 1 + URL "http://prdownloads.sourceforge.net/libpng/libpng-1.6.35.tar.xz?download" + URL_HASH SHA256=23912ec8c9584917ed9b09c5023465d71709dce089be503c7867fec68a93bcd7 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DPNG_SHARED=OFF + -DPNG_TESTS=OFF + ${DEP_CMAKE_OPTS} + INSTALL_COMMAND make install "DESTDIR=${DESTDIR}" + INSTALL_COMMAND "" +) diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake new file mode 100644 index 000000000..d49434ecb --- /dev/null +++ b/deps/deps-windows.cmake @@ -0,0 +1,228 @@ + +if (${DEPS_BITS} EQUAL 32) + set(DEP_MSVC_GEN "Visual Studio 12") +else () + set(DEP_MSVC_GEN "Visual Studio 12 Win64") +endif () + + + +if (${DEP_DEBUG}) + set(DEP_BOOST_DEBUG "debug") +else () + set(DEP_BOOST_DEBUG "") +endif () + +ExternalProject_Add(dep_boost + EXCLUDE_FROM_ALL 1 + URL "https://dl.bintray.com/boostorg/release/1.63.0/source/boost_1_63_0.tar.gz" + URL_HASH SHA256=fe34a4e119798e10b8cc9e565b3b0284e9fd3977ec8a1b19586ad1dec397088b + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND bootstrap.bat + BUILD_COMMAND b2.exe + -j "${NPROC}" + --with-system + --with-filesystem + --with-thread + --with-log + --with-locale + --with-regex + "--prefix=${DESTDIR}/usr/local" + "address-model=${DEPS_BITS}" + toolset=msvc-12.0 + link=static + variant=release + threading=multi + boost.locale.icu=off + "${DEP_BOOST_DEBUG}" release install + INSTALL_COMMAND "" # b2 does that already +) + + +ExternalProject_Add(dep_tbb + EXCLUDE_FROM_ALL 1 + URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" + URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DCMAKE_DEBUG_POSTFIX=_debug + -DTBB_BUILD_SHARED=OFF + -DTBB_BUILD_TESTS=OFF + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_tbb BINARY_DIR) + ExternalProject_Add_Step(dep_tbb build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + + +ExternalProject_Add(dep_gtest + EXCLUDE_FROM_ALL 1 + URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" + URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DBUILD_GMOCK=OFF + -Dgtest_force_shared_crt=ON + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_gtest BINARY_DIR) + ExternalProject_Add_Step(dep_gtest build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + + +ExternalProject_Add(dep_nlopt + EXCLUDE_FROM_ALL 1 + URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" + URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DNLOPT_PYTHON=OFF + -DNLOPT_OCTAVE=OFF + -DNLOPT_MATLAB=OFF + -DNLOPT_GUILE=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_nlopt BINARY_DIR) + ExternalProject_Add_Step(dep_nlopt build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + + +ExternalProject_Add(dep_zlib + EXCLUDE_FROM_ALL 1 + URL "https://zlib.net/zlib-1.2.11.tar.xz" + URL_HASH SHA256=4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + "-DINSTALL_BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}\\fallout" # I found no better way of preventing zlib creating & installing DLLs :-/ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_zlib BINARY_DIR) + ExternalProject_Add_Step(dep_zlib build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + + +ExternalProject_Add(dep_libpng + DEPENDS dep_zlib + EXCLUDE_FROM_ALL 1 + URL "http://prdownloads.sourceforge.net/libpng/libpng-1.6.35.tar.xz?download" + URL_HASH SHA256=23912ec8c9584917ed9b09c5023465d71709dce089be503c7867fec68a93bcd7 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DPNG_SHARED=OFF + -DPNG_TESTS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_libpng BINARY_DIR) + ExternalProject_Add_Step(dep_libpng build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + + +if (${DEPS_BITS} EQUAL 32) + set(DEP_LIBCURL_TARGET "x86") +else () + set(DEP_LIBCURL_TARGET "x64") +endif () + +ExternalProject_Add(dep_libcurl + EXCLUDE_FROM_ALL 1 + URL "https://curl.haxx.se/download/curl-7.58.0.tar.gz" + URL_HASH SHA256=cc245bf9a1a42a45df491501d97d5593392a03f7b4f07b952793518d97666115 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + BUILD_COMMAND cd winbuild && nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=${DEP_LIBCURL_TARGET}" + INSTALL_COMMAND cd builds\\libcurl-*-release-*-winssl + && "${CMAKE_COMMAND}" -E copy_directory include "${DESTDIR}\\usr\\local\\include" + && "${CMAKE_COMMAND}" -E copy_directory lib "${DESTDIR}\\usr\\local\\lib" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_libcurl SOURCE_DIR) + ExternalProject_Add_Step(dep_libcurl build_debug + DEPENDEES build + DEPENDERS install + COMMAND cd winbuild && nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=yes "MACHINE=${DEP_LIBCURL_TARGET}" + WORKING_DIRECTORY "${SOURCE_DIR}" + ) + ExternalProject_Add_Step(dep_libcurl install_debug + DEPENDEES install + COMMAND cd builds\\libcurl-*-debug-*-winssl + && "${CMAKE_COMMAND}" -E copy_directory include "${DESTDIR}\\usr\\local\\include" + && "${CMAKE_COMMAND}" -E copy_directory lib "${DESTDIR}\\usr\\local\\lib" + WORKING_DIRECTORY "${SOURCE_DIR}" + ) +endif () + + +if (${DEPS_BITS} EQUAL 32) + set(DEP_WXWIDGETS_TARGET "") + set(DEP_WXWIDGETS_LIBDIR "vc_lib") +else () + set(DEP_WXWIDGETS_TARGET "TARGET_CPU=X64") + set(DEP_WXWIDGETS_LIBDIR "vc_x64_lib") +endif () + +ExternalProject_Add(dep_wxwidgets + EXCLUDE_FROM_ALL 1 + URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.tar.bz2" + URL_HASH SHA256=c925dfe17e8f8b09eb7ea9bfdcfcc13696a3e14e92750effd839f5e10726159e + BUILD_IN_SOURCE 1 + PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}\\wxwidgets-pngprefix.h" src\\png\\pngprefix.h + CONFIGURE_COMMAND "" + BUILD_COMMAND cd build\\msw && nmake /f makefile.vc BUILD=release SHARED=0 UNICODE=1 USE_GUI=1 "${DEP_WXWIDGETS_TARGET}" + INSTALL_COMMAND "${CMAKE_COMMAND}" -E copy_directory include "${DESTDIR}\\usr\\local\\include" + && "${CMAKE_COMMAND}" -E copy_directory "lib\\${DEP_WXWIDGETS_LIBDIR}" "${DESTDIR}\\usr\\local\\lib\\${DEP_WXWIDGETS_LIBDIR}" +) +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_wxwidgets SOURCE_DIR) + ExternalProject_Add_Step(dep_wxwidgets build_debug + DEPENDEES build + DEPENDERS install + COMMAND cd build\\msw && nmake /f makefile.vc BUILD=debug SHARED=0 UNICODE=1 USE_GUI=1 "${DEP_WXWIDGETS_TARGET}" + WORKING_DIRECTORY "${SOURCE_DIR}" + ) +endif () diff --git a/deps/wxwidgets-pngprefix.h b/deps/wxwidgets-pngprefix.h new file mode 100644 index 000000000..de62e310b --- /dev/null +++ b/deps/wxwidgets-pngprefix.h @@ -0,0 +1,168 @@ +// Patched in Slic3r: These two were missing: +#define png_write_eXIf wx_png_write_eXIf +#define png_handle_eXIf wx_png_handle_eXIf + +#define png_sRGB_table wx_png_sRGB_table +#define png_sRGB_base wx_png_sRGB_base +#define png_sRGB_delta wx_png_sRGB_delta +#define png_zstream_error wx_png_zstream_error +#define png_free_buffer_list wx_png_free_buffer_list +#define png_fixed wx_png_fixed +#define png_user_version_check wx_png_user_version_check +#define png_malloc_base wx_png_malloc_base +#define png_malloc_array wx_png_malloc_array +#define png_realloc_array wx_png_realloc_array +#define png_create_png_struct wx_png_create_png_struct +#define png_destroy_png_struct wx_png_destroy_png_struct +#define png_free_jmpbuf wx_png_free_jmpbuf +#define png_zalloc wx_png_zalloc +#define png_zfree wx_png_zfree +#define png_default_read_data wx_png_default_read_data +#define png_push_fill_buffer wx_png_push_fill_buffer +#define png_default_write_data wx_png_default_write_data +#define png_default_flush wx_png_default_flush +#define png_reset_crc wx_png_reset_crc +#define png_write_data wx_png_write_data +#define png_read_sig wx_png_read_sig +#define png_read_chunk_header wx_png_read_chunk_header +#define png_read_data wx_png_read_data +#define png_crc_read wx_png_crc_read +#define png_crc_finish wx_png_crc_finish +#define png_crc_error wx_png_crc_error +#define png_calculate_crc wx_png_calculate_crc +#define png_flush wx_png_flush +#define png_write_IHDR wx_png_write_IHDR +#define png_write_PLTE wx_png_write_PLTE +#define png_compress_IDAT wx_png_compress_IDAT +#define png_write_IEND wx_png_write_IEND +#define png_write_gAMA_fixed wx_png_write_gAMA_fixed +#define png_write_sBIT wx_png_write_sBIT +#define png_write_cHRM_fixed wx_png_write_cHRM_fixed +#define png_write_sRGB wx_png_write_sRGB +#define png_write_iCCP wx_png_write_iCCP +#define png_write_sPLT wx_png_write_sPLT +#define png_write_tRNS wx_png_write_tRNS +#define png_write_bKGD wx_png_write_bKGD +#define png_write_hIST wx_png_write_hIST +#define png_write_tEXt wx_png_write_tEXt +#define png_write_zTXt wx_png_write_zTXt +#define png_write_iTXt wx_png_write_iTXt +#define png_set_text_2 wx_png_set_text_2 +#define png_write_oFFs wx_png_write_oFFs +#define png_write_pCAL wx_png_write_pCAL +#define png_write_pHYs wx_png_write_pHYs +#define png_write_tIME wx_png_write_tIME +#define png_write_sCAL_s wx_png_write_sCAL_s +#define png_write_finish_row wx_png_write_finish_row +#define png_write_start_row wx_png_write_start_row +#define png_combine_row wx_png_combine_row +#define png_do_read_interlace wx_png_do_read_interlace +#define png_do_write_interlace wx_png_do_write_interlace +#define png_read_filter_row wx_png_read_filter_row +#define png_write_find_filter wx_png_write_find_filter +#define png_read_IDAT_data wx_png_read_IDAT_data +#define png_read_finish_IDAT wx_png_read_finish_IDAT +#define png_read_finish_row wx_png_read_finish_row +#define png_read_start_row wx_png_read_start_row +#define png_zlib_inflate wx_png_zlib_inflate +#define png_read_transform_info wx_png_read_transform_info +#define png_do_strip_channel wx_png_do_strip_channel +#define png_do_swap wx_png_do_swap +#define png_do_packswap wx_png_do_packswap +#define png_do_invert wx_png_do_invert +#define png_do_bgr wx_png_do_bgr +#define png_handle_IHDR wx_png_handle_IHDR +#define png_handle_PLTE wx_png_handle_PLTE +#define png_handle_IEND wx_png_handle_IEND +#define png_handle_bKGD wx_png_handle_bKGD +#define png_handle_cHRM wx_png_handle_cHRM +#define png_handle_gAMA wx_png_handle_gAMA +#define png_handle_hIST wx_png_handle_hIST +#define png_handle_iCCP wx_png_handle_iCCP +#define png_handle_iTXt wx_png_handle_iTXt +#define png_handle_oFFs wx_png_handle_oFFs +#define png_handle_pCAL wx_png_handle_pCAL +#define png_handle_pHYs wx_png_handle_pHYs +#define png_handle_sBIT wx_png_handle_sBIT +#define png_handle_sCAL wx_png_handle_sCAL +#define png_handle_sPLT wx_png_handle_sPLT +#define png_handle_sRGB wx_png_handle_sRGB +#define png_handle_tEXt wx_png_handle_tEXt +#define png_handle_tIME wx_png_handle_tIME +#define png_handle_tRNS wx_png_handle_tRNS +#define png_handle_zTXt wx_png_handle_zTXt +#define png_check_chunk_name wx_png_check_chunk_name +#define png_check_chunk_length wx_png_check_chunk_length +#define png_handle_unknown wx_png_handle_unknown +#define png_chunk_unknown_handling wx_png_chunk_unknown_handling +#define png_do_read_transformations wx_png_do_read_transformations +#define png_do_write_transformations wx_png_do_write_transformations +#define png_init_read_transformations wx_png_init_read_transformations +#define png_push_read_chunk wx_png_push_read_chunk +#define png_push_read_sig wx_png_push_read_sig +#define png_push_check_crc wx_png_push_check_crc +#define png_push_save_buffer wx_png_push_save_buffer +#define png_push_restore_buffer wx_png_push_restore_buffer +#define png_push_read_IDAT wx_png_push_read_IDAT +#define png_process_IDAT_data wx_png_process_IDAT_data +#define png_push_process_row wx_png_push_process_row +#define png_push_handle_unknown wx_png_push_handle_unknown +#define png_push_have_info wx_png_push_have_info +#define png_push_have_end wx_png_push_have_end +#define png_push_have_row wx_png_push_have_row +#define png_push_read_end wx_png_push_read_end +#define png_process_some_data wx_png_process_some_data +#define png_read_push_finish_row wx_png_read_push_finish_row +#define png_push_handle_tEXt wx_png_push_handle_tEXt +#define png_push_read_tEXt wx_png_push_read_tEXt +#define png_push_handle_zTXt wx_png_push_handle_zTXt +#define png_push_read_zTXt wx_png_push_read_zTXt +#define png_push_handle_iTXt wx_png_push_handle_iTXt +#define png_push_read_iTXt wx_png_push_read_iTXt +#define png_colorspace_set_gamma wx_png_colorspace_set_gamma +#define png_colorspace_sync_info wx_png_colorspace_sync_info +#define png_colorspace_sync wx_png_colorspace_sync +#define png_colorspace_set_chromaticities wx_png_colorspace_set_chromaticities +#define png_colorspace_set_endpoints wx_png_colorspace_set_endpoints +#define png_colorspace_set_sRGB wx_png_colorspace_set_sRGB +#define png_colorspace_set_ICC wx_png_colorspace_set_ICC +#define png_icc_check_length wx_png_icc_check_length +#define png_icc_check_header wx_png_icc_check_header +#define png_icc_check_tag_table wx_png_icc_check_tag_table +#define png_icc_set_sRGB wx_png_icc_set_sRGB +#define png_colorspace_set_rgb_coefficients wx_png_colorspace_set_rgb_coefficients +#define png_check_IHDR wx_png_check_IHDR +#define png_do_check_palette_indexes wx_png_do_check_palette_indexes +#define png_fixed_error wx_png_fixed_error +#define png_safecat wx_png_safecat +#define png_format_number wx_png_format_number +#define png_warning_parameter wx_png_warning_parameter +#define png_warning_parameter_unsigned wx_png_warning_parameter_unsigned +#define png_warning_parameter_signed wx_png_warning_parameter_signed +#define png_formatted_warning wx_png_formatted_warning +#define png_app_warning wx_png_app_warning +#define png_app_error wx_png_app_error +#define png_chunk_report wx_png_chunk_report +#define png_ascii_from_fp wx_png_ascii_from_fp +#define png_ascii_from_fixed wx_png_ascii_from_fixed +#define png_check_fp_number wx_png_check_fp_number +#define png_check_fp_string wx_png_check_fp_string +#define png_muldiv wx_png_muldiv +#define png_muldiv_warn wx_png_muldiv_warn +#define png_reciprocal wx_png_reciprocal +#define png_reciprocal2 wx_png_reciprocal2 +#define png_gamma_significant wx_png_gamma_significant +#define png_gamma_correct wx_png_gamma_correct +#define png_gamma_16bit_correct wx_png_gamma_16bit_correct +#define png_gamma_8bit_correct wx_png_gamma_8bit_correct +#define png_destroy_gamma_table wx_png_destroy_gamma_table +#define png_build_gamma_table wx_png_build_gamma_table +#define png_safe_error wx_png_safe_error +#define png_safe_warning wx_png_safe_warning +#define png_safe_execute wx_png_safe_execute +#define png_image_error wx_png_image_error +#define png_check_keyword wx_png_check_keyword + + + + diff --git a/doc/How to build - UNIX.md b/doc/How to build - UNIX.md deleted file mode 100644 index 77ce54419..000000000 --- a/doc/How to build - UNIX.md +++ /dev/null @@ -1,2 +0,0 @@ -# Building Slic3r PE on Linux/UNIX - diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 8b7d37cdd..5c8e7ef3c 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -1,3 +1,9 @@ + +### NOTE: This document is currently outdated wrt. the new post-Perl Slic3rPE. A new build process and the description thereof is a work in progress and is comming soon. Please stay tuned. + +-- + + # Building Slic3r PE on Microsoft Windows The currently supported way of building Slic3r PE on Windows is with CMake and MS Visual Studio 2013 diff --git a/doc/How_to_build_Slic3r.txt b/doc/How_to_build_Slic3r.txt deleted file mode 100644 index 22e6f953e..000000000 --- a/doc/How_to_build_Slic3r.txt +++ /dev/null @@ -1,316 +0,0 @@ -How to build Slic3r on Mac OS X 10.9 Maveric ---------------------------------------------- -Vojtech Bubnik, 2017-12-12 - - -1) Install Mac OS X 10.7 Lion 64 bit with X Code ------------------------------------------------- - -One has to build the OSX Slic3r on a real Mac, either directly on the system, or on a virtualized OSX. On Mac, two commercial solutions are available to legally virtualize MacOS on MacOS: -http://www.parallels.com/eu/products/desktop/ -http://www.vmware.com/products/workstation/ - -Installation of a X Code on an OS X 10.7 Lion needs a bit of work. The latest X Code supported by the Lion on a Virtual Box is 4.21. The trouble is, the certificates of the X Code 4.21 installation package expired. One way to work around the certificate is to flatten the installation package by unpacking and repacking it: -pkgutil --expand Foobar.pkg foobar -pkgutil --flatten foobar barfoo.pkg - -The flattened package is available here: -\\rs.prusa\Development\Slic3r-Prusa\installxcode_421_lion_fixed.pkg - -This installer does not install the X Code directly. Instead, it installs another installer with a set of 47 pkg files. These files have their certificates expired as well. You will find the packages on your MacOS here: -/Applications/Install Xcode.app/Contents/Resources/Packages/ - -It is best to flatten them in a loop: -cd /Applications/Install\ Xcode.app/Contents/Resources/Packages/ -for f in *.pkg; do - pkgutil --expand $f /tmp/$f - rm -f $f - pkgutil --flatten /tmp/$f $f -done - -After that, you may finish the installation of Xcode by running -/Applications/Install\ Xcode.app - - -1b) Installing the Xcode on a newer system -------------------------------------------- -You will need to register as an Apple developer on -https://developer.apple.com/ -log in and download and install Xcode -https://developer.apple.com/downloads/ -You will likely need to download and install Xcode Command Line Tools, though the Xcode 4.1 came with the command line tools installed. - - -2) Prepare the development environment --------------------------------------- - -Install the brew package manager: -http://brew.sh/ -The brew package manager requires the git command line tool. Normally the git tool is installed as part of the Xcode command line tools. -Copy and execute a command line from the top of http://brew.sh/ . It is possible, that the invocation of git fails because of some parameters the old git does not recognize. If so, invoke the git call manually. - -Compile the boost library using brew. Following line compiles a 64bit boost with both static and shared libraries. -brew install boost --universal - -Install dylibbundler tool. The dylibbundler tool serves to collect dependent dynamic libraries and fix their linkage. Execute -brew install dylibbundler - -Install cmake -brew install cmake - -3) Install perl ---------------- - -We don't want to distribute perl pre-installed on the Mac OS box. First, the system perl installation is not correct on some Mac OS versions, second it is not rellocatable. To compile a 64bit rellocatable perl, we use the perlbrew distribution. The perlbrew distribution installs into a user home directory and it allows switching between multiple versions of perl. -http://perlbrew.pl/ - -First install perlbrew -curl -L http://install.perlbrew.pl | bash -Then compile the newest perl with the rellocatable @INC path and with multithreading enabled, execute following line: -perlbrew install --threads -Duserelocatableinc --switch perl-5.26.1 -The --switch parameter switches the active perl to the currently compiled one. -Available perl versions could be listed by calling -perlbrew available -Switch to the newly compiled perl -perl5/perlbrew/bin/perlbrew switch perl-5.26.1 -Install cpanm -perlbrew install-cpanm - -Initialize CPAN, install PAR and PAR::Packer modules -execute cpan command, from the cpan prompt, run -install App::cpanminus -install ExtUtils::CppGuess -install ExtUtils::Typemaps -install ExtUtils::Typemaps::Basic -install PAR -install PAR::Packer -install Module::Build -install Module::Pluggable -install Module::Runtime -install Moo -install Test::Pod -install Test::Pod::Coverage -quit - -4) Download and install Slic3r ------------------------------- - -git clone git://github.com/alexrj/Slic3r -cd Slic3r -perl Build.PL - -Now Slic3r shall be compiled. You may try to execute -perl slic3r.pl -to get a help screen, or -perl slic3r.pl some_model.stl -to have the model sliced. - -5) Download and compile the GUI libraries needed to execute Slic3r in GUI mode ------------------------------------------------------------------------------- - -Building the Perl Alien-Wx containing a wxWidgets library: -We use wxWidgets-3.0.3. -patch wxWidgets-3.0.3//src/osx/cocoa/textctrl.mm , see https://github.com/prusa3d/Slic3r/issues/600 -perl -I. Build.PL --wxWidgets-extraflags="--with-osx_cocoa --with-macosx-version-min=10.9 --with-macosx-sdk=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk --with-libjpeg=builtin --with-libpng=builtin --with-regex=builtin --with-libtiff=builtin --with-zlib=builtin --with-expat=builtin --with-opengl" -perl -I. Build -perl -I. Build test -perl -I. Build - -Building the Perl Wx package: -cpan install Wx - -Building the Perl OpenGL package: -OpenGL needs to be patched to support MSAA, see the Windows patch. - - -For the current Slic3r 1.2.30 code base, set the environment variable SLIC3R_STATIC to link a static version of the boost library: -export SLIC3R_STATIC=1 - -then execute -perl Build.PL --gui -and keep your fingers crossed. The Build.PL script downloads and compiles the WxWidgets 3.0 through a Alien::Wx PERL package. The WxWidget shared libraries will land into -~/perl5/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/ - -On Maverics, we experienced following issue compiling WxPerl: -http://wiki.bolay.net/doku.php?id=acdsn:acdsn-a:mac - -Now you could run the GUI version of slic3r by calling -perl slic3r.pl --gui -If some dependency is missing, the MacOS system will let you know. - -6) Packing the Slic3r ---------------------- - -Perl is an operating system on its own. Many modules are shared among multiple applications and it is difficult to extract a stand-alone application from a perl installation manually. Fortunately, tools are available, which automate the process to some extent. One of the tools is the PAR::Packer. The PAR::Packer tool (pp executable) is able to create a standalone executable for a perl script. The standalone executable contains a PAR archive (a zip file) bundled with a perl interpreter. When executed, the bundled executable will decompress most of the PAR archive into a temp folder. Because of that, we will use the PAR::Packer to resolve and collect the dependencies, but we will create an installer manually. - -The PAR::Packer could analyze the dependencies by a statical analysis, or at a runtime. The statical analysis does not resolve the dynamically loaded modules. On the other side, the statical analysis is pessimistic, therefore it often collects unneeded packages. The dynamic analysis may miss some package, if not all branches of a code are executed. We will try to solely depend on the dynamic analysis to keep the installation size minimal. We may need to develop a protocol or an automatic UI tool to exercise as much as possible from the Slic3r GUI to pack the GUI version reliably. Once a reliable list of dependencies is collected, we may not need the PAR::Packer anymore. - -To create a PAR archive of a command line slic3r, execute -pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par -and let the slic3r slice a cube.stl to load the dynamic modules. - -To create a PAR archive of a GUI slic3r, execute -pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par -and exercise the slic3r gui to load all modules. - -Rename the slic3r.par file to slic3r.zip and decompress. Most of the code needed to execute Slic3r is there, only the perl executable is missing for the command line slic3r, and the WxWidgets shared libraries and the liblzma shared library are missing for the GUI version. - -7) Collecting the dependent shared libraries, making the link paths relative ----------------------------------------------------------------------------- - -We have linked Slic3r against a static boost library, therefore the command line slic3r is not dependent on any non-system shared library. The situation is different for the GUI slic3r, which links dynamically against WxWidgets and liblzma. - -The trick developed by Apple to allow rellocable shared libraries is to addres a shared library relatively to the path of the executable by encoding a special token @executable_path at the start of the path. Unfortunately the libraries requried by Slic3r are compiled with absolute paths. - -Once the slic3r.par archive is unpacked, one may list the native shared libraries by -find ./ -name '*.bundle' -and one may list the dependencies by running -otool -L somefile.bundle -Most of the dependencies point to system directores and these dependences are always fulfilled. Dependencies pointing to the WxWidget libraries need to be fixed. These have a form -~/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/lib/libwx_*.dylib -and we need to replace them with -@executable_path/../Frameworks/libwx_*.dylib -Another dependency, which needs our attention is -/usr/local/Cellar/xz/5.2.2/lib/liblzma.5.dylib - -Fortunately, a tool dylibbundler was developed to address this problem. -First install dylibbundler by calling -brew dylibbundler - -For some installations, the dylibbundler tool sufficiently fixes all dependencies. Unfortunately, the WxWidgets installation is inconsistent in the versioning, therefore a certain clean-up is required. Namely, the WxWidgets libraries are compiled with the full build number in their file name. For example, the base library is built as libwx_baseu-3.0.0.2.0.dylib and a symlink is created libwx_baseu-3.0.dylib pointing to the full name. Then some of the Wx libraries link against the full name and some against the symlink, leading the dylibbundler to pack both. We solved the problem by whipping up a following script: -\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\fix_dependencies.sh - -call -slic3r_dependencies.sh --fix -to collect the shared libraries into Content/Frameworks and to fix their linkage. - -call -slic3r_dependencies.sh --show -to list dependent libraries in a sorted order. All the non-system dependencies shall start with @executable_path after the fix. - - - -8) Packing Slic3r into a dmg image using a bunch of scripts ------------------------------------------------------------ - -Instead of relying on the PAR::Packer to collect the dependencies, we have used PAR::Packer to extract the dependencies, we manually cleaned them up and created an installer script. -\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\Slic3r-Build-MacOS -First compile Slic3r, then call build_dmg.sh with a path to the Slic3r source tree as a parameter. -The script will collect all dependencies into Slic3r.app and it will create Slic3r.dmg. -If SLIC3R_GUI variable is defined, a GUI variant of Slic3r will be packed. - - - - -How to build on Windows ------------------------ - -The prefered perl distribution on MS Windows is the Strawberry Perl 5.22.1.3 (32bit) -http://strawberryperl.com/ - -Let it install into c:\strawberry -You may make a copy of the distribution. We recommend to make following copies: -For a release command line only build: c:\strawberry-minimal -For a release GUI build: c:\strawberry-minimal-gui -For a development build with debugging information: c:\strawberry-debug -and to make one of them active by making a directory junction: -mklink /d c:\Strawberry c:\Strawberry-debug - -Building boost: -Slic3r seems to have a trouble with the latest boost 1.60.0 on Windows. Please use 1.59. -Decompress it to -c:\dev\ -otherwise it will not be found by the Build.PL on Windows. You may consider to hack xs\Build.PL with your prefered boost path. -run -bootstrap.bat mingw -b2 toolset=gcc - -Install git command line -https://git-scm.com/ - -Download Slic3r source code -git clone git://github.com/alexrj/Slic3r.git - -Run compilation -cd Slic3r -perl Build.PL -perl Build.PL --gui - -With a bit of luck, you will end up with a working Slic3r including GUI. - - - - -Packing on Windows ------------------- - -Life is easy on Windows. PAR::Packer will help again to collect the dependencies. The basic procedure is the same as for MacOS: - -To create a PAR archive of a command line slic3r, execute -pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par -and let the slic3r slice a cube.stl to load the dynamic modules. - -To create a PAR archive of a GUI slic3r, execute -pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par -and exercise the slic3r gui to load all modules. - -The standalone installation is then created from the PAR archive by renaming it into a zip and adding following binaries from c:\strawberry to the root of the extracted zip: -perl5.22.1.exe -perl522.dll -libgcc_s_sjlj-1.dll -libstdc++-6.dll -libwinpthread-1.dll - -The GUI build requires following DLLs in addition: -libglut-0_.dll -wxbase30u_gcc_custom.dll -wxmsw30u_adv_gcc_custom.dll -wxmsw30u_core_gcc_custom.dll -wxmsw30u_gl_gcc_custom.dll -wxmsw30u_html_gcc_custom.dll - -and the var directory with the icons needs to be copied to the destination directory. - -To run the slic3r, move the script\slic3r.pl one level up and create a following tiny windows batch in the root of the unpacked zip: -@perl5.22.1.exe slic3r.pl %* -A windows shortcut may be created for the GUI version. Instead of the perl.exe, it is better to use the wperl.exe to start the GUI Slic3r, because it does not open a text console. - -The strawberry perl is already rellocatable, which means that the perl interpreter will find the perl modules in the lib directory, -and Windows look up the missing DLLs in the directory of the executable, therefore no further rellocation effort is necessary. - - -Packing on Windows, a single EXE solution ------------------------------------------ - -One may try to create a PAR executable for command line slic3r: -pp -M Encode::Locale -M Moo -M Thread::Semaphore -M Slic3r::XS -M Unicode::Normalize -o slic3r.exe slic3r.pl - -One may as well create a PAR executable for Windows GUI: -pp -M Encode::Locale -M Moo -M Thread::Semaphore -M OpenGL -M Slic3r::XS -M Unicode::Normalize -M Wx -M Class::Accessor -M Wx::DND -M Wx::Grid -M Wx::Print -M Wx::Html -M Wx::GLCanvas -M Math::Trig -M threads -M threads::shared -M Thread::Queue -l C:\strawberry\perl\site\lib\auto\Wx\Wx.xs.dll -o -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll -o slic3r.exe slic3r.pl - -Remember, that these executables will unpack into a temporary directory. The directory may be declared by setting an environment variable PAR_GLOBAL_TEMP. Otherwise the temporaries are unpacked into -C:\Users\xxx\AppData\Local\Temp\par-xxxxxx - - -Debugging the perl, debugging on Windows ----------------------------------------- - -It is possible to debug perl using the integrated debugger. The EPIC plugin for Eclipse works very well with an older eclipse-SDK-3.6.2. There is a catch though: The Perl debugger does not work correctly with multiple threads running under the Perl interpreter. If that happens, the EPIC plugin gets confused and the debugger stops working. The same happens with the Komodo IDE. - -Debugging the C++ code works fine using the latest Eclipse for C++ and the gdb of MinGW. The gdb packed with the Strawberry distribution does not contain the Python support, so pretty printing of the stl containers only works if another gdb build is used. The one of the QT installation works well. - -It is yet a bit more complicated. The Strawberry MINGW is compiled for a different C++ exception passing model (SJLJ) than the other MINGWs, so one cannot simply combine MINGWs on Windows. For an unknown reason the nice debugger of the QT Creator hangs when debugging the C++ compiled by the Strawberry MINGW. Mabe it is because of the different exception passing models. - -And to disable optimization of the C/C++ code, one has to manually modify Config_heavy.pl in the Perl central installation. The SLIC3R_DEBUG environment variable did not override all the -O2 and -O3 flags that the perl build adds the gcc execution line. - ----------------------------------------------------------------------- - -Building boost. - -One may save compilation time by compiling just what Slic3r needs. -./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex -The -fPIC flag is required on Linux to make the static libraries rellocatable, -so they could be embedded into a shared library. -It is important to disable boost.locale.icu=off when compiling the static boost library. -./bjam -a link=static variant=release threading=multi boost.locale.icu=off --with-locale cxxflags=-fPIC cflags=-fPIC -To install on Linux to /usr/local/..., run the line above with the additional install keyword and with sudo. diff --git a/doc/deps-build/unix-static/Makefile b/doc/deps-build/unix-static/Makefile deleted file mode 100644 index 21740bc53..000000000 --- a/doc/deps-build/unix-static/Makefile +++ /dev/null @@ -1,135 +0,0 @@ - -# -# This makefile downloads, configures and builds Slic3r PE dependencies for Unix. -# (That is, all dependencies except perl + wxWidgets.) -# The libraries are installed in DESTDIR, which you can customize like so: -# -# DESTDIR=foo/bar make -# -# The default DESTDIR is ~/slic3r-destdir -# If the DESTDIR doesn't exits, the makefile tries to create it -# -# To pass the DESTDIR path along to cmake, set the use CMAKE_PREFIX_PATH variable -# and set it to $DESTDIR/usr/local -# -# You can also customize the NPROC variable in the same way to configure the number -# of cores the build process uses. By default this is set to what the `nproc` command says. -# - - -DESTDIR ?= $(HOME)/slic3r-destdir -NPROC ?= $(shell nproc) - - -BOOST = boost_1_66_0 -TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b -TBB = tbb-$(TBB_SHA) -OPENSSL = openssl-OpenSSL_1_1_0g -CURL = curl-7.58.0 - -.PHONY: all destdir boost libcurl libopenssl libtbb - -all: destdir boost libtbb libcurl - @echo - @echo "All done!" - @echo - -destdir: - mkdir -p $(DESTDIR) - - - -boost: $(BOOST).tar.gz - tar -zxvf $(BOOST).tar.gz - cd $(BOOST) && ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex --prefix=$(DESTDIR)/usr/local - cd $(BOOST) && ./b2 \ - -j $(NPROC) \ - link=static \ - variant=release \ - threading=multi \ - boost.locale.icu=off \ - cxxflags=-fPIC cflags=-fPIC \ - install - -$(BOOST).tar.gz: - curl -L -o $@ https://dl.bintray.com/boostorg/release/1.66.0/source/$@ - - - -libtbb: $(TBB).tar.gz - tar -zxvf $(TBB).tar.gz - mkdir -p $(TBB)/mybuild - cd $(TBB)/mybuild && cmake .. -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON - $(MAKE) -C $(TBB)/mybuild -j$(NPROC) - $(MAKE) -C $(TBB)/mybuild install DESTDIR=$(DESTDIR) - -$(TBB).tar.gz: - curl -L -o $@ https://github.com/wjakob/tbb/archive/$(TBB_SHA).tar.gz - - -# Note: libcurl build system seems to be a bit wonky about finding openssl (cf. #2378). -# It seems that currently the only working option is to set a prefix in the openssl build -# and use the `--with-ssl=...` option in libcurl. -# Additionally, pkg-config needs to be installed and openssl libs need to NOT be installed on the build system. - -libopenssl: $(OPENSSL).tar.gz - tar -zxvf $(OPENSSL).tar.gz - cd $(OPENSSL) && ./config --prefix=$(DESTDIR)/usr/local no-shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack' - $(MAKE) -C $(OPENSSL) depend - $(MAKE) -C $(OPENSSL) -j$(NPROC) - $(MAKE) -C $(OPENSSL) install_sw - -$(OPENSSL).tar.gz: - curl -L -o $@ 'https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz' - - - -libcurl: libopenssl $(CURL).tar.gz - tar -zxvf $(CURL).tar.gz - cd $(CURL) && ./configure \ - --enable-static \ - --disable-shared \ - --with-ssl=$(DESTDIR)/usr/local \ - --with-pic \ - --enable-ipv6 \ - --enable-versioned-symbols \ - --enable-threaded-resolver \ - --with-random=/dev/urandom \ - --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \ - --disable-ldap \ - --disable-ldaps \ - --disable-manual \ - --disable-rtsp \ - --disable-dict \ - --disable-telnet \ - --disable-pop3 \ - --disable-imap \ - --disable-smb \ - --disable-smtp \ - --disable-gopher \ - --disable-crypto-auth \ - --without-gssapi \ - --without-libpsl \ - --without-libidn2 \ - --without-gnutls \ - --without-polarssl \ - --without-mbedtls \ - --without-cyassl \ - --without-nss \ - --without-axtls \ - --without-brotli \ - --without-libmetalink \ - --without-libssh \ - --without-libssh2 \ - --without-librtmp \ - --without-nghttp2 \ - --without-zsh-functions-dir - $(MAKE) -C $(CURL) -j$(NPROC) - $(MAKE) -C $(CURL) install DESTDIR=$(DESTDIR) - -$(CURL).tar.gz: - curl -L -o $@ https://curl.haxx.se/download/$@ - - -clean: - rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz diff --git a/doc/deps-build/windows/slic3r-makedeps.ps1 b/doc/deps-build/windows/slic3r-makedeps.ps1 deleted file mode 100644 index e256d61e4..000000000 --- a/doc/deps-build/windows/slic3r-makedeps.ps1 +++ /dev/null @@ -1,141 +0,0 @@ -#!powershell -# -# This script downloads, configures and builds Slic3r PE dependencies for Unix. -# (That is, all dependencies except perl + wxWidgets.) -# -# To use this script, launch the Visual Studio command line, -# `cd` into the directory containing this script and use this command: -# -# powershell .\slic3r-makedeps.ps1 -# -# The dependencies will be downloaded and unpacked into the current dir. -# This script WILL NOT try to guess the build architecture (64 vs 32 bits), -# it will by default build the 64-bit variant. To build the 32-bit variant, use: -# -# powershell .\slic3r-makedeps.ps1 -b32 -# -# Built libraries are installed into $destdir, -# which by default is C:\local\slic3r-destdir-$bits -# You can customize the $destdir using: -# -# powershell .\slic3r-makedeps.ps1 -destdir C:\foo\bar -# -# To pass the $destdir path along to cmake, set the use CMAKE_PREFIX_PATH variable -# and set it to $destdir\usr\local -# -# Script requirements: PowerShell 3.0, .NET 4.5 -# - - -param( - [switch]$b32 = $false, - [string]$destdir = "" -) - -if ($destdir -eq "") { - $destdir = "C:\local\slic3r-destdir-" + ('32', '64')[!$b32] -} - -$BOOST = 'boost_1_63_0' -$CURL = 'curl-7.58.0' -$TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b' -$TBB = "tbb-$TBB_SHA" - - -try -{ - - -# Set up various settings and utilities: -[Environment]::CurrentDirectory = Get-Location -$NPROC = (Get-WmiObject -class Win32_processor).NumberOfLogicalProcessors -Add-Type -A System.IO.Compression.FileSystem -# This fxies SSL/TLS errors, credit goes to Ansible; see their `win_get_url.ps1` file -$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault -if ([Net.SecurityProtocolType].GetMember('Tls11').Count -gt 0) { - $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11 -} -if ([Net.SecurityProtocolType].GetMember('Tls12').Count -gt 0) { - $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12 -} -[Net.ServicePointManager]::SecurityProtocol = $security_protcols -$webclient = New-Object System.Net.WebClient - - -# Ensure DESTDIR exists: -mkdir $destdir -ea 0 -mkdir "$destdir\usr\local" -ea 0 - - -# Download sources: -echo 'Downloading sources ...' -if (!(Test-Path "$BOOST.zip")) { $webclient.DownloadFile("https://dl.bintray.com/boostorg/release/1.63.0/source/$BOOST.zip", "$BOOST.zip") } -if (!(Test-Path "$TBB.zip")) { $webclient.DownloadFile("https://github.com/wjakob/tbb/archive/$TBB_SHA.zip", "$TBB.zip") } -if (!(Test-Path "$CURL.zip")) { $webclient.DownloadFile("https://curl.haxx.se/download/$CURL.zip", ".\$CURL.zip") } - - -# Unpack sources: -echo 'Unpacking ...' -if (!(Test-Path $BOOST)) { [IO.Compression.ZipFile]::ExtractToDirectory("$BOOST.zip", '.') } -if (!(Test-Path $TBB)) { [IO.Compression.ZipFile]::ExtractToDirectory("$TBB.zip", '.') } -if (!(Test-Path $CURL)) { [IO.Compression.ZipFile]::ExtractToDirectory("$CURL.zip", '.') } - - -# Build libraries: -echo 'Building ...' - -# Build boost -pushd "$BOOST" -.\bootstrap -$adr_mode = ('32', '64')[!$b32] -.\b2 ` - -j "$NPROC" ` - --with-system ` - --with-filesystem ` - --with-thread ` - --with-log ` - --with-locale ` - --with-regex ` - "--prefix=$destdir/usr/local" ` - "address-model=$adr_mode" ` - toolset=msvc-12.0 ` - link=static ` - variant=release ` - threading=multi ` - boost.locale.icu=off ` - install -popd - -# Build TBB -pushd "$TBB" -mkdir 'mybuild' -ea 0 -cd 'mybuild' -$generator = ('Visual Studio 12', 'Visual Studio 12 Win64')[!$b32] -cmake .. ` - -G "$generator" ` - -DCMAKE_CONFIGURATION_TYPES=Release ` - -DTBB_BUILD_SHARED=OFF ` - -DTBB_BUILD_TESTS=OFF "-DCMAKE_INSTALL_PREFIX:PATH=$destdir\usr\local" -msbuild /P:Configuration=Release INSTALL.vcxproj -popd - -# Build libcurl: -pushd "$CURL\winbuild" -$machine = ("x86", "x64")[!$b32] -nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=$machine" -Copy-Item -R -Force ..\builds\libcurl-*-winssl\include\* "$destdir\usr\local\include\" -Copy-Item -R -Force ..\builds\libcurl-*-winssl\lib\* "$destdir\usr\local\lib\" -popd - - -echo "" -echo "All done!" -echo "" - - -} -catch [Exception] -{ - # This prints errors in a verbose manner - echo $_.Exception|format-list -force -} diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 20b79a376..db360c811 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -41,6 +41,7 @@ our $PROCESS_COMPLETED_EVENT = Wx::NewEventType; my $PreventListEvents = 0; our $appController; +# XXX: VK: done, except callback handling and timer sub new { my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); @@ -144,18 +145,104 @@ sub new { $self->schedule_background_process; }; + # callback to react to gizmo scale + my $on_gizmo_scale_3D = sub { + my ($scale_x, $scale_y, $scale_z) = @_; + + my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + + my $model_object = $self->{model}->objects->[$obj_idx]; + my $model_instance = $model_object->instances->[0]; + + $self->stop_background_process; + + #FIXME Scale the layer height profile? +# my $variation = $scale / $model_instance->scaling_factor; +# foreach my $range (@{ $model_object->layer_height_ranges }) { +# $range->[0] *= $variation; +# $range->[1] *= $variation; +# } + + my $scale = Slic3r::Pointf3->new($scale_x, $scale_y, $scale_z); + foreach my $inst (@{ $model_object->instances }) { + $inst->set_scaling_factors($scale); + } + Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); + + #update print and start background processing + $self->{print}->add_model_object($model_object, $obj_idx); + + $self->selection_changed(1); # refresh info (size, volume etc.) + $self->update; + $self->schedule_background_process; + + }; + # callback to react to gizmo rotate my $on_gizmo_rotate = sub { my ($angle) = @_; $self->rotate(rad2deg($angle), Z, 'absolute'); }; + # callback to react to gizmo rotate + my $on_gizmo_rotate_3D = sub { + my ($angle_x, $angle_y, $angle_z) = @_; + + my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + + my $model_object = $self->{model}->objects->[$obj_idx]; + my $model_instance = $model_object->instances->[0]; + + $self->stop_background_process; + + my $rotation = Slic3r::Pointf3->new($angle_x, $angle_y, $angle_z); + foreach my $inst (@{ $model_object->instances }) { + $inst->set_rotations($rotation); + } + Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); + + # update print and start background processing + $self->{print}->add_model_object($model_object, $obj_idx); + + $self->selection_changed; # refresh info (size etc.) + $self->update; + $self->schedule_background_process; + }; + # callback to react to gizmo flatten my $on_gizmo_flatten = sub { my ($angle, $axis_x, $axis_y, $axis_z) = @_; $self->rotate(rad2deg($angle), undef, 'absolute', $axis_x, $axis_y, $axis_z) if $angle != 0; }; + # callback to react to gizmo flatten + my $on_gizmo_flatten_3D = sub { + my ($angle_x, $angle_y, $angle_z) = @_; + + my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + + my $model_object = $self->{model}->objects->[$obj_idx]; + my $model_instance = $model_object->instances->[0]; + + $self->stop_background_process; + + my $rotation = Slic3r::Pointf3->new($angle_x, $angle_y, $angle_z); + foreach my $inst (@{ $model_object->instances }) { + $inst->set_rotations($rotation); + } + Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); + + # update print and start background processing + $self->{print}->add_model_object($model_object, $obj_idx); + + $self->selection_changed; # refresh info (size etc.) + $self->update; + $self->schedule_background_process; + }; + # callback to update object's geometry info while using gizmos my $on_update_geometry_info = sub { my ($size_x, $size_y, $size_z, $scale_factor) = @_; @@ -171,6 +258,21 @@ sub new { } }; + # callback to update object's geometry info while using gizmos + my $on_update_geometry_3D_info = sub { + my ($size_x, $size_y, $size_z, $scale_x, $scale_y, $scale_z) = @_; + + my ($obj_idx, $object) = $self->selected_object; + + if ((defined $obj_idx) && ($self->{object_info_size})) { # have we already loaded the info pane? + $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", $size_x, $size_y, $size_z)); + my $model_object = $self->{model}->objects->[$obj_idx]; + if (my $stats = $model_object->mesh_stats) { + $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * $scale_x * $scale_y * $scale_z)); + } + } + }; + # callbacks for toolbar my $on_action_add = sub { $self->add; @@ -260,9 +362,13 @@ sub new { Slic3r::GUI::_3DScene::register_on_instance_moved_callback($self->{canvas3D}, $on_instances_moved); Slic3r::GUI::_3DScene::register_on_enable_action_buttons_callback($self->{canvas3D}, $enable_action_buttons); Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); + Slic3r::GUI::_3DScene::register_on_gizmo_scale_3D_callback($self->{canvas3D}, $on_gizmo_scale_3D); Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); + Slic3r::GUI::_3DScene::register_on_gizmo_rotate_3D_callback($self->{canvas3D}, $on_gizmo_rotate_3D); Slic3r::GUI::_3DScene::register_on_gizmo_flatten_callback($self->{canvas3D}, $on_gizmo_flatten); + Slic3r::GUI::_3DScene::register_on_gizmo_flatten_3D_callback($self->{canvas3D}, $on_gizmo_flatten_3D); Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); + Slic3r::GUI::_3DScene::register_on_update_geometry_3D_info_callback($self->{canvas3D}, $on_update_geometry_3D_info); Slic3r::GUI::_3DScene::register_action_add_callback($self->{canvas3D}, $on_action_add); Slic3r::GUI::_3DScene::register_action_delete_callback($self->{canvas3D}, $on_action_delete); Slic3r::GUI::_3DScene::register_action_deleteall_callback($self->{canvas3D}, $on_action_deleteall); @@ -296,29 +402,42 @@ sub new { } }); - Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); + Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, + sub { + $self->{preview_iface}->set_viewport_from_scene($self->{canvas3D}); + }); +# Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); } Slic3r::GUI::register_on_request_update_callback(sub { $self->schedule_background_process; }); # Initialize 3D toolpaths preview if ($Slic3r::GUI::have_OpenGL) { - $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config}); - Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); - Slic3r::GUI::_3DScene::enable_dynamic_background($self->{preview3D}->canvas, 1); - Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); }); - $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview')); + $self->{preview_iface} = Slic3r::GUI::create_preview_iface($self->{preview_notebook}, $self->{config}, $self->{print}, $self->{gcode_preview_data}); + $self->{preview_page_idx} = $self->{preview_notebook}->GetPageCount-1; + $self->{preview_iface}->register_on_viewport_changed_callback(sub { $self->{preview_iface}->set_viewport_into_scene($self->{canvas3D}); }); +# $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config}); +# Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); +# Slic3r::GUI::_3DScene::enable_dynamic_background($self->{preview3D}->canvas, 1); +# Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); }); +# $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview')); $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; } EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { my $preview = $self->{preview_notebook}->GetCurrentPage; - if (($preview != $self->{preview3D}) && ($preview != $self->{canvas3D})) { + my $page_id = $self->{preview_notebook}->GetSelection; + if (($preview != $self->{canvas3D}) && ($page_id != $self->{preview_page_idx})) { +# if (($preview != $self->{preview3D}) && ($preview != $self->{canvas3D})) { $preview->OnActivate if $preview->can('OnActivate'); - } elsif ($preview == $self->{preview3D}) { - $self->{preview3D}->reload_print; + } elsif ($page_id == $self->{preview_page_idx}) { + $self->{preview_iface}->reload_print; # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) - Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); + $self->{preview_iface}->set_canvas_as_dirty; +# } elsif ($preview == $self->{preview3D}) { +# $self->{preview3D}->reload_print; +# # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) +# Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); } elsif ($preview == $self->{canvas3D}) { if (Slic3r::GUI::_3DScene::is_reload_delayed($self->{canvas3D})) { my $selections = $self->collect_selections; @@ -461,7 +580,8 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), - $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; + $self, $self->{canvas3D}, $self->{preview_iface}, $self->{list}; +# $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; # $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $SLICING_COMPLETED_EVENT, sub { @@ -474,6 +594,7 @@ sub new { $self->on_process_completed($event->GetInt ? undef : $event->GetString); }); +# XXX: not done { my $timer_id = Wx::NewId(); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); @@ -488,9 +609,10 @@ sub new { Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape); Slic3r::GUI::_3DScene::zoom_to_bed($self->{canvas3D}); } - if ($self->{preview3D}) { - Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape); - } + $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); +# if ($self->{preview3D}) { +# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape); +# } $self->update; { @@ -692,12 +814,14 @@ sub new { return $self; } +# XXX: VK: WIP # sets the callback sub on_select_preset { my ($self, $cb) = @_; $self->{on_select_preset} = $cb; } +# XXX: merged with on_select_preset # Called from the platter combo boxes selecting the active print, filament or printer. sub _on_select_preset { my ($self, $group, $choice, $idx) = @_; @@ -734,6 +858,7 @@ sub _on_select_preset { $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } +# XXX: VK: done sub on_layer_editing_toggled { my ($self, $new_state) = @_; Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state); @@ -752,11 +877,13 @@ sub on_layer_editing_toggled { $self->{canvas3D}->Update; } +# XXX: VK: done (Plater::priv::main_frame) sub GetFrame { my ($self) = @_; return &Wx::GetTopLevelParent($self); } +# XXX: not done # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings @@ -768,6 +895,7 @@ sub update_ui_from_settings } } +# XXX: VK: done # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. # Called by # Slic3r::GUI::Tab::Print::_on_presets_changed @@ -813,12 +941,14 @@ sub update_presets { wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); } +# XXX: VK: done, in on_action_add() sub add { my ($self) = @_; my @input_files = wxTheApp->open_model($self); $self->load_files(\@input_files); } +# XXX: VK: done sub load_files { my ($self, $input_files) = @_; @@ -840,7 +970,7 @@ sub load_files { # Object indices for the UI. my @obj_idx = (); # Collected file names to display the final message on the status bar. - my @loaded_files = (); + my @loaded_files = (); # XXX: used??? # For all input files. for (my $i = 0; $i < @$input_files; $i += 1) { my $input_file = $input_files->[$i]; @@ -876,7 +1006,7 @@ sub load_files { # objects imported from 3mf require a call to center_around_origin to have gizmos working properly and this call # need to be done after looks_like_multipart_object detection - if ($input_file =~ /.3[mM][fF]$/) + if ($input_file =~ /[.]3[mM][fF]$/) { foreach my $model_object (@{$model->objects}) { $model_object->center_around_origin; # also aligns object to Z = 0 @@ -909,6 +1039,7 @@ sub load_files { return @obj_idx; } +# XXX: VK: done, except a few todos sub load_model_objects { my ($self, @model_objects) = @_; @@ -991,6 +1122,7 @@ sub load_model_objects { return @obj_idx; } +# XXX: Removed, replaced with bed_shape_bb() sub bed_centerf { my ($self) = @_; @@ -999,13 +1131,15 @@ sub bed_centerf { return Slic3r::Pointf->new(unscale($bed_center->x), unscale($bed_center->y)); #) } +# XXX: VK: done sub remove { my ($self, $obj_idx) = @_; $self->stop_background_process; # Prevent toolpaths preview from rendering while we modify the Print object - $self->{preview3D}->enabled(0) if $self->{preview3D}; + $self->{preview_iface}->set_enabled(0) if $self->{preview_iface}; +# $self->{preview3D}->enabled(0) if $self->{preview3D}; # If no object index is supplied, remove the selected one. if (! defined $obj_idx) { @@ -1024,13 +1158,15 @@ sub remove { $self->update; } +# XXX: VK: done sub reset { my ($self) = @_; $self->stop_background_process; # Prevent toolpaths preview from rendering while we modify the Print object - $self->{preview3D}->enabled(0) if $self->{preview3D}; + $self->{preview_iface}->set_enabled(0) if $self->{preview_iface}; +# $self->{preview3D}->enabled(0) if $self->{preview3D}; @{$self->{objects}} = (); $self->{model}->clear_objects; @@ -1043,6 +1179,7 @@ sub reset { $self->update; } +# XXX: VK: done sub increase { my ($self, $copies) = @_; $copies //= 1; @@ -1074,6 +1211,7 @@ sub increase { $self->schedule_background_process; } +# XXX: VK: done sub decrease { my ($self, $copies_asked) = @_; my $copies = $copies_asked // 1; @@ -1101,6 +1239,7 @@ sub decrease { $self->update; } +# XXX: VK: done sub set_number_of_copies { my ($self) = @_; # get current number of copies @@ -1119,6 +1258,7 @@ sub set_number_of_copies { } } +# XXX: VK: removed sub _get_number_from_user { my ($self, $title, $prompt_message, $error_message, $default, $only_positive) = @_; for (;;) { @@ -1141,6 +1281,7 @@ sub _get_number_from_user { } } +# XXX: not done sub rotate { my ($self, $angle, $axis, $relative_key, $axis_x, $axis_y, $axis_z) = @_; $relative_key //= 'absolute'; # relative or absolute coordinates @@ -1211,6 +1352,7 @@ sub rotate { $self->update; } +# XXX: not done sub mirror { my ($self, $axis) = @_; @@ -1240,6 +1382,7 @@ sub mirror { $self->update; } +# XXX: not done, renamed as Plater::priv::scale() sub changescale { my ($self, $axis, $tosize) = @_; @@ -1314,6 +1457,7 @@ sub changescale { $self->update; } +# XXX: VK: WIP sub arrange { my ($self) = @_; @@ -1331,6 +1475,7 @@ sub arrange { $self->update(0); } +# XXX: not done sub split_object { my $self = shift; @@ -1362,6 +1507,7 @@ sub split_object { } } +# XXX: not done # Trigger $self->async_apply_config() after 500ms. # The call is delayed to avoid restarting the background processing during typing into an edit field. sub schedule_background_process { @@ -1369,6 +1515,7 @@ sub schedule_background_process { $self->{apply_config_timer}->Start(0.5 * 1000, 1); # 1 = one shot, every half a second. } +# XXX: not done # Executed asynchronously by a timer every PROCESS_DELAY (0.5 second). # The timer is started by schedule_background_process(), sub async_apply_config { @@ -1391,7 +1538,8 @@ sub async_apply_config { # Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. # Otherwise they will be just refreshed. $self->{gcode_preview_data}->reset; - $self->{preview3D}->reload_print if $self->{preview3D}; + $self->{preview_iface}->reload_print if $self->{preview_iface}; +# $self->{preview3D}->reload_print if $self->{preview3D}; # We also need to reload 3D scene because of the wipe tower preview box if ($self->{config}->wipe_tower) { my $selections = $self->collect_selections; @@ -1402,6 +1550,7 @@ sub async_apply_config { } } +# XXX: not done # Background processing is started either by the "Slice now" button, by the "Export G-code button" or by async_apply_config(). sub start_background_process { my ($self) = @_; @@ -1422,13 +1571,16 @@ sub start_background_process { $self->{background_slicing_process}->start; } +# XXX: not done # Stop the background processing sub stop_background_process { my ($self) = @_; $self->{background_slicing_process}->stop(); - $self->{preview3D}->reload_print if $self->{preview3D}; + $self->{preview_iface}->reload_print if $self->{preview_iface}; +# $self->{preview3D}->reload_print if $self->{preview3D}; } +# XXX: not done # Called by the "Slice now" button, which is visible only if the background processing is disabled. sub reslice { # explicitly cancel a previous thread and start a new one. @@ -1449,6 +1601,7 @@ sub reslice { } } +# XXX: VK: done sub export_gcode { my ($self, $output_file) = @_; @@ -1529,10 +1682,12 @@ sub export_gcode { return $self->{export_gcode_output_file}; } +# XXX: not done # This message should be called by the background process synchronously. sub on_update_print_preview { my ($self) = @_; - $self->{preview3D}->reload_print if $self->{preview3D}; + $self->{preview_iface}->reload_print if $self->{preview_iface}; +# $self->{preview3D}->reload_print if $self->{preview3D}; # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: my $selections = $self->collect_selections; @@ -1540,6 +1695,7 @@ sub on_update_print_preview { Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); } +# XXX: not done # This gets called also if we have no threads. sub on_progress_event { my ($self, $percent, $message) = @_; @@ -1550,6 +1706,7 @@ sub on_progress_event { $self->statusbar->SetStatusText("$message..."); } +# XXX: not done # Called when the G-code export finishes, either successfully or with an error. # This gets called also if we don't have threads. sub on_process_completed { @@ -1613,9 +1770,11 @@ sub on_process_completed { $self->object_list_changed; # refresh preview - $self->{preview3D}->reload_print if $self->{preview3D}; + $self->{preview_iface}->reload_print if $self->{preview_iface}; +# $self->{preview3D}->reload_print if $self->{preview3D}; } +# XXX: partially done in the Sidebar # Fill in the "Sliced info" box with the result of the G-code generator. sub print_info_box_show { my ($self, $show) = @_; @@ -1690,9 +1849,9 @@ sub print_info_box_show { $self->Layout; $panel->Refresh; - $self->Layout; } +# XXX: not done - to be removed sub do_print { my ($self) = @_; @@ -1706,6 +1865,7 @@ sub do_print { $printer_panel->load_print_job($self->{print_file}, $filament_stats); } +# XXX: VK: done sub export_stl { my ($self) = @_; return if !@{$self->{objects}}; @@ -1716,6 +1876,7 @@ sub export_stl { $self->statusbar->SetStatusText(L("STL file exported to ").$output_file); } +# XXX: VK: done sub reload_from_disk { my ($self) = @_; @@ -1747,6 +1908,7 @@ sub reload_from_disk { $self->remove($obj_idx); } +# XXX: VK: integrated into Plater::export_stl() sub export_object_stl { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; @@ -1758,6 +1920,7 @@ sub export_object_stl { $self->statusbar->SetStatusText(L("STL file exported to ").$output_file); } +# XXX: not done sub fix_through_netfabb { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; @@ -1786,6 +1949,7 @@ sub fix_through_netfabb { $self->remove($obj_idx); } +# XXX: VK: done sub export_amf { my ($self) = @_; return if !@{$self->{objects}}; @@ -1802,6 +1966,7 @@ sub export_amf { } } +# XXX: VK: done sub export_3mf { my ($self) = @_; return if !@{$self->{objects}}; @@ -1818,6 +1983,7 @@ sub export_3mf { } } +# XXX: VK: done # Ask user to select an output file for a given file format (STl, AMF, 3MF). # Propose a default file name based on the 'output_filename_format' configuration value. sub _get_export_file { @@ -1867,6 +2033,7 @@ sub _get_export_file { # $self->{objects}[$obj_idx]->thumbnail(undef); #} +# XXX: VK: done # this method gets called whenever print center is changed or the objects' bounding box changes # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { @@ -1882,12 +2049,15 @@ sub update { my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); - $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D}; - $self->{preview3D}->reload_print if $self->{preview3D}; + $self->{preview_iface}->reset_gcode_preview_data if $self->{preview_iface}; + $self->{preview_iface}->reload_print if $self->{preview_iface}; +# $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D}; +# $self->{preview3D}->reload_print if $self->{preview3D}; $self->schedule_background_process; $self->Thaw; } +# XXX: YS: done # When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. sub show_preset_comboboxes{ my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF" @@ -1906,6 +2076,7 @@ sub show_preset_comboboxes{ $self->Layout; } +# XXX: YS: done # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly # and some reasonable default has to be selected for the additional extruders. @@ -1950,6 +2121,7 @@ sub on_extruders_change { $self->Layout; } +# XXX: not done sub on_config_change { my ($self, $config) = @_; @@ -1959,7 +2131,8 @@ sub on_config_change { if ($opt_key eq 'bed_shape') { # $self->{canvas}->update_bed_size; Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D}; - Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; + $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); +# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; $update_scheduled = 1; } elsif ($opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material') { $update_scheduled = 1; @@ -1994,13 +2167,15 @@ sub on_config_change { } elsif ($opt_key eq 'extruder_colour') { $update_scheduled = 1; my $extruder_colors = $config->get('extruder_colour'); - $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); + $self->{preview_iface}->set_number_extruders(scalar(@{$extruder_colors})); +# $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); } elsif ($opt_key eq 'max_print_height') { $update_scheduled = 1; } elsif ($opt_key eq 'printer_model') { # update to force bed selection (for texturing) Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D}; - Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; + $self->{preview_iface}->set_bed_shape($self->{config}->bed_shape) if ($self->{preview_iface}); +# Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; $update_scheduled = 1; } } @@ -2013,6 +2188,7 @@ sub on_config_change { $self->schedule_background_process; } +# XXX: YS: WIP sub item_changed_selection { my ($self, $obj_idx) = @_; @@ -2028,6 +2204,7 @@ sub item_changed_selection { } } +# XXX: VK: done sub collect_selections { my ($self) = @_; my $selections = []; @@ -2037,6 +2214,7 @@ sub collect_selections { return $selections; } +# XXX: YS: done, lambda on LEFT_DOWN # Called when clicked on the filament preset combo box. # When clicked on the icon, show the color picker. sub filament_color_box_lmouse_down @@ -2090,6 +2268,7 @@ sub filament_color_box_lmouse_down # } #} +# XXX: YS: done sub changed_object_settings { my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_; @@ -2115,6 +2294,7 @@ sub changed_object_settings { } } +# XXX: VK: done # Called to update various buttons depending on whether there are any objects or # whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active. sub object_list_changed { @@ -2145,6 +2325,7 @@ sub object_list_changed { for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } +# XXX: VK: WIP # Selection of an active 3D object changed. sub selection_changed { my ($self) = @_; @@ -2262,6 +2443,7 @@ sub selection_changed { $self->{right_panel}->Thaw; } +# XXX: VK: done sub select_object { my ($self, $obj_idx, $child) = @_; @@ -2282,6 +2464,7 @@ sub select_object { $self->selection_changed(1); } +# XXX: YS: WIP sub select_object_from_cpp { my ($self, $obj_idx, $vol_idx) = @_; @@ -2326,16 +2509,19 @@ sub select_object_from_cpp { $self->selection_changed(1); } +# XXX: VK: done sub selected_object { my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } +# XXX: VK: done sub statusbar { return $_[0]->GetFrame->{statusbar}; } +# XXX: not done, to be removed (?) sub object_menu { my ($self) = @_; @@ -2446,20 +2632,26 @@ sub object_menu { return $menu; } +# XXX: not done # Set a camera direction, zoom to all objects. sub select_view { my ($self, $direction) = @_; my $idx_page = $self->{preview_notebook}->GetSelection; my $page = ($idx_page == &Wx::wxNOT_FOUND) ? L('3D') : $self->{preview_notebook}->GetPageText($idx_page); if ($page eq L('Preview')) { - Slic3r::GUI::_3DScene::select_view($self->{preview3D}->canvas, $direction); - Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); + $self->{preview_iface}->select_view($direction); + $self->{preview_iface}->set_viewport_into_scene($self->{canvas3D}); +# Slic3r::GUI::_3DScene::select_view($self->{preview3D}->canvas, $direction); +# Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); } else { Slic3r::GUI::_3DScene::select_view($self->{canvas3D}, $direction); - Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); + $self->{preview_iface}->set_viewport_from_scene($self->{canvas3D}); +# Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); } } + +# XXX: VK: done, in PlaterDropTarget package Slic3r::GUI::Plater::DropTarget; use Wx::DND; use base 'Wx::FileDropTarget'; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 22a76100d..ec31f6c8a 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -122,9 +122,9 @@ sub add_instance { my $new_instance = $self->_add_instance; - $new_instance->set_rotation($args{rotation}) + $new_instance->set_rotations($args{rotation}) if defined $args{rotation}; - $new_instance->set_scaling_factor($args{scaling_factor}) + $new_instance->set_scaling_factors($args{scaling_factor}) if defined $args{scaling_factor}; $new_instance->set_offset($args{offset}) if defined $args{offset}; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index c0137f9e4..b767ca593 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -158,8 +158,12 @@ sub model { $object->add_volume(mesh => $mesh, material_id => $model_name); $object->add_instance( offset => Slic3r::Pointf->new(0,0), - rotation => $params{rotation} // 0, - scaling_factor => $params{scale} // 1, + # 3D full transform + rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), + scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), + # old transform +# rotation => $params{rotation} // 0, +# scaling_factor => $params{scale} // 1, ); return $model; } diff --git a/resources/fonts/NotoSans-Regular.ttf b/resources/fonts/NotoSans-Regular.ttf new file mode 100644 index 000000000..a1b8994ed Binary files /dev/null and b/resources/fonts/NotoSans-Regular.ttf differ diff --git a/resources/fonts/NotoSans-hinted.zip b/resources/fonts/NotoSans-hinted.zip new file mode 100644 index 000000000..861c1be8c Binary files /dev/null and b/resources/fonts/NotoSans-hinted.zip differ diff --git a/resources/icons/bed/mk2_bottom.png b/resources/icons/bed/mk2_bottom.png index d06de22eb..ed94b0d62 100644 Binary files a/resources/icons/bed/mk2_bottom.png and b/resources/icons/bed/mk2_bottom.png differ diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png index e93430b09..63b034a99 100644 Binary files a/resources/icons/bed/mk2_top.png and b/resources/icons/bed/mk2_top.png differ diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png index 3f12e7efb..f72cd93da 100644 Binary files a/resources/icons/bed/mk3_bottom.png and b/resources/icons/bed/mk3_bottom.png differ diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png index 967448497..e6f3261b5 100644 Binary files a/resources/icons/bed/mk3_top.png and b/resources/icons/bed/mk3_top.png differ diff --git a/resources/icons/bed/sl1_bottom.png b/resources/icons/bed/sl1_bottom.png new file mode 100644 index 000000000..04272ac22 Binary files /dev/null and b/resources/icons/bed/sl1_bottom.png differ diff --git a/resources/icons/bed/sl1_top.png b/resources/icons/bed/sl1_top.png new file mode 100644 index 000000000..49305f531 Binary files /dev/null and b/resources/icons/bed/sl1_top.png differ diff --git a/resources/icons/mode_expert.png b/resources/icons/mode_expert.png new file mode 100644 index 000000000..1716bab44 Binary files /dev/null and b/resources/icons/mode_expert.png differ diff --git a/resources/icons/mode_expert_.png b/resources/icons/mode_expert_.png new file mode 100644 index 000000000..4d78bcccf Binary files /dev/null and b/resources/icons/mode_expert_.png differ diff --git a/resources/icons/mode_middle.png b/resources/icons/mode_middle.png new file mode 100644 index 000000000..b08f73ca6 Binary files /dev/null and b/resources/icons/mode_middle.png differ diff --git a/resources/icons/mode_middle_.png b/resources/icons/mode_middle_.png new file mode 100644 index 000000000..d98d8f709 Binary files /dev/null and b/resources/icons/mode_middle_.png differ diff --git a/resources/icons/mode_simple.png b/resources/icons/mode_simple.png new file mode 100644 index 000000000..8bc300228 Binary files /dev/null and b/resources/icons/mode_simple.png differ diff --git a/resources/icons/mode_simple_.png b/resources/icons/mode_simple_.png new file mode 100644 index 000000000..aac2b61b0 Binary files /dev/null and b/resources/icons/mode_simple_.png differ diff --git a/resources/icons/move_hover.png b/resources/icons/move_hover.png new file mode 100644 index 000000000..8cd66cab9 Binary files /dev/null and b/resources/icons/move_hover.png differ diff --git a/resources/icons/move_off.png b/resources/icons/move_off.png new file mode 100644 index 000000000..9e44690df Binary files /dev/null and b/resources/icons/move_off.png differ diff --git a/resources/icons/move_on.png b/resources/icons/move_on.png new file mode 100644 index 000000000..c19be461d Binary files /dev/null and b/resources/icons/move_on.png differ diff --git a/resources/icons/overlay/cut_hover.png b/resources/icons/overlay/cut_hover.png new file mode 100644 index 000000000..eccce0243 Binary files /dev/null and b/resources/icons/overlay/cut_hover.png differ diff --git a/resources/icons/overlay/cut_off.png b/resources/icons/overlay/cut_off.png new file mode 100644 index 000000000..4d0f8c93f Binary files /dev/null and b/resources/icons/overlay/cut_off.png differ diff --git a/resources/icons/overlay/cut_on.png b/resources/icons/overlay/cut_on.png new file mode 100644 index 000000000..967d15458 Binary files /dev/null and b/resources/icons/overlay/cut_on.png differ diff --git a/resources/icons/overlay/layflat_hover.png b/resources/icons/overlay/layflat_hover.png index afce81d19..31ee91f46 100644 Binary files a/resources/icons/overlay/layflat_hover.png and b/resources/icons/overlay/layflat_hover.png differ diff --git a/resources/icons/overlay/layflat_off.png b/resources/icons/overlay/layflat_off.png index 70d198112..4feadbaf4 100644 Binary files a/resources/icons/overlay/layflat_off.png and b/resources/icons/overlay/layflat_off.png differ diff --git a/resources/icons/overlay/layflat_on.png b/resources/icons/overlay/layflat_on.png index 3c1891b3c..93a186957 100644 Binary files a/resources/icons/overlay/layflat_on.png and b/resources/icons/overlay/layflat_on.png differ diff --git a/resources/icons/overlay/move_hover.png b/resources/icons/overlay/move_hover.png index 99dc4cf8d..8cd66cab9 100644 Binary files a/resources/icons/overlay/move_hover.png and b/resources/icons/overlay/move_hover.png differ diff --git a/resources/icons/overlay/move_off.png b/resources/icons/overlay/move_off.png index cd4f130e1..9e44690df 100644 Binary files a/resources/icons/overlay/move_off.png and b/resources/icons/overlay/move_off.png differ diff --git a/resources/icons/overlay/move_on.png b/resources/icons/overlay/move_on.png index 7e78e0e6a..c19be461d 100644 Binary files a/resources/icons/overlay/move_on.png and b/resources/icons/overlay/move_on.png differ diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png index 56d4fd277..818abcbdc 100644 Binary files a/resources/icons/overlay/rotate_hover.png and b/resources/icons/overlay/rotate_hover.png differ diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png index 8491f7e43..d8037d773 100644 Binary files a/resources/icons/overlay/rotate_off.png and b/resources/icons/overlay/rotate_off.png differ diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png index e2db5120c..f40f28d4b 100644 Binary files a/resources/icons/overlay/rotate_on.png and b/resources/icons/overlay/rotate_on.png differ diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png index d620aee7a..394cb4b20 100644 Binary files a/resources/icons/overlay/scale_hover.png and b/resources/icons/overlay/scale_hover.png differ diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png index 1ae999bbe..fac035099 100644 Binary files a/resources/icons/overlay/scale_off.png and b/resources/icons/overlay/scale_off.png differ diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png index 62e805f12..4d4c075f3 100644 Binary files a/resources/icons/overlay/scale_on.png and b/resources/icons/overlay/scale_on.png differ diff --git a/resources/icons/overlay/sla_support_points_hover.png b/resources/icons/overlay/sla_support_points_hover.png new file mode 100644 index 000000000..6303232b2 Binary files /dev/null and b/resources/icons/overlay/sla_support_points_hover.png differ diff --git a/resources/icons/overlay/sla_support_points_off.png b/resources/icons/overlay/sla_support_points_off.png new file mode 100644 index 000000000..b654366d5 Binary files /dev/null and b/resources/icons/overlay/sla_support_points_off.png differ diff --git a/resources/icons/overlay/sla_support_points_on.png b/resources/icons/overlay/sla_support_points_on.png new file mode 100644 index 000000000..8c1e69565 Binary files /dev/null and b/resources/icons/overlay/sla_support_points_on.png differ diff --git a/resources/icons/printers/PrusaResearch_SL1.png b/resources/icons/printers/PrusaResearch_SL1.png new file mode 100644 index 000000000..281d7a620 Binary files /dev/null and b/resources/icons/printers/PrusaResearch_SL1.png differ diff --git a/resources/icons/scale_hover.png b/resources/icons/scale_hover.png new file mode 100644 index 000000000..394cb4b20 Binary files /dev/null and b/resources/icons/scale_hover.png differ diff --git a/resources/icons/scale_off.png b/resources/icons/scale_off.png new file mode 100644 index 000000000..fac035099 Binary files /dev/null and b/resources/icons/scale_off.png differ diff --git a/resources/icons/scale_on.png b/resources/icons/scale_on.png new file mode 100644 index 000000000..4d4c075f3 Binary files /dev/null and b/resources/icons/scale_on.png differ diff --git a/resources/icons/shape_ungroup_o.png b/resources/icons/shape_ungroup_o.png new file mode 100644 index 000000000..97746d32c Binary files /dev/null and b/resources/icons/shape_ungroup_o.png differ diff --git a/resources/icons/shape_ungroup_p.png b/resources/icons/shape_ungroup_p.png new file mode 100644 index 000000000..81c0198ff Binary files /dev/null and b/resources/icons/shape_ungroup_p.png differ diff --git a/resources/icons/sla_support_points_reset.png b/resources/icons/sla_support_points_reset.png new file mode 100644 index 000000000..6e051fe95 Binary files /dev/null and b/resources/icons/sla_support_points_reset.png differ diff --git a/resources/icons/sla_support_points_tooltip.png b/resources/icons/sla_support_points_tooltip.png new file mode 100644 index 000000000..1bdfd3440 Binary files /dev/null and b/resources/icons/sla_support_points_tooltip.png differ diff --git a/resources/icons/support_blocker_.png b/resources/icons/support_blocker_.png new file mode 100644 index 000000000..fc658e4f6 Binary files /dev/null and b/resources/icons/support_blocker_.png differ diff --git a/resources/icons/support_enforcer_.png b/resources/icons/support_enforcer_.png new file mode 100644 index 000000000..ac34b6cb0 Binary files /dev/null and b/resources/icons/support_enforcer_.png differ diff --git a/resources/icons/toolbar.png b/resources/icons/toolbar.png index 79246bcf9..75faea658 100644 Binary files a/resources/icons/toolbar.png and b/resources/icons/toolbar.png differ diff --git a/resources/icons/toolbar_background.png b/resources/icons/toolbar_background.png new file mode 100644 index 000000000..2b5ea013b Binary files /dev/null and b/resources/icons/toolbar_background.png differ diff --git a/resources/icons/view_toolbar.png b/resources/icons/view_toolbar.png new file mode 100644 index 000000000..dd1f5aca4 Binary files /dev/null and b/resources/icons/view_toolbar.png differ diff --git a/resources/localization/CMakeLists.txt b/resources/localization/CMakeLists.txt deleted file mode 100644 index 8a7fba068..000000000 --- a/resources/localization/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(L10N_DIR "${PROJECT_SOURCE_DIR}/resources/localization") -add_custom_target(pot - COMMAND xgettext --keyword=L --from-code=UTF-8 --debug - -f "${L10N_DIR}/list.txt" - -o "${L10N_DIR}/Slic3rPE.pot" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMENT "Generate pot file from strings in the source tree" -) diff --git a/resources/localization/ko_KR/Slic3rPE.mo b/resources/localization/ko_KR/Slic3rPE.mo new file mode 100644 index 000000000..10164d14a Binary files /dev/null and b/resources/localization/ko_KR/Slic3rPE.mo differ diff --git a/resources/localization/ko_KR/Slic3rPE.po b/resources/localization/ko_KR/Slic3rPE.po new file mode 100644 index 000000000..5691b17e4 --- /dev/null +++ b/resources/localization/ko_KR/Slic3rPE.po @@ -0,0 +1,4679 @@ +msgid "" +msgstr "" +"Project-Id-Version: slic3rkorean\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-07-23 12:06+0200\n" +"PO-Revision-Date: 2018-12-11 02:52+0900\n" +"Last-Translator: 이학민 \n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.2\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Crowdin-Project: slic3rkorean\n" +"X-Crowdin-Language: ko\n" +"X-Crowdin-File: ko_KR.po\n" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:32 +msgid "About Slic3r" +msgstr "Slic3r에 대하여" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:67 +msgid "Version" +msgstr "버전" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:39 +msgid "Shape" +msgstr "모양" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:46 +msgid "Rectangular" +msgstr "직사각형" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1826 +#: lib/Slic3r/GUI/Plater.pm:498 +msgid "Size" +msgstr "사이즈" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:51 +msgid "Size in X and Y of the rectangular plate." +msgstr "사격형 플레이트 X 및 Y 크기" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:57 +msgid "Origin" +msgstr "원본" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:58 +msgid "Distance of the 0,0 G-code coordinate from the front left corner of the rectangle." +msgstr "사각형의 전면 왼쪽된 모서리에서 0, 0 G-코드 좌표 거리입니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:62 +msgid "Circular" +msgstr "원형" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 xs/src/slic3r/GUI/ConfigWizard.cpp:446 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:460 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +#: xs/src/libslic3r/PrintConfig.cpp:133 xs/src/libslic3r/PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:189 xs/src/libslic3r/PrintConfig.cpp:237 +#: xs/src/libslic3r/PrintConfig.cpp:248 xs/src/libslic3r/PrintConfig.cpp:363 +#: xs/src/libslic3r/PrintConfig.cpp:374 xs/src/libslic3r/PrintConfig.cpp:393 +#: xs/src/libslic3r/PrintConfig.cpp:531 xs/src/libslic3r/PrintConfig.cpp:890 +#: xs/src/libslic3r/PrintConfig.cpp:1002 xs/src/libslic3r/PrintConfig.cpp:1010 +#: xs/src/libslic3r/PrintConfig.cpp:1068 xs/src/libslic3r/PrintConfig.cpp:1086 +#: xs/src/libslic3r/PrintConfig.cpp:1104 xs/src/libslic3r/PrintConfig.cpp:1166 +#: xs/src/libslic3r/PrintConfig.cpp:1176 xs/src/libslic3r/PrintConfig.cpp:1292 +#: xs/src/libslic3r/PrintConfig.cpp:1300 xs/src/libslic3r/PrintConfig.cpp:1342 +#: xs/src/libslic3r/PrintConfig.cpp:1351 xs/src/libslic3r/PrintConfig.cpp:1361 +#: xs/src/libslic3r/PrintConfig.cpp:1369 xs/src/libslic3r/PrintConfig.cpp:1377 +#: xs/src/libslic3r/PrintConfig.cpp:1463 xs/src/libslic3r/PrintConfig.cpp:1669 +#: xs/src/libslic3r/PrintConfig.cpp:1739 xs/src/libslic3r/PrintConfig.cpp:1773 +#: xs/src/libslic3r/PrintConfig.cpp:1969 xs/src/libslic3r/PrintConfig.cpp:1976 +#: xs/src/libslic3r/PrintConfig.cpp:1983 xs/src/libslic3r/PrintConfig.cpp:2015 +#: xs/src/libslic3r/PrintConfig.cpp:2025 xs/src/libslic3r/PrintConfig.cpp:2035 +msgid "mm" +msgstr "mm" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 xs/src/libslic3r/PrintConfig.cpp:528 +msgid "Diameter" +msgstr "노즐 직경:" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:67 +msgid "Diameter of the print bed. It is assumed that origin (0,0) is located in the center." +msgstr "인쇄 침대의 직경. 원점 (0,0) 은 중심에 있다고 가정합니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:71 +#: xs/src/libslic3r/GCode/PreviewData.cpp:175 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:102 +msgid "Custom" +msgstr "커스터" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:75 +msgid "Load shape from STL..." +msgstr "STL파일 로드." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:120 +msgid "Settings" +msgstr "설정" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:299 +msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "침대 모양 (STL/OBJ/AMF/3MF/PRUSA) 에서 가져오려는 파일을 선택 합니다:" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:316 +msgid "Error! " +msgstr "에러!" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:325 +msgid "The selected file contains no geometry." +msgstr "선택한 파일에는 형상이 없는 포함 되어 있습니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:329 +msgid "The selected file contains several disjoint areas. This is not supported." +msgstr "선택한 파일 여러 분리 된 영역을 포함 되어 있습니다. 이 지원 되지 않습니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 +msgid "Bed Shape" +msgstr "배드 모양" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:53 +msgid "Network lookup" +msgstr "네트워크 조회" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:66 +msgid "Address" +msgstr "주소" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:67 +msgid "Hostname" +msgstr "호스트이름" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:68 +msgid "Service name" +msgstr "서비스 이름" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:69 +msgid "OctoPrint version" +msgstr "옥토프린트 버전" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:187 +msgid "Searching for devices" +msgstr "디바이스 검색" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:194 +msgid "Finished" +msgstr "완료" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:13 +msgid "Buttons And Text Colors Description" +msgstr "버튼 및 텍스트 색상 설명" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:38 +msgid "Value is the same as the system value" +msgstr "값은 시스템 값과 같습니다" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:55 +msgid "Value was changed and is not equal to the system value or the last saved preset" +msgstr "값이 변경 되었고 시스템 값 또는 마지막으로 저장 된 사전 설정과 같지 않음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:15 +msgid "Upgrade" +msgstr "업그레이드" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:17 +msgid "Downgrade" +msgstr "다운그레이드" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:19 +msgid "Before roll back" +msgstr "롤백 전에" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:21 +msgid "User" +msgstr "사용자" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:24 +msgid "Unknown" +msgstr "알 수 없음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:36 +msgid "Active: " +msgstr "활성:" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:42 +msgid "slic3r version" +msgstr "slic3r에 대하여" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:43 +msgid "print" +msgstr "프린트" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:44 +msgid "filaments" +msgstr "필라멘트" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:45 +msgid "printer" +msgstr "프린터" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:730 +msgid "vendor" +msgstr "벤더" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 +msgid "version" +msgstr "버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 +msgid "min slic3r version" +msgstr "최소 slic3r 버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 +msgid "max slic3r version" +msgstr "최대 slic3r 버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "model" +msgstr "모델" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "variants" +msgstr "변종" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:67 +msgid "Incompatible with this Slic3r" +msgstr "이 Slic3r와 호환 되지 않음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:70 +msgid "Activate" +msgstr "활성화" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Configuration Snapshots" +msgstr "구성 스냅숏" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 +msgid "nozzle" +msgstr "노즐" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:89 +msgid "(default)" +msgstr "(기본값)" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:108 +msgid "Select all" +msgstr "모두 선택" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:109 +msgid "Select none" +msgstr "선택 없음" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +#, c-format +msgid "Welcome to the Slic3r %s" +msgstr "Slic3r %s에 오신것을 환영 합니다" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +msgid "Welcome" +msgstr "환영합니다" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 xs/src/slic3r/GUI/GUI.cpp:346 +#, c-format +msgid "Run %s" +msgstr "%s 실행" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:226 +#, c-format +msgid "Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print." +msgstr "안녕하세요, Slic3r prusa 버전에 오신 것을 환영 합니다! 이 %s 초기 구성;에 도움이 됩니다. 단지 몇 가지 설정 하 고 당신은 인쇄 준비가 될 것입니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:230 +msgid "Remove user profiles - install from scratch (a snapshot will be taken beforehand)" +msgstr "사용자 프로필 제거-처음부터 설치 (스냅숏은 사전에 수행 됩니다)" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:252 +msgid "Other vendors" +msgstr "다른 공급 업체" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:254 +msgid "Custom setup" +msgstr "사용자 지정 설치" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Automatic updates" +msgstr "자동 업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Updates" +msgstr "업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:59 +msgid "Check for application updates" +msgstr "프로그램 업데이트 확인" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:61 +msgid "If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." +msgstr "활성화 된 경우 Slic3r은 Slic3r PE 온라인의 새 버전을 확인합니다. 새 버전을 사용할 수있게되면 다음 응용 프로그램 시작시 알림이 표시됩니다 (프로그램 사용 중에는 절대로 사용하지 마십시오). 이것은 알림 메커니즘 일뿐 자동 설치가 수행되지 않습니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:293 xs/src/slic3r/GUI/Preferences.cpp:67 +msgid "Update built-in Presets automatically" +msgstr "기존의 설정 자동 업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:296 xs/src/slic3r/GUI/Preferences.cpp:69 +msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." +msgstr "활성화 된 경우 Slic3r은 백그라운드에서 내장 시스템 사전 설정의 업데이트를 다운로드합니다. 이러한 업데이트는 별도의 임시 위치에 다운로드됩니다. 새로운 사전 설정 버전을 사용할 수있게되면 응용 프로그램 시작시 제공됩니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 +msgid "Updates are never applied without user's consent and never overwrite user's customized settings." +msgstr "업데이트는 사용자의 동의없이 적용되지 않으며 사용자의 사용자 지정된 설정을 덮어 쓰지 않습니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 +msgid "Additionally a backup snapshot of the whole configuration is created before an update is applied." +msgstr "또한 업데이트가 적용되기 전에 전체 구성의 백업 스냅 샷이 생성됩니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:309 +msgid "Other Vendors" +msgstr "다른 공급 업체" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:311 +msgid "Pick another vendor supported by Slic3r PE:" +msgstr "Slic3r PE가 지원하는 다른 공급 업체를 선택하십시오:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 +msgid "Firmware Type" +msgstr "펌웨어 타입" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 xs/src/slic3r/GUI/Tab.cpp:1606 +msgid "Firmware" +msgstr "펌웨어" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:374 +msgid "Choose the type of firmware used by your printer." +msgstr "프린터에 패치할 펌웨어를 선택하세요." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 +msgid "Bed Shape and Size" +msgstr "배드 모양과 크기" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 +msgid "Set the shape of your printer's bed." +msgstr "프린터 배드모양을 설정하세요." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Filament and Nozzle Diameters" +msgstr "필라멘트와 노즐 크기" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Print Diameters" +msgstr "인쇄 직경" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:442 +msgid "Enter the diameter of your printer's hot end nozzle." +msgstr "핫 엔드 노즐 직경을 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:445 +msgid "Nozzle Diameter:" +msgstr "노즐 직경:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:455 +msgid "Enter the diameter of your filament." +msgstr "필라멘트의 직경을 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:456 +msgid "Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average." +msgstr "정밀도가 필요하므로 캘리퍼를 사용하여 필라멘트를 따라 여러 번 측정 한 다음 평균을 계산하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:459 +msgid "Filament Diameter:" +msgstr "필라멘트 직경:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Extruder and Bed Temperatures" +msgstr "익스트루더와 배드 온도" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Temperatures" +msgstr "온도 " + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:493 +msgid "Enter the temperature needed for extruding your filament." +msgstr "필라멘트 압출에 필요한 온도를 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:494 +msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." +msgstr "보통 PLA의 경우 160 ~ 230 ° C, ABS의 경우 215 ~ 250 ° C입니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:497 +msgid "Extrusion Temperature:" +msgstr "출력 온도 :" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:498 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:512 +msgid "°C" +msgstr "°C" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:507 +msgid "Enter the bed temperature needed for getting your filament to stick to your heated bed." +msgstr "필라멘트가 핫배드에 접착하는데 필요한 배드온도를 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:508 +msgid "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed." +msgstr "보통은 PLA의 경우 60 ° C이고 ABS의 경우 110 ° C입니다. 핫배드가 없는 경우에는 0으로 두십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:511 +msgid "Bed Temperature:" +msgstr "배드 온도 :" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:824 +msgid "< &Back" +msgstr "< &뒤로" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:825 +msgid "&Next >" +msgstr "&다음 >" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:826 +msgid "&Finish" +msgstr "&완료" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:896 +msgid "Configuration Wizard" +msgstr "구성 마법사" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:898 +msgid "Configuration Assistant" +msgstr "구성 도우미" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:87 +msgid "Flash!" +msgstr "완료!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:88 +msgid "Cancel" +msgstr "취소" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:128 +msgid "Flashing in progress. Please do not disconnect the printer!" +msgstr "아직 플래싱 중입니다. 커넥트를 분리하지 마십시오!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:155 +msgid "Flashing succeeded!" +msgstr "플래싱 성공!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:156 +msgid "Flashing failed. Please see the avrdude log below." +msgstr "플래시 실패. 아래의 로그를 확인하세요." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:157 +msgid "Flashing cancelled." +msgstr "깜빡임 취소됨." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:294 +msgid "Cancelling..." +msgstr "취소 중..." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:347 +msgid "Firmware flasher" +msgstr "펌웨어 플래셔" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:367 +msgid "Serial port:" +msgstr "시리얼 포트:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:369 +msgid "Rescan" +msgstr "재검색" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:374 +msgid "Firmware image:" +msgstr "펌웨어 이미지:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:377 +msgid "Status:" +msgstr "지위:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:378 +msgid "Ready" +msgstr "준비" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:381 +msgid "Progress:" +msgstr "진행:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:400 +msgid "Advanced: avrdude output log" +msgstr "고급: avrdude 로그 출력" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:446 +msgid "" +"Are you sure you want to cancel firmware flashing?\n" +"This could leave your printer in an unusable state!" +msgstr "" +"펌웨어 플래싱을 취소하시겠습니까?\n" +"프린터가 사용할 수 없는 상태가 될 수 있습니다!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:447 +msgid "Confirmation" +msgstr "확인" + +#: xs/src/slic3r/GUI/GLCanvas3D.cpp:2308 +msgid "Detected object outside print volume" +msgstr "출력물이 프린터 출력 사이즈를 넘었습니다" + +#: xs/src/slic3r/GUI/GUI.cpp:233 +msgid "Array of language names and identifiers should have the same size." +msgstr "언어 이름과 식별자 배열은 같은 크기 여야합니다." + +#: xs/src/slic3r/GUI/GUI.cpp:244 +msgid "Select the language" +msgstr "언어를 선택" + +#: xs/src/slic3r/GUI/GUI.cpp:244 +msgid "Language" +msgstr "언어" + +#: xs/src/slic3r/GUI/GUI.cpp:306 xs/src/libslic3r/PrintConfig.cpp:195 +msgid "Default" +msgstr "기본값" + +#: xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Inspect / activate configuration snapshots" +msgstr "구성 스냅 샷 검사 / 활성화" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Take Configuration Snapshot" +msgstr "구성 스냅 샷 가져 오기" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Capture a configuration snapshot" +msgstr "구성 스냅 샷 캡처" + +#: xs/src/slic3r/GUI/GUI.cpp:353 xs/src/slic3r/GUI/Preferences.cpp:9 +msgid "Preferences" +msgstr "환경 설정" + +#: xs/src/slic3r/GUI/GUI.cpp:353 +msgid "Application preferences" +msgstr "응용 프로그램 환경 설정" + +#: xs/src/slic3r/GUI/GUI.cpp:354 +msgid "Change Application Language" +msgstr "응용 프로그램 언어 번경" + +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Flash printer firmware" +msgstr "프린터 펌웨어 플래시" + +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Upload a firmware image into an Arduino based printer" +msgstr "아두이노 기반의 프린터 이미지 업로드" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Taking configuration snapshot" +msgstr "구성 스냅 샷 만들기" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Snapshot name" +msgstr "스냅 샷 이름" + +#: xs/src/slic3r/GUI/GUI.cpp:406 +msgid "Application will be restarted" +msgstr "애플리케이션이 다시 시작됨" + +#: xs/src/slic3r/GUI/GUI.cpp:406 +msgid "Attention!" +msgstr "주목!" + +#: xs/src/slic3r/GUI/GUI.cpp:422 +msgid "&Configuration" +msgstr "&구성" + +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid "You have unsaved changes " +msgstr "저장되지 않은 변경 사항이 있습니다" + +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid ". Discard changes and continue anyway?" +msgstr ". 변경 사항을 취소하고 계속 하시겠습니까?" + +#: xs/src/slic3r/GUI/GUI.cpp:447 +msgid "Unsaved Presets" +msgstr "저장되지 않은 기존설정" + +#: xs/src/slic3r/GUI/GUI.cpp:655 +msgid "Notice" +msgstr "공지" + +#: xs/src/slic3r/GUI/GUI.cpp:660 +msgid "Attempt to free unreferenced scalar" +msgstr "참조 되지 않은 스칼라를 비우려고" + +#: xs/src/slic3r/GUI/GUI.cpp:662 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:321 +msgid "Warning" +msgstr "위험" + +#: xs/src/slic3r/GUI/GUI.cpp:859 +msgid "Support" +msgstr "서포트(지지대)" + +#: xs/src/slic3r/GUI/GUI.cpp:862 +msgid "Select what kind of support do you need" +msgstr "서포트의 종류를 선택하세요" + +#: xs/src/slic3r/GUI/GUI.cpp:863 xs/src/libslic3r/GCode/PreviewData.cpp:162 +msgid "None" +msgstr "없음" + +#: xs/src/slic3r/GUI/GUI.cpp:864 xs/src/libslic3r/PrintConfig.cpp:1656 +msgid "Support on build plate only" +msgstr "출력물만 서포트를 지지" + +#: xs/src/slic3r/GUI/GUI.cpp:865 +msgid "Everywhere" +msgstr "모든곳" + +#: xs/src/slic3r/GUI/GUI.cpp:877 xs/src/slic3r/GUI/Tab.cpp:844 +msgid "Brim" +msgstr "브림" + +#: xs/src/slic3r/GUI/GUI.cpp:879 +msgid "This flag enables the brim that will be printed around each object on the first layer." +msgstr "이 플래그는 첫 번째 레이어의 각 개체 주위에 인쇄 될 브림을 활성화합니다." + +#: xs/src/slic3r/GUI/GUI.cpp:888 +msgid "Purging volumes" +msgstr "볼륨 삭제" + +#: xs/src/slic3r/GUI/GUI.cpp:930 +msgid "Export print config" +msgstr "인쇄 설정 내보내기" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r error" +msgstr "Slic3r 오류" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r has encountered an error" +msgstr "Slic3r에 오류가 발생했습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:84 +msgid "Save current " +msgstr "지금 저장" + +#: xs/src/slic3r/GUI/Tab.cpp:85 +msgid "Delete this preset" +msgstr "이전 설정 삭제" + +#: xs/src/slic3r/GUI/Tab.cpp:97 +msgid "" +"Hover the cursor over buttons to find more information \n" +"or click this button." +msgstr "" +"버튼 위로 커서를 가져 가서 자세한 정보를 찾습니다.\n" +"또는이 버튼을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:716 +msgid "It's a default preset." +msgstr "기본 설정입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:717 +msgid "It's a system preset." +msgstr "시스템 설정입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:718 +msgid "Current preset is inherited from " +msgstr "전의 설정에서 가져 옵니다" + +#: xs/src/slic3r/GUI/Tab.cpp:723 +msgid "It can't be deleted or modified. " +msgstr "삭제하거나 수정할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:724 +msgid "Any modifications should be saved as a new preset inherited from this one. " +msgstr "모든 수정 사항은 이 항목에서 받은 기본 설정으로 저장해야합니다" + +#: xs/src/slic3r/GUI/Tab.cpp:725 +msgid "To do that please specify a new name for the preset." +msgstr "그렇게하려면 기본 설정의 새 이름을 지정하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:729 +msgid "Additional information:" +msgstr "추가 정보:" + +#: xs/src/slic3r/GUI/Tab.cpp:737 +msgid "printer model" +msgstr "프린터 모델" + +#: xs/src/slic3r/GUI/Tab.cpp:739 +msgid "default print profile" +msgstr "기본 인쇄 프로파일" + +#: xs/src/slic3r/GUI/Tab.cpp:742 +msgid "default filament profile" +msgstr "기본 필라멘트 프로파일" + +#: xs/src/slic3r/GUI/Tab.cpp:786 +msgid "Layers and perimeters" +msgstr "레이어 및 경계선" + +#: xs/src/slic3r/GUI/Tab.cpp:787 xs/src/libslic3r/PrintConfig.cpp:886 +msgid "Layer height" +msgstr "레이어 높이" + +#: xs/src/slic3r/GUI/Tab.cpp:791 +msgid "Vertical shells" +msgstr "쉘 높이" + +#: xs/src/slic3r/GUI/Tab.cpp:802 +msgid "Horizontal shells" +msgstr "쉘 너비" + +#: xs/src/slic3r/GUI/Tab.cpp:803 xs/src/libslic3r/PrintConfig.cpp:1562 +msgid "Solid layers" +msgstr "솔리드 레이어" + +#: xs/src/slic3r/GUI/Tab.cpp:808 +msgid "Quality (slower slicing)" +msgstr "품질(슬라이싱이 느려짐)" + +#: xs/src/slic3r/GUI/Tab.cpp:815 xs/src/slic3r/GUI/Tab.cpp:829 +#: xs/src/slic3r/GUI/Tab.cpp:923 xs/src/slic3r/GUI/Tab.cpp:926 +#: xs/src/slic3r/GUI/Tab.cpp:1276 xs/src/slic3r/GUI/Tab.cpp:1625 +#: xs/src/libslic3r/PrintConfig.cpp:110 xs/src/libslic3r/PrintConfig.cpp:245 +#: xs/src/libslic3r/PrintConfig.cpp:833 xs/src/libslic3r/PrintConfig.cpp:2021 +msgid "Advanced" +msgstr "고급" + +#: xs/src/slic3r/GUI/Tab.cpp:819 xs/src/slic3r/GUI/Tab.cpp:820 +#: xs/src/slic3r/GUI/Tab.cpp:1127 xs/src/libslic3r/PrintConfig.cpp:90 +#: xs/src/libslic3r/PrintConfig.cpp:284 xs/src/libslic3r/PrintConfig.cpp:585 +#: xs/src/libslic3r/PrintConfig.cpp:599 xs/src/libslic3r/PrintConfig.cpp:637 +#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:806 xs/src/libslic3r/PrintConfig.cpp:824 +#: xs/src/libslic3r/PrintConfig.cpp:843 xs/src/libslic3r/PrintConfig.cpp:1511 +#: xs/src/libslic3r/PrintConfig.cpp:1528 +msgid "Infill" +msgstr "인필(채움)" + +#: xs/src/slic3r/GUI/Tab.cpp:825 +msgid "Reducing printing time" +msgstr "출력 시간 단축" + +#: xs/src/slic3r/GUI/Tab.cpp:837 +msgid "Skirt and brim" +msgstr "스커트와 브림" + +#: xs/src/slic3r/GUI/Tab.cpp:838 xs/src/libslic3r/GCode/PreviewData.cpp:171 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:98 +msgid "Skirt" +msgstr "스커트" + +#: xs/src/slic3r/GUI/Tab.cpp:847 xs/src/slic3r/GUI/Tab.cpp:848 +#: xs/src/libslic3r/PrintConfig.cpp:228 xs/src/libslic3r/PrintConfig.cpp:1278 +#: xs/src/libslic3r/PrintConfig.cpp:1628 xs/src/libslic3r/PrintConfig.cpp:1635 +#: xs/src/libslic3r/PrintConfig.cpp:1647 xs/src/libslic3r/PrintConfig.cpp:1657 +#: xs/src/libslic3r/PrintConfig.cpp:1665 xs/src/libslic3r/PrintConfig.cpp:1680 +#: xs/src/libslic3r/PrintConfig.cpp:1701 xs/src/libslic3r/PrintConfig.cpp:1712 +#: xs/src/libslic3r/PrintConfig.cpp:1728 xs/src/libslic3r/PrintConfig.cpp:1737 +#: xs/src/libslic3r/PrintConfig.cpp:1746 xs/src/libslic3r/PrintConfig.cpp:1757 +#: xs/src/libslic3r/PrintConfig.cpp:1771 xs/src/libslic3r/PrintConfig.cpp:1779 +#: xs/src/libslic3r/PrintConfig.cpp:1780 xs/src/libslic3r/PrintConfig.cpp:1789 +#: xs/src/libslic3r/PrintConfig.cpp:1797 xs/src/libslic3r/PrintConfig.cpp:1811 +#: xs/src/libslic3r/GCode/PreviewData.cpp:172 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:99 +msgid "Support material" +msgstr "서포트 재료(Support material)" + +#: xs/src/slic3r/GUI/Tab.cpp:853 +msgid "Raft" +msgstr "라프트" + +#: xs/src/slic3r/GUI/Tab.cpp:857 +msgid "Options for support material and raft" +msgstr "서포트와 라프트 재료를 선택" + +#: xs/src/slic3r/GUI/Tab.cpp:871 xs/src/libslic3r/PrintConfig.cpp:122 +#: xs/src/libslic3r/PrintConfig.cpp:315 xs/src/libslic3r/PrintConfig.cpp:732 +#: xs/src/libslic3r/PrintConfig.cpp:844 xs/src/libslic3r/PrintConfig.cpp:1212 +#: xs/src/libslic3r/PrintConfig.cpp:1449 xs/src/libslic3r/PrintConfig.cpp:1499 +#: xs/src/libslic3r/PrintConfig.cpp:1550 xs/src/libslic3r/PrintConfig.cpp:1871 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:77 +msgid "Speed" +msgstr "속도" + +#: xs/src/slic3r/GUI/Tab.cpp:872 +msgid "Speed for print moves" +msgstr "출력중 이동 속도" + +#: xs/src/slic3r/GUI/Tab.cpp:884 +msgid "Speed for non-print moves" +msgstr "미출력시 이동속도" + +#: xs/src/slic3r/GUI/Tab.cpp:887 +msgid "Modifiers" +msgstr "수정" + +#: xs/src/slic3r/GUI/Tab.cpp:890 +msgid "Acceleration control (advanced)" +msgstr "가속 제어(고급)" + +#: xs/src/slic3r/GUI/Tab.cpp:897 +msgid "Autospeed (advanced)" +msgstr "오토스피트(고급)" + +#: xs/src/slic3r/GUI/Tab.cpp:903 +msgid "Multiple Extruders" +msgstr "다중 익스트루더" + +#: xs/src/slic3r/GUI/Tab.cpp:904 xs/src/slic3r/GUI/Tab.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:345 xs/src/libslic3r/PrintConfig.cpp:799 +#: xs/src/libslic3r/PrintConfig.cpp:1191 xs/src/libslic3r/PrintConfig.cpp:1520 +#: xs/src/libslic3r/PrintConfig.cpp:1693 xs/src/libslic3r/PrintConfig.cpp:1719 +#: xs/src/libslic3r/PrintConfig.cpp:1995 xs/src/libslic3r/PrintConfig.cpp:2004 +msgid "Extruders" +msgstr "익스트루더" + +#: xs/src/slic3r/GUI/Tab.cpp:911 +msgid "Ooze prevention" +msgstr "오즈 방지(Ooze prevention)" + +#: xs/src/slic3r/GUI/Tab.cpp:915 xs/src/libslic3r/GCode/PreviewData.cpp:174 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:101 +msgid "Wipe tower" +msgstr "와이프 타워(Wipe tower)" + +#: xs/src/slic3r/GUI/Tab.cpp:927 +msgid "Extrusion width" +msgstr "악출 폭(Extrusion width)" + +#: xs/src/slic3r/GUI/Tab.cpp:937 +msgid "Overlap" +msgstr "겹침(Overlap)" + +#: xs/src/slic3r/GUI/Tab.cpp:940 +msgid "Flow" +msgstr "유량(Flow)" + +#: xs/src/slic3r/GUI/Tab.cpp:943 +msgid "Other" +msgstr "그 외" + +#: xs/src/slic3r/GUI/Tab.cpp:950 +msgid "Output options" +msgstr "출력 옵션" + +#: xs/src/slic3r/GUI/Tab.cpp:951 +msgid "Sequential printing" +msgstr "연속 인쇄" + +#: xs/src/slic3r/GUI/Tab.cpp:953 +msgid "Extruder clearance (mm)" +msgstr "익스트루더 간격(mm)" + +#: xs/src/slic3r/GUI/Tab.cpp:962 +msgid "Output file" +msgstr "출력 파일" + +#: xs/src/slic3r/GUI/Tab.cpp:968 xs/src/libslic3r/PrintConfig.cpp:1234 +msgid "Post-processing scripts" +msgstr "포스트 프로세싱 스크립트" + +#: xs/src/slic3r/GUI/Tab.cpp:974 xs/src/slic3r/GUI/Tab.cpp:975 +#: xs/src/slic3r/GUI/Tab.cpp:1329 xs/src/slic3r/GUI/Tab.cpp:1330 +#: xs/src/slic3r/GUI/Tab.cpp:1668 xs/src/slic3r/GUI/Tab.cpp:1669 +msgid "Notes" +msgstr "메모" + +#: xs/src/slic3r/GUI/Tab.cpp:981 xs/src/slic3r/GUI/Tab.cpp:1337 +#: xs/src/slic3r/GUI/Tab.cpp:1675 +msgid "Dependencies" +msgstr "속한 그룹" + +#: xs/src/slic3r/GUI/Tab.cpp:982 xs/src/slic3r/GUI/Tab.cpp:1338 +#: xs/src/slic3r/GUI/Tab.cpp:1676 +msgid "Profile dependencies" +msgstr "프로파일 종석성" + +#: xs/src/slic3r/GUI/Tab.cpp:983 xs/src/slic3r/GUI/Tab.cpp:1339 +#: xs/src/slic3r/GUI/Tab.cpp:2364 xs/src/libslic3r/PrintConfig.cpp:147 +msgid "Compatible printers" +msgstr "호환 가능한 프린터들" + +#: xs/src/slic3r/GUI/Tab.cpp:1016 +#, no-c-format +msgid "" +"The Spiral Vase mode requires:\n" +"- one perimeter\n" +"- no top solid layers\n" +"- 0% fill density\n" +"- no support material\n" +"- no ensure_vertical_shell_thickness\n" +"\n" +"Shall I adjust those settings in order to enable Spiral Vase?" +msgstr "" +"스파이럴 바이스 모드에는 다음이 필요합니다.\n" +"- one 둘레\n" +"- 탑 솔리드 레이어 없음\n" +"- 채우기(fill) 밀도 0 %\n" +"- 서포트 재료 없음\n" +"- 수직 벽 두깨를 보장하지 않음\n" +"\n" +"스파이럴 바이스를 사용하려면 이러한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1023 +msgid "Spiral Vase" +msgstr "스파이럴 바이스" + +#: xs/src/slic3r/GUI/Tab.cpp:1044 +msgid "" +"The Wipe Tower currently supports the non-soluble supports only\n" +"if they are printed with the current extruder without triggering a tool change.\n" +"(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" +"\n" +"Shall I adjust those settings in order to enable the Wipe Tower?" +msgstr "" +"와이퍼 타워는 현재 비 가용성 서포트 만 지원합니다.\n" +"공구 교환을 트리거하지 않고 현재 압출기로 인쇄 한 경우.\n" +"(support_material_extruder 및 support_material_interface_extruder를 모두 0으로 설정해야 함).\n" +"\n" +"와이퍼 타워를 사용하려면 이러한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1048 xs/src/slic3r/GUI/Tab.cpp:1065 +msgid "Wipe Tower" +msgstr "와이프 타워(Wipe Tower)" + +#: xs/src/slic3r/GUI/Tab.cpp:1062 +msgid "" +"For the Wipe Tower to work with the soluble supports, the support layers\n" +"need to be synchronized with the object layers.\n" +"\n" +"Shall I synchronize support layers in order to enable the Wipe Tower?" +msgstr "" +"와이퍼 타워가 가용성 서포트와 함께 작용하기 위해, 서포트 레이어\n" +"객체 레이어와 동기화되어야합니다.\n" +"\n" +"와이퍼 타워를 사용하려면 서포트 레이어를 동기화해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1080 +msgid "" +"Supports work better, if the following feature is enabled:\n" +"- Detect bridging perimeters\n" +"\n" +"Shall I adjust those settings for supports?" +msgstr "" +"다음 기능을 사용하는 경우 더 나은 작업을 지원합니다.\n" +"- 브리지 경계 검출\n" +"\n" +"서포트에 대한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1083 +msgid "Support Generator" +msgstr "서포트 생성" + +#: xs/src/slic3r/GUI/Tab.cpp:1125 +msgid "The " +msgstr "The" + +#: xs/src/slic3r/GUI/Tab.cpp:1125 +#, no-c-format +msgid "" +" infill pattern is not supposed to work at 100% density.\n" +"\n" +"Shall I switch to rectilinear fill pattern?" +msgstr "" +"infill 패턴은 100 % 밀도에서 작동하지 않습니다.\n" +"\n" +"직선 채우기 패턴으로 전환해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1231 xs/src/slic3r/GUI/Tab.cpp:1232 +#: lib/Slic3r/GUI/Plater.pm:454 +msgid "Filament" +msgstr "필라멘트" + +#: xs/src/slic3r/GUI/Tab.cpp:1239 +msgid "Temperature " +msgstr "온도" + +#: xs/src/slic3r/GUI/Tab.cpp:1240 xs/src/libslic3r/PrintConfig.cpp:344 +msgid "Extruder" +msgstr "익스트루더(Extruder)" + +#: xs/src/slic3r/GUI/Tab.cpp:1245 +msgid "Bed" +msgstr "배드(Bed)" + +#: xs/src/slic3r/GUI/Tab.cpp:1250 +msgid "Cooling" +msgstr "냉각(Cooling)" + +#: xs/src/slic3r/GUI/Tab.cpp:1251 xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1941 +msgid "Enable" +msgstr "사용" + +#: xs/src/slic3r/GUI/Tab.cpp:1262 +msgid "Fan settings" +msgstr "팬 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1263 +msgid "Fan speed" +msgstr "팬 속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1271 +msgid "Cooling thresholds" +msgstr "냉각 임계 값" + +#: xs/src/slic3r/GUI/Tab.cpp:1277 +msgid "Filament properties" +msgstr "필라멘트 특성" + +#: xs/src/slic3r/GUI/Tab.cpp:1281 +msgid "Print speed override" +msgstr "인쇄 속도 중단" + +#: xs/src/slic3r/GUI/Tab.cpp:1291 +msgid "Toolchange parameters with single extruder MM printers" +msgstr "싱글 익스트루더 MM 프린터를 사용한 공구 교환 매개 변수" + +#: xs/src/slic3r/GUI/Tab.cpp:1299 +msgid "Ramming" +msgstr "래미" + +#: xs/src/slic3r/GUI/Tab.cpp:1301 +msgid "Ramming settings" +msgstr "래밍 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1316 xs/src/slic3r/GUI/Tab.cpp:1631 +msgid "Custom G-code" +msgstr "수동 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1317 xs/src/slic3r/GUI/Tab.cpp:1632 +#: xs/src/libslic3r/PrintConfig.cpp:1590 xs/src/libslic3r/PrintConfig.cpp:1605 +msgid "Start G-code" +msgstr "스타트 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1323 xs/src/slic3r/GUI/Tab.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:254 xs/src/libslic3r/PrintConfig.cpp:264 +msgid "End G-code" +msgstr "엔드 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1419 xs/src/slic3r/GUI/Preferences.cpp:17 +msgid "General" +msgstr "일반" + +#: xs/src/slic3r/GUI/Tab.cpp:1420 +msgid "Size and coordinates" +msgstr "크기와 좌표" + +#: xs/src/slic3r/GUI/Tab.cpp:1422 xs/src/libslic3r/PrintConfig.cpp:37 +msgid "Bed shape" +msgstr "배드 모양" + +#: xs/src/slic3r/GUI/Tab.cpp:1424 xs/src/slic3r/GUI/Tab.cpp:2332 +msgid " Set " +msgstr " 세트 " + +#: xs/src/slic3r/GUI/Tab.cpp:1447 +msgid "Capabilities" +msgstr "기능" + +#: xs/src/slic3r/GUI/Tab.cpp:1452 +msgid "Number of extruders of the printer." +msgstr "프린터 익스트루더 숫자" + +#: xs/src/slic3r/GUI/Tab.cpp:1477 +msgid "USB/Serial connection" +msgstr "USB/시리얼 연결" + +#: xs/src/slic3r/GUI/Tab.cpp:1478 xs/src/libslic3r/PrintConfig.cpp:1441 +msgid "Serial port" +msgstr "시리얼 포트" + +#: xs/src/slic3r/GUI/Tab.cpp:1483 +msgid "Rescan serial ports" +msgstr "시리얼포트 재검색" + +#: xs/src/slic3r/GUI/Tab.cpp:1492 xs/src/slic3r/GUI/Tab.cpp:1539 +msgid "Test" +msgstr "시험(test)" + +#: xs/src/slic3r/GUI/Tab.cpp:1505 +msgid "Connection to printer works correctly." +msgstr "프린터 연결이 올바르게 작동합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1505 xs/src/slic3r/GUI/Tab.cpp:1549 +msgid "Success!" +msgstr "성공!" + +#: xs/src/slic3r/GUI/Tab.cpp:1508 +msgid "Connection failed." +msgstr "연결 실패" + +#: xs/src/slic3r/GUI/Tab.cpp:1520 xs/src/slic3r/Utils/OctoPrint.cpp:110 +msgid "OctoPrint upload" +msgstr "옥토프린트 업로드" + +#: xs/src/slic3r/GUI/Tab.cpp:1523 xs/src/slic3r/GUI/Tab.cpp:1572 +msgid " Browse " +msgstr " 검색 " + +#: xs/src/slic3r/GUI/Tab.cpp:1549 +msgid "Connection to OctoPrint works correctly." +msgstr "OctoPrint에 연결하면 올바르게 작동합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Could not connect to OctoPrint" +msgstr "OctoPrint에 연결할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Note: OctoPrint version at least 1.1.0 is required." +msgstr "참고 : OctoPrint 버전 1.1.0 이상이 필요합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1578 +msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" +msgstr "인증서 파일 (* .crt, * .pem) | * .crt; * .pem | 모든 파일 | *. *" + +#: xs/src/slic3r/GUI/Tab.cpp:1579 +msgid "Open CA certificate file" +msgstr "Open CA certificate file" + +#: xs/src/slic3r/GUI/Tab.cpp:1593 +msgid "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate." +msgstr "HTTPS CA 파일은 선택 사항입니다. 자체 서명 된 인증서로 HTTPS를 사용하는 경우에만 필요합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1644 xs/src/libslic3r/PrintConfig.cpp:51 +msgid "Before layer change G-code" +msgstr "레이어 변경 전 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1650 xs/src/libslic3r/PrintConfig.cpp:875 +msgid "After layer change G-code" +msgstr "레이어 변경 후 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:1848 +msgid "Tool change G-code" +msgstr "툴 채인지 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1662 +msgid "Between objects G-code (for sequential printing)" +msgstr "객체 간 G 코드 (순차 인쇄용)" + +#: xs/src/slic3r/GUI/Tab.cpp:1717 xs/src/slic3r/GUI/Tab.cpp:1778 +#: xs/src/slic3r/GUI/Tab.cpp:2037 xs/src/libslic3r/PrintConfig.cpp:920 +#: xs/src/libslic3r/PrintConfig.cpp:929 xs/src/libslic3r/PrintConfig.cpp:938 +#: xs/src/libslic3r/PrintConfig.cpp:950 xs/src/libslic3r/PrintConfig.cpp:960 +#: xs/src/libslic3r/PrintConfig.cpp:970 xs/src/libslic3r/PrintConfig.cpp:980 +msgid "Machine limits" +msgstr "머신 한계설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1730 +msgid "Values in this column are for Full Power mode" +msgstr "이 열의 값은 최대 전력 모드입니다" + +#: xs/src/slic3r/GUI/Tab.cpp:1731 +msgid "Full Power" +msgstr "최대 파워" + +#: xs/src/slic3r/GUI/Tab.cpp:1736 +msgid "Values in this column are for Silent mode" +msgstr "이 열의 값은 무음 모드 용입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1737 +msgid "Silent" +msgstr "무음" + +#: xs/src/slic3r/GUI/Tab.cpp:1745 +msgid "Maximum feedrates" +msgstr "최대 이송속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1750 +msgid "Maximum accelerations" +msgstr "최고 가속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1757 +msgid "Jerk limits" +msgstr "저크(Jerk)값 한계" + +#: xs/src/slic3r/GUI/Tab.cpp:1762 +msgid "Minimum feedrates" +msgstr "최대 이송속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1800 xs/src/slic3r/GUI/Tab.cpp:1808 +#: xs/src/slic3r/GUI/Tab.cpp:2037 +msgid "Single extruder MM setup" +msgstr "싱글 익스트루더 MM 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1809 +msgid "Single extruder multimaterial parameters" +msgstr "싱글 익스트루더 멀티메터리알 파라미터" + +#: xs/src/slic3r/GUI/Tab.cpp:1822 xs/src/libslic3r/GCode/PreviewData.cpp:446 +#, c-format +msgid "Extruder %d" +msgstr "익스트루더 %d" + +#: xs/src/slic3r/GUI/Tab.cpp:1829 +msgid "Layer height limits" +msgstr "레이어 높이 한계치" + +#: xs/src/slic3r/GUI/Tab.cpp:1834 +msgid "Position (for multi-extruder printers)" +msgstr "위치 (멀티 익스트루더 프린터 포함)" + +#: xs/src/slic3r/GUI/Tab.cpp:1837 +msgid "Retraction" +msgstr "리트렉션" + +#: xs/src/slic3r/GUI/Tab.cpp:1840 +msgid "Only lift Z" +msgstr "Z축만 올림" + +#: xs/src/slic3r/GUI/Tab.cpp:1853 +msgid "Retraction when tool is disabled (advanced settings for multi-extruder setups)" +msgstr "도구 비활성화시 리트렉션 (멀티 익스트루더 고급 설정)" + +#: xs/src/slic3r/GUI/Tab.cpp:1857 lib/Slic3r/GUI/Plater.pm:217 +#: lib/Slic3r/GUI/Plater.pm:2324 +msgid "Preview" +msgstr "프리뷰" + +#: xs/src/slic3r/GUI/Tab.cpp:1953 +msgid "" +"The Wipe option is not available when using the Firmware Retraction mode.\n" +"\n" +"Shall I disable it in order to enable Firmware Retraction?" +msgstr "" +"펌웨어 리트렉션 모드를 사용할 때는 Wipe 옵션을 사용할 수 없습니다.\n" +"\n" +"펌웨어 리트렉션 하려면 비활성화해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1955 +msgid "Firmware Retraction" +msgstr "펌웨어 레트렉션" + +#: xs/src/slic3r/GUI/Tab.cpp:2130 +msgid "Default " +msgstr "기본값 " + +#: xs/src/slic3r/GUI/Tab.cpp:2130 +msgid " preset" +msgstr " 기본 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2131 +msgid " preset\n" +msgstr " 기본설정\n" + +#: xs/src/slic3r/GUI/Tab.cpp:2149 +msgid "" +"\n" +"\n" +"is not compatible with printer\n" +msgstr "" +"\n" +"\n" +"프린터와 호완 되지 않습니다.\n" + +#: xs/src/slic3r/GUI/Tab.cpp:2149 +msgid "" +"\n" +"\n" +"and it has the following unsaved changes:" +msgstr "" +"\n" +"\n" +"저장되지 않은 변경점은 다음과 같습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2150 +msgid "" +"\n" +"\n" +"has the following unsaved changes:" +msgstr "" +"\n" +"\n" +"저장되지 않은 수정사항:" + +#: xs/src/slic3r/GUI/Tab.cpp:2152 +msgid "" +"\n" +"\n" +"Discard changes and continue anyway?" +msgstr "" +"\n" +"\n" +"수정된 사항을 취소하고 계속하겠습니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:2153 +msgid "Unsaved Changes" +msgstr "미 저장된 변경점" + +#: xs/src/slic3r/GUI/Tab.cpp:2240 +msgid "The supplied name is empty. It can't be saved." +msgstr "파일 이름이 비어 있습니다. 저장할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2245 +msgid "Cannot overwrite a system profile." +msgstr "시스템 프로파일을 겹쳐 쓸 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2249 +msgid "Cannot overwrite an external profile." +msgstr "외부 프로필을 덮어 쓸 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2275 +msgid "remove" +msgstr "제거(remove)" + +#: xs/src/slic3r/GUI/Tab.cpp:2275 +msgid "delete" +msgstr "지우기(delete)" + +#: xs/src/slic3r/GUI/Tab.cpp:2276 +msgid "Are you sure you want to " +msgstr "정말로 다음과 같이 하겠습니까? " + +#: xs/src/slic3r/GUI/Tab.cpp:2276 +msgid " the selected preset?" +msgstr " 를(가) 선택된 설정을 실행 할까요?" + +#: xs/src/slic3r/GUI/Tab.cpp:2277 +msgid "Remove" +msgstr "제거(remove)" + +#: xs/src/slic3r/GUI/Tab.cpp:2277 lib/Slic3r/GUI/Plater.pm:251 +#: lib/Slic3r/GUI/Plater.pm:269 lib/Slic3r/GUI/Plater.pm:2215 +msgid "Delete" +msgstr "지우기(delete)" + +#: xs/src/slic3r/GUI/Tab.cpp:2278 +msgid " Preset" +msgstr " 기본 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2331 +msgid "All" +msgstr "모두 선택" + +#: xs/src/slic3r/GUI/Tab.cpp:2363 +msgid "Select the printers this profile is compatible with." +msgstr "이 프로파일과 호환 가능한 프린터를 선택하세요." + +#: xs/src/slic3r/GUI/Tab.cpp:2409 xs/src/slic3r/GUI/Tab.cpp:2495 +#: xs/src/slic3r/GUI/Preset.cpp:702 xs/src/slic3r/GUI/Preset.cpp:742 +#: xs/src/slic3r/GUI/Preset.cpp:770 xs/src/slic3r/GUI/Preset.cpp:802 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1193 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1246 lib/Slic3r/GUI/Plater.pm:603 +msgid "System presets" +msgstr "시스템 기본설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2410 xs/src/slic3r/GUI/Tab.cpp:2496 +msgid "Default presets" +msgstr "시스템 기본값" + +#: xs/src/slic3r/GUI/Tab.cpp:2565 +msgid "LOCKED LOCK;indicates that the settings are the same as the system values for the current option group" +msgstr "자물쇠 잠금 : 설정이 현재 옵션 그룹의 시스템 값과 동일 함을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2568 +msgid "" +"UNLOCKED LOCK;indicates that some settings were changed and are not equal to the system values for the current option group.\n" +"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system values." +msgstr "" +"잠금 풀림 : 일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2574 +msgid "" +"WHITE BULLET;for the left button: \tindicates a non-system preset,\n" +"for the right button: \tindicates that the settings hasn't been modified." +msgstr "" +"흰색 총알; 왼쪽 버튼 : 시스템이 아닌 사전 설정을 나타내며,\n" +"오른쪽 버튼의 경우 : 설정이 수정되지 않았 음을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2578 +msgid "" +"BACK ARROW;indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" +"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "" +"잠금 풀림;일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2604 +msgid "LOCKED LOCK icon indicates that the settings are the same as the system values for the current option group" +msgstr "자물쇠 잠금 아이코 설정이 현재 옵션 그룹의 시스템 값과 동일 함을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2606 +msgid "" +"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system values for the current option group.\n" +"Click to reset all settings for current option group to the system values." +msgstr "" +"잠금 풀림 아이코 일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2609 +msgid "WHITE BULLET icon indicates a non system preset." +msgstr "흰색 글머리 아이콘은 시스템 사전 설정이 아닌 것을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2612 +msgid "WHITE BULLET icon indicates that the settings are the same as in the last saved preset for the current option group." +msgstr "흰색 글머리 기호 아이콘은 설정이 현재 옵션 그룹에 대해 마지막으로 저장 된 사전 설정과 동일 하다는 것을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2614 +msgid "" +"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" +"Click to reset all settings for the current option group to the last saved preset." +msgstr "" +"백화살표 아이콘 설정을 변경 하 고 현재 옵션 그룹에 대 한 마지막 저장 된 프리셋을 동일 하지 않습니다 나타냅니다.\n" +"마지막 현재 옵션 그룹에 대 한 모든 설정 다시 설정을 클릭 하 여 사전 설정을 저장." + +#: xs/src/slic3r/GUI/Tab.cpp:2620 +msgid "LOCKED LOCK icon indicates that the value is the same as the system value." +msgstr "잠긴 자물쇠 아이콘 값 같은 시스템 값 임을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2621 +msgid "" +"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system value.\n" +"Click to reset current value to the system value." +msgstr "" +"잠금 해제 자물쇠 아이콘 값 변경 된 시스템 값은 나타냅니다.\n" +"시스템 값을 현재 값으로 설정 하려면 클릭 합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2627 +msgid "WHITE BULLET icon indicates that the value is the same as in the last saved preset." +msgstr "흰색 글머리 기호 아이콘은 마지막으로 저장 한 사전 설정과 동일한 값을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2628 +msgid "" +"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" +"Click to reset current value to the last saved preset." +msgstr "" +"잠금 해제 자물쇠 아이콘 값 변경 된 시스템 값은 나타냅니다.\n" +"시스템 값을 현재 값으로 설정 하려면 클릭 합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2703 lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/Plater.pm:1795 +msgid "Save " +msgstr "저장 " + +#: xs/src/slic3r/GUI/Tab.cpp:2703 +msgid " as:" +msgstr " as:" + +#: xs/src/slic3r/GUI/Tab.cpp:2742 xs/src/slic3r/GUI/Tab.cpp:2746 +msgid "The supplied name is not valid;" +msgstr "제공된 이름이 유효하지 않습니다;" + +#: xs/src/slic3r/GUI/Tab.cpp:2743 +msgid "the following characters are not allowed:" +msgstr "다음 문자는 허용되지 않습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2747 +msgid "the following postfix are not allowed:" +msgstr "다음 접미사는 허용되지 않습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2750 +msgid "The supplied name is not available." +msgstr "The supplied name is not available." + +#: xs/src/slic3r/GUI/Tab.hpp:286 +msgid "Print Settings" +msgstr "출력 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:306 +msgid "Filament Settings" +msgstr "필라멘트 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:332 +msgid "Printer Settings" +msgstr "프린터 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:348 +msgid "Save preset" +msgstr "사전 설정 저장" + +#: xs/src/slic3r/GUI/Field.cpp:98 +msgid "default" +msgstr "기본값 " + +#: xs/src/slic3r/GUI/Field.cpp:128 +#, c-format +msgid "%s doesn't support percentage" +msgstr "%s 이(가) 백분율을 지원하지 않음" + +#: xs/src/slic3r/GUI/Field.cpp:137 +msgid "Input value is out of range" +msgstr "Input value is out of range" + +#: xs/src/slic3r/GUI/Preset.cpp:144 +msgid "modified" +msgstr "수정된곳" + +#: xs/src/slic3r/GUI/Preset.cpp:746 xs/src/slic3r/GUI/Preset.cpp:806 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1251 lib/Slic3r/GUI/Plater.pm:604 +msgid "User presets" +msgstr "사용자 사전설정" + +#: xs/src/slic3r/GUI/PresetHints.cpp:27 +#, c-format +msgid "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s)." +msgstr "예상 레이어 시간이 ~ % d 초 미만이면 팬이 % d %%에서 실행되고 인쇄 속도가 감소되어 해당 레이어에 % ds 이상 소비됩니다 (단, 속도는 % dmm / s 이하로 감소하지 않습니다) ." + +#: xs/src/slic3r/GUI/PresetHints.cpp:31 +#, c-format +msgid "" +"\n" +"If estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%." +msgstr "" +"\n" +"예상 레이어 시간이 더 길지만 ~ % ds 미만인 경우 팬은 % d %%와 % d %% 사이에 비례하여 감소하는 속도로 실행됩니다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:35 +msgid "" +"\n" +"During the other layers, fan " +msgstr "" +"\n" +"다른 레이어 중 팬 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:37 +msgid "Fan " +msgstr "팬(Fan) " + +#: xs/src/slic3r/GUI/PresetHints.cpp:42 +#, c-format +msgid "will always run at %d%% " +msgstr "항상 다음처럼 실행 %d%% " + +#: xs/src/slic3r/GUI/PresetHints.cpp:45 +#, c-format +msgid "except for the first %d layers" +msgstr "첫 번째 %d 레이어를 제외하고" + +#: xs/src/slic3r/GUI/PresetHints.cpp:49 +msgid "except for the first layer" +msgstr "첫 번째 레이어를 제외하고" + +#: xs/src/slic3r/GUI/PresetHints.cpp:51 +msgid "will be turned off." +msgstr "off 됩니다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:152 +msgid "external perimeters" +msgstr "외부 둘레" + +#: xs/src/slic3r/GUI/PresetHints.cpp:161 +msgid "perimeters" +msgstr "둘레" + +#: xs/src/slic3r/GUI/PresetHints.cpp:170 +msgid "infill" +msgstr "채움(infill)" + +#: xs/src/slic3r/GUI/PresetHints.cpp:180 +msgid "solid infill" +msgstr "고체(solid)부분 채움" + +#: xs/src/slic3r/GUI/PresetHints.cpp:188 +msgid "top solid infill" +msgstr "가장 윗부분 채움" + +#: xs/src/slic3r/GUI/PresetHints.cpp:199 +msgid "support" +msgstr "서포트" + +#: xs/src/slic3r/GUI/PresetHints.cpp:209 +msgid "support interface" +msgstr "서포트 인터페이스" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "First layer volumetric" +msgstr "첫번째 레이어 용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "Bridging volumetric" +msgstr "브리징(Bridging) 용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "Volumetric" +msgstr "용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:216 +msgid " flow rate is maximized " +msgstr "유속(flow)이 최대화된다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:219 +msgid "by the print profile maximum" +msgstr "인쇄 프로파일 최대 값" + +#: xs/src/slic3r/GUI/PresetHints.cpp:220 +msgid "when printing " +msgstr "인쇄 할때 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:221 +msgid " with a volumetric rate " +msgstr " 용적 비율로 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:225 +#, c-format +msgid "%3.2f mm³/s" +msgstr "%3.2f mm³/s" + +#: xs/src/slic3r/GUI/PresetHints.cpp:227 +#, c-format +msgid " at filament speed %3.2f mm/s." +msgstr " 필라멘트 속도는 %3.2f mm/s." + +#: xs/src/slic3r/GUI/PresetHints.cpp:246 +msgid "Recommended object thin wall thickness: Not available due to invalid layer height." +msgstr "권장 객체(object) 벽(wall) 두께: 잘못된 레이어 높이 때문에 사용할 수 없음" + +#: xs/src/slic3r/GUI/PresetHints.cpp:263 +#, c-format +msgid "Recommended object thin wall thickness for layer height %.2f and " +msgstr "개체 레이어 높이 %.2f 에 대 한 얇은 벽 두께 권장 하 고 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:270 +#, c-format +msgid "%d lines: %.2lf mm" +msgstr "%d 라인(lines): %.2lf mm" + +#: xs/src/slic3r/GUI/Preferences.cpp:34 +msgid "Remember output directory" +msgstr "출력 디렉토리 기억하기" + +#: xs/src/slic3r/GUI/Preferences.cpp:36 +msgid "If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files." +msgstr "이 옵션을 사용하면 Slic3r은 입력 파일이 들어있는 디렉터리 대신 마지막 출력 디렉터리를 묻습니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:42 +msgid "Auto-center parts" +msgstr "부품을 자동으로 중심에" + +#: xs/src/slic3r/GUI/Preferences.cpp:44 +msgid "If this is enabled, Slic3r will auto-center objects around the print bed center." +msgstr "이 옵션을 사용하면 Slic3r이 개체를 인쇄판 중앙에 자동으로 배치합니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:50 +msgid "Background processing" +msgstr "백그라운드 프로세싱" + +#: xs/src/slic3r/GUI/Preferences.cpp:52 +msgid "If this is enabled, Slic3r will pre-process objects as soon as they're loaded in order to save time when exporting G-code." +msgstr "이 사용 하는 경우 Slic3r는 전처리 개체 최대한 빨리 그들이 시간을 절약 하기 위해 로드 G-코드를 내보낼 때." + +#: xs/src/slic3r/GUI/Preferences.cpp:74 +msgid "Disable USB/serial connection" +msgstr "USB/시리얼 연결 비활성화" + +#: xs/src/slic3r/GUI/Preferences.cpp:76 +msgid "Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer." +msgstr "시리얼을 통해 프린터와의 통신을 사용 하지 않도록 설정 / USB 케이블. 이 프린터는 결코 컴퓨터에 연결 하는 경우에 사용자 인터페이스를 간소화 합니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:82 +msgid "Suppress \" - default - \" presets" +msgstr "\"- 기본 -\"사전 설정 숨기기" + +#: xs/src/slic3r/GUI/Preferences.cpp:84 +msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." +msgstr "사용 가능한 다른 유효한 사전 설정이 있으면 인쇄 / 필라멘트 / 프린터 선택에서 \"- 기본 -\"사전 설정을 억제하십시오." + +#: xs/src/slic3r/GUI/Preferences.cpp:90 +msgid "Show incompatible print and filament presets" +msgstr "호환 되지 않는 인쇄 및 필라멘트 설정" + +#: xs/src/slic3r/GUI/Preferences.cpp:92 +msgid "When checked, the print and filament presets are shown in the preset editor even if they are marked as incompatible with the active printer" +msgstr "이 옵션을 선택하면 활성 프린터와 호환되지 않는 것으로 표시된 경우에도 인쇄 및 필라멘트 사전 설정이 사전 설정 편집기에 표시됩니다" + +#: xs/src/slic3r/GUI/Preferences.cpp:98 +msgid "Use legacy OpenGL 1.1 rendering" +msgstr "레거시 OpenGL 1.1 렌더링 사용" + +#: xs/src/slic3r/GUI/Preferences.cpp:100 +msgid "If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may try to check this checkbox. This will disable the layer height editing and anti aliasing, so it is likely better to upgrade your graphics driver." +msgstr "버그가있는 OpenGL 2.0 드라이버로 인한 렌더링 문제가있는 경우이 확인란을 선택해보십시오. 이렇게하면 레이어 높이 편집 및 앤티 앨리어싱이 비활성화되므로 그래픽 드라이버를 업그레이드하는 것이 좋습니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:124 +msgid "You need to restart Slic3r to make the changes effective." +msgstr "변경 사항을 적용하려면 Slic3r을 다시 시작해야합니다." + +#: xs/src/slic3r/GUI/RammingChart.cpp:23 +msgid "NO RAMMING AT ALL" +msgstr "전혀 충돌 없음" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 +msgid "Time" +msgstr "시간" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +#: xs/src/libslic3r/PrintConfig.cpp:490 +msgid "s" +msgstr "s" + +#: xs/src/slic3r/GUI/RammingChart.cpp:81 +msgid "Volumetric speed" +msgstr "용적(Volumetric) 스피트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "Update available" +msgstr "사용가능한 업데이트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "New version of Slic3r PE is available" +msgstr "새로운 버전의 Slic3r PE 사용 가능" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:34 +msgid "To download, follow the link below." +msgstr "다운로드하려면 아래 링크를 클릭하십시오." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:41 +msgid "Current version:" +msgstr "현재 버전:" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:43 +msgid "New version:" +msgstr "새로운 버전:" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:51 +msgid "Don't notify about new releases any more" +msgstr "새로운 수정사항에 대해 더 이상 알림 안 함" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:161 +msgid "Configuration update" +msgstr "구성 업데이트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +msgid "Configuration update is available" +msgstr "구성 업데이트를 사용할 수 있음" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:72 +msgid "" +"Would you like to install it?\n" +"\n" +"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" +"\n" +"Updated configuration bundles:" +msgstr "" +"그것을 설치 하시겠습니까?\n" +"\n" +"전체 구성 스냅 샷이 먼저 만들어집니다. 그런 다음 새 버전에 문제가있을 경우 언제든지 복원 할 수 있습니다.\n" +"\n" +"업데이트 된 구성 번들 :" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r incompatibility" +msgstr "Slic3r와 호환 되지 않음" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r configuration is incompatible" +msgstr "Slic3r 구성이 호환되지 않습니다." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:111 +msgid "" +"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" +"This probably happened as a result of running an older Slic3r PE after using a newer one.\n" +"\n" +"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n" +msgstr "" +"이 버전의 Slic3r PE는 현재 설치된 구성 번들과 호환되지 않습니다.\n" +"이것은 아마도 새로운 Slic3r PE를 사용한 후에 실행 된 결과 일 것입니다.\n" +"\n" +"Slic3r을 종료하고 새 버전으로 다시 시도하거나 초기 구성을 다시 실행할 수 있습니다. 이렇게하면이 Slic3r과 호환되는 파일을 설치하기 전에 기존 구성의 백업 스냅 샷을 생성 할 수 있습니다.\n" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:120 +#, c-format +msgid "This Slic3r PE version: %s" +msgstr "이 Slic3r PE 버전 : % s" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:125 +msgid "Incompatible bundles:" +msgstr "호환되지 않는 번들 :" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:141 +msgid "Exit Slic3r" +msgstr "Exit Slic3r" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:144 +msgid "Re-configure" +msgstr "재구성" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:165 +#, c-format +msgid "" +"Slic3r PE now uses an updated configuration structure.\n" +"\n" +"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" +"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" +"\n" +"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "" +"Slic3r PE는 이제 업데이트 된 구성 구조를 사용합니다.\n" +"\n" +"'시스템 사전 설정'이 도입되어 다양한 프린터에 기본 제공되는 기본 설정이 유지됩니다. 이러한 시스템 사전 설정은 수정할 수 없으며 대신 사용자는 시스템 사전 설정 중 하나에서 설정을 상속하는 자체 사전 설정을 만들 수 있습니다.\n" +"상속 된 사전 설정은 부모로부터 특정 값을 상속 받거나 사용자 정의 값으로 대체 할 수 있습니다.\n" +"\n" +"새 사전 설정을 설정하고 자동 사전 설정 업데이트를 사용할지 여부를 선택하려면 다음의 % s을 계속 진행하십시오." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:181 +msgid "For more information please visit our wiki page:" +msgstr "자세한 정보는 위키 페이지를 참조하십시오 :" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:9 +msgid "Ramming customization" +msgstr "사용자 정의 다지기(Ramming)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:35 +msgid "" +"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" +"\n" +"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "" +"래밍은 단일 압출기 MM 프린터에서 공구 교환 직전의 신속한 압출을 나타냅니다. 그 목적은 언로드 된 필라멘트의 끝 부분을 적절히 형성하여 새로운 필라멘트의 삽입을 방지하고 나중에 다시 삽입 할 수 있도록하기위한 것입니다. 이 단계는 중요하며 다른 재료는 좋은 모양을 얻기 위해 다른 압출 속도를 요구할 수 있습니다. 이러한 이유로, 래밍 중 압출 속도는 조정 가능합니다.\n" +"\n" +"전문가 수준의 설정이므로 잘못된 조정으로 인해 용지 걸림, 압출기 휠이 필라멘트 등에 연삭 될 수 있습니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +msgid "Total ramming time" +msgstr "총 래밍 시간" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +msgid "Total rammed volume" +msgstr "총 레미드 양" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:83 +msgid "Ramming line width" +msgstr "래밍 선 너비" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:85 +msgid "Ramming line spacing" +msgstr "래밍 선 간격" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:137 +msgid "Wipe tower - Purging volume adjustment" +msgstr "와이프 타워 - 버려진 필라멘트 조절" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:218 +msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." +msgstr "여기서 주어진 도구 쌍에 필요한 정화 용량 (mm³)을 조정할 수 있습니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:219 +msgid "Extruder changed to" +msgstr "익스트루더 번경" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:227 +msgid "unloaded" +msgstr "언로드(unloaded)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:228 +msgid "loaded" +msgstr "로드(loaded)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:233 +msgid "Tool #" +msgstr "툴(Tool) #" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:240 +msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." +msgstr "총 정화 량은 어느 공구가로드 / 언로드되는지에 따라 아래의 두 값을 합산하여 계산됩니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:241 +msgid "Volume to purge (mm³) when the filament is being" +msgstr "제거할 필라멘트 양 (mm³)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:255 +msgid "From" +msgstr "From" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:320 +msgid "" +"Switching to simple settings will discard changes done in the advanced mode!\n" +"\n" +"Do you want to proceed?" +msgstr "" +"단순 설정으로 전환하면 고급 모드에서 수행된 변경 내용이 삭제됨!\n" +"\n" +"계속하시겠습니까?" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show simplified settings" +msgstr "간단한 설정보기" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show advanced settings" +msgstr "고급 설정보기" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Send G-Code to printer" +msgstr "프린터에 G 코드 전송" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Upload to OctoPrint with the following filename:" +msgstr "OctoPrint에 다음 파일명으로 업로드하십시오 :" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:35 +msgid "Start printing after upload" +msgstr "업로드 후 인쇄 시작" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:37 +msgid "Use forward slashes ( / ) as a directory separator if needed." +msgstr "필요한 경우 디렉토리 분리 기호로 슬래시 (/)를 사용하십시오." + +#: xs/src/slic3r/Utils/OctoPrint.cpp:98 +msgid "Error while uploading to the OctoPrint server" +msgstr "OctoPrint 서버에 업로드하는 동안 오류가 발생했습니다." + +#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1558 +msgid "Sending G-code file to the OctoPrint server..." +msgstr "OctoPrint 서버에 G 코드 파일 보내기 ..." + +#: xs/src/slic3r/Utils/PresetUpdater.cpp:544 +#, c-format +msgid "requires min. %s and max. %s" +msgstr "최소. %s 와 최대. %s" + +#: xs/src/libslic3r/Print.cpp:553 +msgid "All objects are outside of the print volume." +msgstr "모든 개체가 인쇄 볼륨 외부에 있습니다." + +#: xs/src/libslic3r/Print.cpp:579 +msgid "Some objects are too close; your extruder will collide with them." +msgstr "일부 개체가 너무 가깝습니다. 귀하의 압출기가 그들과 충돌합니다." + +#: xs/src/libslic3r/Print.cpp:594 +msgid "Some objects are too tall and cannot be printed without extruder collisions." +msgstr "일부 개체는 너무 크고 익스트루더 충돌없이 인쇄 할 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:604 +msgid "The Spiral Vase option can only be used when printing a single object." +msgstr "나선형 꽃병(Spiral Vase) 옵션은 단일 개체를 인쇄 할 때만 사용할 수 있습니다." + +#: xs/src/libslic3r/Print.cpp:606 +msgid "The Spiral Vase option can only be used when printing single material objects." +msgstr "나선형 꽃병 옵션(Spiral Vase)은 단일 재료 객체를 인쇄 할 때만 사용할 수 있습니다." + +#: xs/src/libslic3r/Print.cpp:612 +msgid "All extruders must have the same diameter for single extruder multimaterial printer." +msgstr "모든 익스트루더는 멀티메터리얼 프린터의 싱글 익스트루더에 대해 동일한 직경을 가져야합니다." + +#: xs/src/libslic3r/Print.cpp:617 +msgid "The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors." +msgstr "현재 Wipe Tower는 Marlin 및 RepRap / Sprinter G 코드의 경우에만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:619 +msgid "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)." +msgstr "와이프 타워는 현재 상대적 압출기 어드레싱 (use_relative_e_distances = 1)에서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:631 +msgid "The Wipe Tower is only supported for multiple objects if they have equal layer heigths" +msgstr "와이프 타워 (Wipe Tower)는 같은 레이어 높이에 경우 여러 객체에 대해서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:633 +msgid "The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers" +msgstr "와이프 타워는 같은 수의 라프트 레이어 위에 인쇄 된 경우 여러 객체에 대해서만 지원됩니다" + +#: xs/src/libslic3r/Print.cpp:635 +msgid "The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance" +msgstr "와이프 타워는 동일한 support_material_contact_distance로 인쇄 된 경우 여러 객체에 대해서만 지원됩니다" + +#: xs/src/libslic3r/Print.cpp:637 +msgid "The Wipe Tower is only supported for multiple objects if they are sliced equally." +msgstr "와이프 타워는 똑같이 슬라이스 된 경우 여러 오브젝트에 대해서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:661 +msgid "The Wipe tower is only supported if all objects have the same layer height profile" +msgstr "모든 오브젝트의 레이어 높이 프로필이 동일한 경우에만 와이프 타워가 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:670 +msgid "The supplied settings will cause an empty print." +msgstr "제공된 설정으로 인해 빈 인쇄가 발생합니다." + +#: xs/src/libslic3r/Print.cpp:680 +msgid "One or more object were assigned an extruder that the printer does not have." +msgstr "하나 이상의 개체에 프린터에없는 압출기가 지정되었습니다." + +#: xs/src/libslic3r/Print.cpp:689 +msgid "Printing with multiple extruders of differing nozzle diameters. If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), all nozzles have to be of the same diameter." +msgstr "노즐 지름이 다른 여러 압출기로 인쇄. 지원이 현재 압출기 (support_material_extruder == 0 또는 support_material_interface_extruder == 0)로 인쇄되는 경우 모든 노즐은 동일한 지름이어야합니다." + +#: xs/src/libslic3r/Print.cpp:695 +msgid "first_layer_height" +msgstr "first_layer_height" + +#: xs/src/libslic3r/Print.cpp:710 +msgid "First layer height can't be greater than nozzle diameter" +msgstr "첫번째 레이어 높이는 노즐 직경보다 클 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:714 +msgid "Layer height can't be greater than nozzle diameter" +msgstr "레이어 높이는 노즐 직경보다 클 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:1196 +msgid "Failed processing of the output_filename_format template." +msgstr "Failed processing of the output_filename_format template." + +#: xs/src/libslic3r/PrintConfig.cpp:29 +msgid "Avoid crossing perimeters" +msgstr "출력된 외측을 피하세요" + +#: xs/src/libslic3r/PrintConfig.cpp:30 +msgid "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation." +msgstr "둘레의 교차를 최소화하기 위해 여행 이동을 최적화하십시오. 이것은 보 잉 (Bowling) 압출기가 흘러 나오기 쉬운 경우에 주로 유용합니다. 이 기능을 사용하면 인쇄 및 G 코드 생성 속도가 느려집니다." + +#: xs/src/libslic3r/PrintConfig.cpp:41 xs/src/libslic3r/PrintConfig.cpp:1818 +msgid "Other layers" +msgstr "다른 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:42 +msgid "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output." +msgstr "첫 번째 레이어 이후의 레이어 온도. 이 값을 0으로 설정하면 출력에서 ​​베드 온도 제어 명령을 비활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:45 +msgid "Bed temperature" +msgstr "배드 온도" + +#: xs/src/libslic3r/PrintConfig.cpp:52 +msgid "This custom code is inserted at every layer change, right before the Z move. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]." +msgstr "이 사용자 정의 코드는 Z 이동 직전의 모든 레이어 변경에 삽입됩니다. [Slide3r] 설정과 [layer_num] 및 [layer_z]에 대한 자리 표시 자 변수를 사용할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:62 +msgid "Between objects G-code" +msgstr "객체 간 G 코드" + +#: xs/src/libslic3r/PrintConfig.cpp:63 +msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." +msgstr "이 코드는 순차 인쇄를 사용할 때 객체간에 삽입됩니다. 기본적으로 익스트루더 및 베드 온도는 대기 모드가 아닌 명령을 사용하여 재설정됩니다. 그러나 이 사용자 코드에서 M104, M109, M140 또는 M190이 감지되면 Slic3r은 온도 명령을 추가하지 않습니다. 모든 Slic3r 설정에 자리 표시 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:71 lib/Slic3r/GUI/MainFrame.pm:328 +msgid "Bottom" +msgstr "바닥(Bottom)" + +#: xs/src/libslic3r/PrintConfig.cpp:72 xs/src/libslic3r/PrintConfig.cpp:276 +#: xs/src/libslic3r/PrintConfig.cpp:327 xs/src/libslic3r/PrintConfig.cpp:335 +#: xs/src/libslic3r/PrintConfig.cpp:701 xs/src/libslic3r/PrintConfig.cpp:871 +#: xs/src/libslic3r/PrintConfig.cpp:887 xs/src/libslic3r/PrintConfig.cpp:1156 +#: xs/src/libslic3r/PrintConfig.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1400 +#: xs/src/libslic3r/PrintConfig.cpp:1829 xs/src/libslic3r/PrintConfig.cpp:1885 +msgid "Layers and Perimeters" +msgstr "레이어 및 경계선" + +#: xs/src/libslic3r/PrintConfig.cpp:73 +msgid "Number of solid layers to generate on bottom surfaces." +msgstr "바닥면에 생성 할 솔리드 레이어의 수." + +#: xs/src/libslic3r/PrintConfig.cpp:75 +msgid "Bottom solid layers" +msgstr "바닥 단일 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:80 +msgid "Bridge" +msgstr "브리지" + +#: xs/src/libslic3r/PrintConfig.cpp:81 +msgid "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges." +msgstr "이것은 프린터가 브릿지에 사용할 가속도입니다. 브리지의 가속 제어를 사용하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:83 xs/src/libslic3r/PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:673 xs/src/libslic3r/PrintConfig.cpp:781 +#: xs/src/libslic3r/PrintConfig.cpp:931 xs/src/libslic3r/PrintConfig.cpp:972 +#: xs/src/libslic3r/PrintConfig.cpp:982 xs/src/libslic3r/PrintConfig.cpp:1185 +msgid "mm/s²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:89 +msgid "Bridging angle" +msgstr "브릿지 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:91 +msgid "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges. Use 180° for zero angle." +msgstr "브리징 각도 오버라이드(override)값이. 왼쪽으로 0 일 경우 브리징 각도가 자동으로 계산됩니다. 그렇지 않으면 제공된 각도가 모든 브리지에 사용됩니다. 각도 제로는 180 °를 사용하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:94 xs/src/libslic3r/PrintConfig.cpp:589 +#: xs/src/libslic3r/PrintConfig.cpp:1418 xs/src/libslic3r/PrintConfig.cpp:1429 +#: xs/src/libslic3r/PrintConfig.cpp:1649 xs/src/libslic3r/PrintConfig.cpp:1803 +msgid "°" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:100 +msgid "Bridges fan speed" +msgstr "브릿지 팬 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:101 +msgid "This fan speed is enforced during all bridges and overhangs." +msgstr "이 팬 속도는 모든 브릿지 및 오버행 중에 적용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:102 xs/src/libslic3r/PrintConfig.cpp:601 +#: xs/src/libslic3r/PrintConfig.cpp:990 xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1308 +msgid "%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:109 +msgid "Bridge flow ratio" +msgstr "브릿지 유량(flow)값" + +#: xs/src/libslic3r/PrintConfig.cpp:111 +msgid "This factor affects the amount of plastic for bridging. You can decrease it slightly to pull the extrudates and prevent sagging, although default settings are usually good and you should experiment with cooling (use a fan) before tweaking this." +msgstr "이 요인은 브리징을위한 플라스틱의 양에 영향을 미칩니다. 압출 성형물을 잡아 당겨 처짐을 방지하기 위해 약간 줄일 수 있지만 기본 설정은 일반적으로 좋지만이 문제를 해결하기 전에 냉각 (팬 사용)을 시도해야합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:121 +msgid "Bridges" +msgstr "브릿지(Bridges)" + +#: xs/src/libslic3r/PrintConfig.cpp:123 +msgid "Speed for printing bridges." +msgstr "브릿지 인쇄 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:124 xs/src/libslic3r/PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:480 xs/src/libslic3r/PrintConfig.cpp:508 +#: xs/src/libslic3r/PrintConfig.cpp:516 xs/src/libslic3r/PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:846 xs/src/libslic3r/PrintConfig.cpp:922 +#: xs/src/libslic3r/PrintConfig.cpp:940 xs/src/libslic3r/PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:962 xs/src/libslic3r/PrintConfig.cpp:1019 +#: xs/src/libslic3r/PrintConfig.cpp:1076 xs/src/libslic3r/PrintConfig.cpp:1214 +#: xs/src/libslic3r/PrintConfig.cpp:1385 xs/src/libslic3r/PrintConfig.cpp:1394 +#: xs/src/libslic3r/PrintConfig.cpp:1782 xs/src/libslic3r/PrintConfig.cpp:1895 +msgid "mm/s" +msgstr "mm/s" + +#: xs/src/libslic3r/PrintConfig.cpp:131 +msgid "Brim width" +msgstr "브림 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:132 +msgid "Horizontal width of the brim that will be printed around each object on the first layer." +msgstr "첫 번째 레이어의 각 객체 주위에 인쇄 될 가장자리의 가로 폭입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:139 +msgid "Clip multi-part objects" +msgstr "여러 파트 오브젝트 클립" + +#: xs/src/libslic3r/PrintConfig.cpp:140 +msgid "When printing multi-material objects, this settings will make slic3r to clip the overlapping object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)." +msgstr "멀티 메터리얼(multi-material) 개체를 인쇄 할 때이 설정을 사용하면 겹치는 개체 파트를 서로 겹쳐서 잘라낼 수 있습니다 (두 번째 부분은 첫 번째 부분에서 클리핑되며 세 번째 부분은 첫 번째 및 두 번째 부분에서 잘립니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:151 +msgid "Compatible printers condition" +msgstr "호환 가능한 프린터 조건" + +#: xs/src/libslic3r/PrintConfig.cpp:152 +msgid "A boolean expression using the configuration values of an active printer profile. If this expression evaluates to true, this profile is considered compatible with the active printer profile." +msgstr "활성 프린터 프로파일의 구성 값을 사용하는 부울 표현식. 이 표현식이 true로 평가되면이 프로필은 활성 프린터 프로필과 호환되는 것으로 간주됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:163 +msgid "Complete individual objects" +msgstr "개별 개체 완성" + +#: xs/src/libslic3r/PrintConfig.cpp:164 +msgid "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware." +msgstr "여러 객체 또는 사본을 인쇄 할 때이 객체는 다음 객체로 이동하기 전에 각 객체를 완성합니다 (맨 아래 레이어에서 시작). 이 기능은 인쇄물이 망가지는 위험을 피할 때 유용합니다. Slic3r은 압출기 충돌을 경고하고 예방해야하지만 조심하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:172 +msgid "Enable auto cooling" +msgstr "자동 냉각 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:173 +msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." +msgstr "이 플래그는 레이어 인쇄 시간에 따라 인쇄 속도와 팬 속도를 조정하는 자동 냉각 논리를 활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:179 +msgid "Cooling tube position" +msgstr "냉각 튜브 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:180 +msgid "Distance of the center-point of the cooling tube from the extruder tip " +msgstr "압출기 팁에서 냉각 튜브의 중심점까지의 거리 " + +#: xs/src/libslic3r/PrintConfig.cpp:187 +msgid "Cooling tube length" +msgstr "냉각 튜브 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:188 +msgid "Length of the cooling tube to limit space for cooling moves inside it " +msgstr "내부의 냉각 이동을 위해 공간을 제한하는 냉각 튜브의 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:196 +msgid "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all." +msgstr "역할 별 가속도 값이 사용 된 후에 프린터가 재설정되는 속도입니다 (둘레 / 충전). 가속을 전혀 재설정하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:205 +msgid "Default filament profile" +msgstr "기본 필라멘트 프로파일" + +#: xs/src/libslic3r/PrintConfig.cpp:206 +msgid "Default filament profile associated with the current printer profile. On selection of the current printer profile, this filament profile will be activated." +msgstr "현재 프린터 프로파일과 연관된 기본 필라멘트 프로파일. 현재 프린터 프로파일을 선택하면 이 필라멘트 프로파일이 활성화됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:211 +msgid "Default print profile" +msgstr "기본 인쇄 프로파일" + +#: xs/src/libslic3r/PrintConfig.cpp:212 +msgid "Default print profile associated with the current printer profile. On selection of the current printer profile, this print profile will be activated." +msgstr "현재 프린터 프로파일과 연관된 기본 인쇄 프로파일. 현재 프린터 프로파일을 선택하면이 인쇄 프로파일이 활성화됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:217 +msgid "Disable fan for the first" +msgstr "첫 번째 팬 사용 중지" + +#: xs/src/libslic3r/PrintConfig.cpp:218 +msgid "You can set this to a positive value to disable fan at all during the first layers, so that it does not make adhesion worse." +msgstr "이 값을 양수 값으로 설정하면 첫 번째 레이어에서 팬을 사용하지 않도록 설정하여 접착력을 악화시키지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:220 xs/src/libslic3r/PrintConfig.cpp:791 +#: xs/src/libslic3r/PrintConfig.cpp:1281 xs/src/libslic3r/PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1533 xs/src/libslic3r/PrintConfig.cpp:1685 +#: xs/src/libslic3r/PrintConfig.cpp:1730 +msgid "layers" +msgstr "레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:227 +msgid "Don't support bridges" +msgstr "서포트와 브릿지를 사용하지 마세요." + +#: xs/src/libslic3r/PrintConfig.cpp:229 +msgid "Experimental option for preventing support material from being generated under bridged areas." +msgstr "브릿지 영역 아래에 서포팅 재료가 생성되는 것을 방지하기위한 실험적 옵션." + +#: xs/src/libslic3r/PrintConfig.cpp:235 +msgid "Distance between copies" +msgstr "복사본 간 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:236 +msgid "Distance used for the auto-arrange feature of the plater." +msgstr "플래터(plater)의 자동 정렬 기능에 사용되는 거리입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:244 +msgid "Elephant foot compensation" +msgstr "코끼리 발(Elephant foot) 보상값" + +#: xs/src/libslic3r/PrintConfig.cpp:246 +msgid "The first layer will be shrunk in the XY plane by the configured value to compensate for the 1st layer squish aka an Elephant Foot effect." +msgstr "첫 번째 레이어는 구성 요소 값에 따라 XY 평면에서 수축되어 일층 스 퀴시 코끼리발(Elephant Foot) 효과를 보완합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:255 +msgid "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings." +msgstr "이 종료 절차는 출력 파일의 끝에 삽입된다. 모든 Slic3r 설정에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:265 +msgid "This end procedure is inserted at the end of the output file, before the printer end gcode. Note that you can use placeholder variables for all Slic3r settings. If you have multiple extruders, the gcode is processed in extruder order." +msgstr "이 종료 절차는 출력 파일의 끝에 프린터 끝 코드 앞에 삽입된다. 모든 Slic3r 설정에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오. 여러 개의 압출부가 있는 경우, 그 코드는 압출 순서대로 처리된다." + +#: xs/src/libslic3r/PrintConfig.cpp:275 +msgid "Ensure vertical shell thickness" +msgstr "수직 쉘(shell) 두께 확인" + +#: xs/src/libslic3r/PrintConfig.cpp:277 +msgid "Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers)." +msgstr "경사 표면 근처에 솔리드 인필을 추가하여 수직 셸 두께(상단+하단 솔리드 레이어)를 보장하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:283 +msgid "Top/bottom fill pattern" +msgstr "상단/하단 채우기(fill) 패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:285 +msgid "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells." +msgstr "상단/하단 주입의 채우기 패턴은 인접한 외부면이 아닌 외부 솔리드 층에만 영향을 미친다." + +#: xs/src/libslic3r/PrintConfig.cpp:294 xs/src/libslic3r/PrintConfig.cpp:654 +#: xs/src/libslic3r/PrintConfig.cpp:1764 +msgid "Rectilinear" +msgstr "직선면(Rectilinear)" + +#: xs/src/libslic3r/PrintConfig.cpp:295 xs/src/libslic3r/PrintConfig.cpp:660 +msgid "Concentric" +msgstr "동심원(Concentric)" + +#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:664 +msgid "Hilbert Curve" +msgstr "힐버트 곡선(Hilbert Curve)" + +#: xs/src/libslic3r/PrintConfig.cpp:297 xs/src/libslic3r/PrintConfig.cpp:665 +msgid "Archimedean Chords" +msgstr "아르키메데우스(Archimedean Chords)" + +#: xs/src/libslic3r/PrintConfig.cpp:298 xs/src/libslic3r/PrintConfig.cpp:666 +msgid "Octagram Spiral" +msgstr "옥타그램 나선(Octagram Spiral)" + +#: xs/src/libslic3r/PrintConfig.cpp:304 xs/src/libslic3r/PrintConfig.cpp:314 +msgid "External perimeters" +msgstr "외측 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:305 xs/src/libslic3r/PrintConfig.cpp:415 +#: xs/src/libslic3r/PrintConfig.cpp:689 xs/src/libslic3r/PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:1200 xs/src/libslic3r/PrintConfig.cpp:1540 +#: xs/src/libslic3r/PrintConfig.cpp:1702 xs/src/libslic3r/PrintConfig.cpp:1860 +msgid "Extrusion Width" +msgstr "압출 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:306 +msgid "Set this to a non-zero value to set a manual extrusion width for external perimeters. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 200%), it will be computed over layer height." +msgstr "외부 경계에 대한 수동 압출 폭을 설정하려면 이 값을 0이 아닌 값으로 설정하십시오. 0인 경우 기본 압출 너비가 사용되며, 그렇지 않으면 1.125 x 노즐 직경이 사용된다. 백분율(예: 200%)로 표현되는 경우, 레이어 높이에 걸쳐 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:309 xs/src/libslic3r/PrintConfig.cpp:694 +#: xs/src/libslic3r/PrintConfig.cpp:812 xs/src/libslic3r/PrintConfig.cpp:1205 +#: xs/src/libslic3r/PrintConfig.cpp:1544 xs/src/libslic3r/PrintConfig.cpp:1706 +#: xs/src/libslic3r/PrintConfig.cpp:1865 +msgid "mm or % (leave 0 for default)" +msgstr "mm 또는 %(기본값의 경우 0으로 유지)" + +#: xs/src/libslic3r/PrintConfig.cpp:316 +msgid "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto." +msgstr "이 별도의 설정은 외부 경계선(시각적 경계선)의 속도에 영향을 미친다. 백분율(예: 80%)로 표현되는 경우 위의 Perimeter 속도 설정에 따라 계산된다. 자동을 위해 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:319 xs/src/libslic3r/PrintConfig.cpp:716 +#: xs/src/libslic3r/PrintConfig.cpp:1503 xs/src/libslic3r/PrintConfig.cpp:1554 +#: xs/src/libslic3r/PrintConfig.cpp:1749 xs/src/libslic3r/PrintConfig.cpp:1877 +msgid "mm/s or %" +msgstr "mm/s 또는 %" + +#: xs/src/libslic3r/PrintConfig.cpp:326 +msgid "External perimeters first" +msgstr "외부 경계선 먼저" + +#: xs/src/libslic3r/PrintConfig.cpp:328 +msgid "Print contour perimeters from the outermost one to the innermost one instead of the default inverse order." +msgstr "기본 역순 대신 가장 바깥쪽부터 가장 안쪽까지 윤곽선을 인쇄하십시오. 타겟 TTS복사하기번역 저장번역 저장번역 수정." + +#: xs/src/libslic3r/PrintConfig.cpp:334 +msgid "Extra perimeters if needed" +msgstr "필요한 경우 추가 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:336 +#, no-c-format +msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." +msgstr "경사 벽의 틈을 피하기 위해 필요한 경우 더 많은 perimeter를 추가하십시오. 위의 루프의 70% 이상이 지지될 때까지 Slic3r는 계속해서 perimeter를 추가한다." + +#: xs/src/libslic3r/PrintConfig.cpp:346 +msgid "The extruder to use (unless more specific extruder settings are specified). This value overrides perimeter and infill extruders, but not the support extruders." +msgstr "사용할 압출부(더 구체적인 압출부 설정이 지정되지 않은 경우) 이 값은 경계 및 압출부를 초과하지만 지원 압출자를 주입하지는 않는다." + +#: xs/src/libslic3r/PrintConfig.cpp:358 lib/Slic3r/GUI/Plater/3DPreview.pm:75 +msgid "Height" +msgstr "높이" + +#: xs/src/libslic3r/PrintConfig.cpp:359 +msgid "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects." +msgstr "이것을 노즐 팁과 (일반적으로) X 캐리지 로드 사이의 수직 거리로 설정하십시오. 다시 말하면, 이것은 당신의 압출기 주위의 틈새 실린더의 높이이며, 그것은 다른 인쇄된 물체와 충돌하기 전에 압출기가 엿볼 수 있는 최대 깊이를 나타낸다." + +#: xs/src/libslic3r/PrintConfig.cpp:369 +msgid "Radius" +msgstr "반지름" + +#: xs/src/libslic3r/PrintConfig.cpp:370 +msgid "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater." +msgstr "이것을 당신의 압출기 주변의 간극 반경으로 설정하시오. 압출부가 중앙에 있지 않으면 안전을 위해 가장 큰 값을 선택하십시오. 이 설정은 충돌 여부를 확인하고 플래터에 그래픽 미리 보기를 표시하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:380 +msgid "Extruder Color" +msgstr "익스트루더 컬러" + +#: xs/src/libslic3r/PrintConfig.cpp:381 xs/src/libslic3r/PrintConfig.cpp:444 +msgid "This is only used in the Slic3r interface as a visual help." +msgstr "이것은 시각적 도움말로 Slic3r 인터페이스에서만 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:388 +msgid "Extruder offset" +msgstr "익스트루더 오프셋" + +#: xs/src/libslic3r/PrintConfig.cpp:389 +msgid "If your firmware doesn't handle the extruder displacement you need the G-code to take it into account. This option lets you specify the displacement of each extruder with respect to the first one. It expects positive coordinates (they will be subtracted from the XY coordinate)." +msgstr "펌웨어가 압출기 위치 변경을 처리하지 못하면 G 코드를 고려해야합니다. 이 옵션을 사용하면 첫 번째 것에 대한 각 압출기의 변위를 지정할 수 있습니다. 양의 좌표가 필요합니다 (XY 좌표에서 뺍니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:398 +msgid "Extrusion axis" +msgstr "압출 축" + +#: xs/src/libslic3r/PrintConfig.cpp:399 +msgid "Use this option to set the axis letter associated to your printer's extruder (usually E but some printers use A)." +msgstr "이 옵션을 사용하여 프린터의 압출기에 연결된 축 문자를 설정합니다 (보통 E이지만 일부 프린터는 A를 사용합니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:405 +msgid "Extrusion multiplier" +msgstr "압출 승수" + +#: xs/src/libslic3r/PrintConfig.cpp:406 +msgid "This factor changes the amount of flow proportionally. You may need to tweak this setting to get nice surface finish and correct single wall widths. Usual values are between 0.9 and 1.1. If you think you need to change this more, check filament diameter and your firmware E steps." +msgstr "이 요소는 비례하여 유량의 양을 변경합니다. 멋진 서페이스 마무리와 단일 벽 너비를 얻기 위해이 설정을 조정해야 할 수도 있습니다. 일반적인 값은 0.9와 1.1 사이입니다. 이 값을 더 변경해야한다고 판단되면 필라멘트 직경과 펌웨어 E 단계를 확인하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:414 +msgid "Default extrusion width" +msgstr "기본 압출 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:416 +msgid "Set this to a non-zero value to allow a manual extrusion width. If left to zero, Slic3r derives extrusion widths from the nozzle diameter (see the tooltips for perimeter extrusion width, infill extrusion width etc). If expressed as percentage (for example: 230%), it will be computed over layer height." +msgstr "수동 압출 폭을 허용하려면이 값을 0이 아닌 값으로 설정하십시오. 0으로 남겨두면 Slic3r은 노즐 직경에서 압출 폭을 도출합니다 (주변 압출 폭, 성형 압출 폭 등의 툴팁 참조). 백분율로 표시되는 경우 (예 : 230 %) 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:420 +msgid "mm or % (leave 0 for auto)" +msgstr "mm 또는 % (자동으로 0을 유지)" + +#: xs/src/libslic3r/PrintConfig.cpp:425 +msgid "Keep fan always on" +msgstr "항상 팬 켜기" + +#: xs/src/libslic3r/PrintConfig.cpp:426 +msgid "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS." +msgstr "이 기능을 사용하면 팬이 비활성화되지 않으며 최소한 최소 속도로 계속 회전합니다. PLA에 유용하며 ABS에 해롭다." + +#: xs/src/libslic3r/PrintConfig.cpp:432 +msgid "Enable fan if layer print time is below" +msgstr "레이어 인쇄 시간이 미만인 경우 팬 활성화" + +#: xs/src/libslic3r/PrintConfig.cpp:433 +msgid "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds." +msgstr "레이어 인쇄 시간이이 초 미만으로 예상되는 경우 팬이 활성화되고 속도는 최소 및 최대 속도를 보간하여 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:435 xs/src/libslic3r/PrintConfig.cpp:1490 +msgid "approximate seconds" +msgstr "근사치 초" + +#: xs/src/libslic3r/PrintConfig.cpp:443 +msgid "Color" +msgstr "색상" + +#: xs/src/libslic3r/PrintConfig.cpp:450 +msgid "Filament notes" +msgstr "필라멘트 메모" + +#: xs/src/libslic3r/PrintConfig.cpp:451 +msgid "You can put your notes regarding the filament here." +msgstr "여기에 필라멘트에 관한 메모를 넣을 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:459 xs/src/libslic3r/PrintConfig.cpp:1025 +msgid "Max volumetric speed" +msgstr "최대 체적 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:460 +msgid "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric speed of a print to the minimum of print and filament volumetric speed. Set to zero for no limit." +msgstr "이 필라멘트에 허용되는 최대 체적 속도. 인쇄물의 최대 체적 속도를 인쇄 및 필라멘트 체적 속도 최소로 제한한다. 제한 없음에 대해 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:463 xs/src/libslic3r/PrintConfig.cpp:1028 +msgid "mm³/s" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:469 +msgid "Loading speed" +msgstr "로딩 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:470 +msgid "Speed used for loading the filament on the wipe tower. " +msgstr "와이퍼 탑(wipe)에 필라멘트를 장착하는 데 사용되는 속도. " + +#: xs/src/libslic3r/PrintConfig.cpp:477 +msgid "Unloading speed" +msgstr "언로딩 스피드" + +#: xs/src/libslic3r/PrintConfig.cpp:478 +msgid "Speed used for unloading the filament on the wipe tower (does not affect initial part of unloading just after ramming). " +msgstr "와이퍼 타워에서 필라멘트를 언로드하는 데 사용되는 속도(램핑 후 바로 언로딩의 초기 부분에는 영향을 주지 않음)" + +#: xs/src/libslic3r/PrintConfig.cpp:486 +msgid "Delay after unloading" +msgstr "언로드 후 딜레이" + +#: xs/src/libslic3r/PrintConfig.cpp:487 +msgid "Time to wait after the filament is unloaded. May help to get reliable toolchanges with flexible materials that may need more time to shrink to original dimensions. " +msgstr "필라멘트를 내린 후 기다리는 시간. 원래 치수로 축소하는 데 더 많은 시간이 필요할 수있는 유연한 재료로 신뢰할 수있는 공구 교환을 얻을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:496 +msgid "Number of cooling moves" +msgstr "쿨링 이동 숫자" + +#: xs/src/libslic3r/PrintConfig.cpp:497 +msgid "Filament is cooled by being moved back and forth in the cooling tubes. Specify desired number of these moves " +msgstr "필라멘트는 냉각 튜브에서 앞뒤로 움직여 냉각됩니다. 원하는 이동 숫자 지정" + +#: xs/src/libslic3r/PrintConfig.cpp:505 +msgid "Speed of the first cooling move" +msgstr "첫 번째 냉각 이동 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:506 +msgid "Cooling moves are gradually accelerating beginning at this speed. " +msgstr "냉각 속도가 서서히 빨라지고 있습니다. " + +#: xs/src/libslic3r/PrintConfig.cpp:513 +msgid "Speed of the last cooling move" +msgstr "마지막 냉각 이동 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:514 +msgid "Cooling moves are gradually accelerating towards this speed. " +msgstr "냉각은 이 속도쪽으로 점차 가속화되고 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:521 +msgid "Ramming parameters" +msgstr "래밍 파라미터" + +#: xs/src/libslic3r/PrintConfig.cpp:522 +msgid "This string is edited by RammingDialog and contains ramming specific parameters " +msgstr "이 문자열은 RammingDialog에 의해 편집되고 램밍 특정 매개 변수를 포함합니다" + +#: xs/src/libslic3r/PrintConfig.cpp:529 +msgid "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average." +msgstr "여기에 필라멘트 직경을 입력하십시오. 정밀도가 필요하므로 캘리퍼를 사용하여 필라멘트를 따라 여러 번 측정 한 다음 평균을 계산하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:537 +msgid "Density" +msgstr "밀도" + +#: xs/src/libslic3r/PrintConfig.cpp:538 +msgid "Enter your filament density here. This is only for statistical information. A decent way is to weigh a known length of filament and compute the ratio of the length to volume. Better is to calculate the volume directly through displacement." +msgstr "여기서 필라멘트 밀도를 입력하십시오. 이것은 통계 정보 용입니다. 괜찮은 방법은 알려진 길이의 필라멘트의 무게를 측정하고 길이와 볼륨의 비율을 계산하는 것입니다. 변위를 통해 직접적으로 부피를 계산하는 것이 더 좋습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:541 +msgid "g/cm³" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:547 +msgid "Filament type" +msgstr "필라멘트 타입" + +#: xs/src/libslic3r/PrintConfig.cpp:548 xs/src/libslic3r/PrintConfig.cpp:1235 +msgid "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables." +msgstr "사용자 정의 스크립트를 통해 출력 G 코드를 처리하려면 여기에 절대 경로를 나열하십시오. 여러 개의 스크립트를 세미콜론으로 구분하십시오. 스크립트는 G 코드 파일의 절대 경로를 첫 번째 인수로 전달되며 환경 변수를 읽음으로써 Slic3r 구성 설정에 액세스 할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:567 +msgid "Soluble material" +msgstr "수용성 재료" + +#: xs/src/libslic3r/PrintConfig.cpp:568 +msgid "Soluble material is most likely used for a soluble support." +msgstr "수용성 재료눈 물에 녹는 서포트에 가장 많이 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:573 lib/Slic3r/GUI/Plater.pm:1616 +msgid "Cost" +msgstr "비용" + +#: xs/src/libslic3r/PrintConfig.cpp:574 +msgid "Enter your filament cost per kg here. This is only for statistical information." +msgstr "kg 당 필라멘트 비용을 여기에 입력하십시오. 통계를 내기 위해서 입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:575 +msgid "money/kg" +msgstr "원(\\)/kg" + +#: xs/src/libslic3r/PrintConfig.cpp:584 +msgid "Fill angle" +msgstr "채움 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:586 +msgid "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them." +msgstr "본 오리엔테이션 방향의 기본 각도입니다. 해칭이 적용될 것입니다. Slic3r이 감지 할 수있는 최상의 방향을 사용하여 브릿징이 채워지므로이 설정은 영향을 미치지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:598 +msgid "Fill density" +msgstr "채우기(fill) 밀도" + +#: xs/src/libslic3r/PrintConfig.cpp:600 +#, no-c-format +msgid "Density of internal infill, expressed in the range 0% - 100%." +msgstr "0 % - 100 % 범위로 표현 된 내부 채움(infill)의 밀도." + +#: xs/src/libslic3r/PrintConfig.cpp:636 +msgid "Fill pattern" +msgstr "채우기(fill) 패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:638 +msgid "Fill pattern for general low-density infill." +msgstr "일반 낮은 밀도 채움의 패턴." + +#: xs/src/libslic3r/PrintConfig.cpp:655 +msgid "Grid" +msgstr "그리드(Grid)" + +#: xs/src/libslic3r/PrintConfig.cpp:656 +msgid "Triangles" +msgstr "삼각형(Triangles)" + +#: xs/src/libslic3r/PrintConfig.cpp:657 +msgid "Stars" +msgstr "별(Stars)" + +#: xs/src/libslic3r/PrintConfig.cpp:658 +msgid "Cubic" +msgstr "큐빅" + +#: xs/src/libslic3r/PrintConfig.cpp:659 +msgid "Line" +msgstr "선(Line)" + +#: xs/src/libslic3r/PrintConfig.cpp:661 xs/src/libslic3r/PrintConfig.cpp:1766 +msgid "Honeycomb" +msgstr "벌집" + +#: xs/src/libslic3r/PrintConfig.cpp:662 +msgid "3D Honeycomb" +msgstr "3D 벌집" + +#: xs/src/libslic3r/PrintConfig.cpp:663 +msgid "Gyroid" +msgstr "자이로이드(Gyroid)" + +#: xs/src/libslic3r/PrintConfig.cpp:670 xs/src/libslic3r/PrintConfig.cpp:679 +#: xs/src/libslic3r/PrintConfig.cpp:688 xs/src/libslic3r/PrintConfig.cpp:722 +msgid "First layer" +msgstr "첫 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:671 +msgid "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer." +msgstr "이것은 프린터가 첫 번째 레이어에 사용할 가속도입니다. 0을 설정하면 첫 번째 레이어에 대한 가속 제어가 사용되지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:680 +msgid "Heated build plate temperature for the first layer. Set this to zero to disable bed temperature control commands in the output." +msgstr "첫 번째 레이어에 대한 빌드 플레이트 온도를 가열. 이 값을 0으로 설정하면 출력에서 ​​베드 온도 제어 명령을 비활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:690 +msgid "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) it will be computed over first layer height. If set to zero, it will use the default extrusion width." +msgstr "첫 번째 레이어의 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정합니다. 이 방법을 사용하면보다 우수한 접착력을 위해 더 두꺼운 압출 성형물을 만들 수 있습니다. 백분율 (예 : 120 %)로 표현하면 첫 번째 레이어 높이를 기준으로 계산됩니다. 0으로 설정하면 기본 압출 폭이 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:700 +msgid "First layer height" +msgstr "첫 레이어 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:702 +msgid "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height." +msgstr "매우 낮은 층의 높이로 인쇄할 때, 당신은 여전히 완벽하지 않은 빌드 플레이트의 부착력과 허용오차를 개선하기 위해 더 두꺼운 바닥 층을 인쇄하기를 원할 수 있다. 이것은 절대값 또는 기본 계층 높이에 대한 백분율(예: 150%)로 표시할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:706 xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:1638 +msgid "mm or %" +msgstr "mm/s 또는 %" + +#: xs/src/libslic3r/PrintConfig.cpp:712 +msgid "First layer speed" +msgstr "첫 레이어 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:713 +msgid "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds." +msgstr "절대값(mm/s)으로 표현되는 경우, 이 속도는 유형에 관계없이 첫 번째 층의 모든 인쇄 이동에 적용된다. 백분율(예: 40%)로 표현되는 경우 기본 속도를 스케일링한다." + +#: xs/src/libslic3r/PrintConfig.cpp:723 +msgid "Extruder temperature for first layer. If you want to control temperature manually during print, set this to zero to disable temperature control commands in the output file." +msgstr "첫 번째 층의 외부 온도. 인쇄 중에 온도를 수동으로 제어하려면 출력 파일에서 온도 제어 명령을 사용하지 않으려면 이 값을 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:731 +#: xs/src/libslic3r/GCode/PreviewData.cpp:170 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:97 +msgid "Gap fill" +msgstr "공백 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:733 +msgid "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling." +msgstr "짧은 지그재그로 작은 틈을 메우기 위한 속도. 너무 많은 진동과 공진 문제를 피하기 위해 이것을 합리적으로 낮게 유지한다. 간격 채우기를 사용하지 않으려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:741 +msgid "Verbose G-code" +msgstr "세부 G-코드" + +#: xs/src/libslic3r/PrintConfig.cpp:742 +msgid "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down." +msgstr "설명 텍스트로 설명되는 각 행과 함께 코멘트된 G-code 파일을 가져오려면 이 옵션을 선택하십시오. 만일 당신이 SD카드로 인쇄한다면, 파일의 추가 무게로 인해 펌웨어의 속도가 느려질 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:749 +msgid "G-code flavor" +msgstr "G-code 형식" + +#: xs/src/libslic3r/PrintConfig.cpp:750 +msgid "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all." +msgstr "온도 조절 등을 포함한 일부 G/M-코드 명령은 보편적이지 않다. 호환되는 출력을 얻으려면 이 옵션을 프린터의 펌웨어로 설정하십시오. \"압출 없음\" 형식은 Slic3r가 어떠한 압출 값도 출력하지 못하게 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:774 +msgid "No extrusion" +msgstr "압출 없음" + +#: xs/src/libslic3r/PrintConfig.cpp:779 +msgid "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill." +msgstr "이것은 당신 프린터의 채움 가속력이다. 주입에 대한 가속 제어를 비활성화하려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:787 +msgid "Combine infill every" +msgstr "다음 시간마다 결합" + +#: xs/src/libslic3r/PrintConfig.cpp:789 +msgid "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy." +msgstr "이 기능은 인필을 결합하고 얇은 주변기기를 보존하면서 두꺼운 인필 층을 압출하여 인쇄 속도를 높일 수 있도록 하여 정확도를 높인다." + +#: xs/src/libslic3r/PrintConfig.cpp:793 +msgid "Combine infill every n layers" +msgstr "모든 n개 층을 채우기 위해 결합" + +#: xs/src/libslic3r/PrintConfig.cpp:798 +msgid "Infill extruder" +msgstr "채움(Infill) 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:800 +msgid "The extruder to use when printing infill." +msgstr "채움으로 사용할 익스트루더." + +#: xs/src/libslic3r/PrintConfig.cpp:808 +msgid "Set this to a non-zero value to set a manual extrusion width for infill. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "채움에 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정합니다. 0으로 설정하면 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 1.125 x 노즐 직경이 사용됩니다. 채움 속도를 높이고 부품을 더 강하게 만들려면보다 큰 압출 성형물을 사용하는 것이 좋습니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:817 +msgid "Infill before perimeters" +msgstr "둘레보다 앞쪽에 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:818 +msgid "This option will switch the print order of perimeters and infill, making the latter first." +msgstr "이 옵션은 외부출력과 채움 인쇄 순서를 바꾸어, 후자를 먼저 만든다." + +#: xs/src/libslic3r/PrintConfig.cpp:823 +msgid "Only infill where needed" +msgstr "필요한 경우 채음" + +#: xs/src/libslic3r/PrintConfig.cpp:825 +msgid "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved." +msgstr "이 옵션은 천장 지원에 실제로 필요한 영역에만 적용된다(내부 서포트 재료 역할을 할 것이다). 활성화된 경우 관련된 여러 번의 점검으로 인해 G-code 생성 속도를 늦춰라." + +#: xs/src/libslic3r/PrintConfig.cpp:832 +msgid "Infill/perimeters overlap" +msgstr "채움/둘레 겹침(perimeters overlap)" + +#: xs/src/libslic3r/PrintConfig.cpp:834 +msgid "This setting applies an additional overlap between infill and perimeters for better bonding. Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed as percentage (example: 15%) it is calculated over perimeter extrusion width." +msgstr "이 설정은 더 나은 본딩을 위해 충전 및 둘레 사이에 추가 겹침을 적용합니다. 이론적으로 이것은 필요하지 않아야하지만 백래시가 갭을 유발할 수 있습니다. 백분율 (예 : 15 %)로 표시되는 경우 경계 압출 폭을 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:845 +msgid "Speed for printing the internal fill. Set to zero for auto." +msgstr "내부 채우기 인쇄 속도. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:854 +msgid "Inherits profile" +msgstr "프로필 상속" + +#: xs/src/libslic3r/PrintConfig.cpp:855 +msgid "Name of the profile, from which this profile inherits." +msgstr "이 프로파일이 상속되는 프로파일의 이름." + +#: xs/src/libslic3r/PrintConfig.cpp:866 +msgid "Interface shells" +msgstr "인터페이스 셸(shells)" + +#: xs/src/libslic3r/PrintConfig.cpp:867 +msgid "Force the generation of solid shells between adjacent materials/volumes. Useful for multi-extruder prints with translucent materials or manual soluble support material." +msgstr "인접 재료/볼륨 사이에 고체 쉘 생성을 강제하십시오. 반투명 재료 또는 수동 수용성 서포트 재료를 사용한 다중 압ㅊ기 인쇄에 유용함." + +#: xs/src/libslic3r/PrintConfig.cpp:876 +msgid "This custom code is inserted at every layer change, right after the Z move and before the extruder moves to the first layer point. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]." +msgstr "이 사용자 정의 코드는 Z 이동 직후와 압출부가 첫 번째 레이어 포인트로 이동하기 전에 모든 레이어 변경 시 삽입된다. 모든 Slic3r 설정뿐만 아니라 [layer_num] 및 [layer_z]에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:888 +msgid "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print." +msgstr "이 설정은 슬라이스/레이어의 높이(따라서 총 수)를 제어한다. 얇은 층은 더 나은 정확성을 제공하지만 인쇄하는 데는 더 많은 시간이 걸린다." + +#: xs/src/libslic3r/PrintConfig.cpp:896 +msgid "Support silent mode" +msgstr "서포트 무음 모드" + +#: xs/src/libslic3r/PrintConfig.cpp:897 +msgid "Set silent mode for the G-code flavor" +msgstr "G-코드 특징에 대한 무음 모드 설정" + +#: xs/src/libslic3r/PrintConfig.cpp:919 +#, c-format +msgid "Maximum feedrate %1%" +msgstr "최 대 공 급 속 도" + +#: xs/src/libslic3r/PrintConfig.cpp:921 +#, c-format +msgid "Maximum feedrate of the %1% axis" +msgstr "최대 공급 속도 of the %1% axis" + +#: xs/src/libslic3r/PrintConfig.cpp:928 +#, c-format +msgid "Maximum acceleration %1%" +msgstr "최대가속 %1%" + +#: xs/src/libslic3r/PrintConfig.cpp:930 +#, c-format +msgid "Maximum acceleration of the %1% axis" +msgstr "최대 가속도는 %1% 축" + +#: xs/src/libslic3r/PrintConfig.cpp:937 +#, c-format +msgid "Maximum jerk %1%" +msgstr "최대 저크(jerk) %1%" + +#: xs/src/libslic3r/PrintConfig.cpp:939 +#, c-format +msgid "Maximum jerk of the %1% axis" +msgstr "최대 저크는(jerk) %1% axis" + +#: xs/src/libslic3r/PrintConfig.cpp:949 xs/src/libslic3r/PrintConfig.cpp:951 +msgid "Minimum feedrate when extruding" +msgstr "압출시 최소 공급 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:959 xs/src/libslic3r/PrintConfig.cpp:961 +msgid "Minimum travel feedrate" +msgstr "최소 이송 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:969 xs/src/libslic3r/PrintConfig.cpp:971 +msgid "Maximum acceleration when extruding" +msgstr "압출시 최대 가속도" + +#: xs/src/libslic3r/PrintConfig.cpp:979 xs/src/libslic3r/PrintConfig.cpp:981 +msgid "Maximum acceleration when retracting" +msgstr "리트렉션 최대 가속도" + +#: xs/src/libslic3r/PrintConfig.cpp:988 xs/src/libslic3r/PrintConfig.cpp:997 +msgid "Max" +msgstr "최대" + +#: xs/src/libslic3r/PrintConfig.cpp:989 +msgid "This setting represents the maximum speed of your fan." +msgstr "이 설정은 팬의 최대 속도를 나타냅니다." + +#: xs/src/libslic3r/PrintConfig.cpp:998 +#, no-c-format +msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." +msgstr "이것은이 익스트루더의 가장 높은 인쇄 가능 층 높이이며, 가변 층 높이 및 지지층 높이를 캡하는 데 사용됩니다. 합당한 층간 접착력을 얻기 위해 최대 권장 높이는 압출 폭의 75 %입니다. 0으로 설정하면 층 높이가 노즐 지름의 75 %로 제한됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1008 +msgid "Max print height" +msgstr "최대 프린트 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:1009 +msgid "Set this to the maximum height that can be reached by your extruder while printing." +msgstr "인쇄 중에 익스트루더가 도달 할 수있는 최대 높이로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1015 +msgid "Max print speed" +msgstr "최대 프린트 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1016 +msgid "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow." +msgstr "다른 속도 설정을 0으로 설정할 경우, 지속적인 외부 압력을 유지하기 위해 최적의 속도를 자동 계산한다. 이 실험 설정은 허용할 최대 인쇄 속도를 설정하는 데 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1026 +msgid "This experimental setting is used to set the maximum volumetric speed your extruder supports." +msgstr "이 실험 설정은 압출기가 지원하는 최대 체적 속도를 설정하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1034 +msgid "Max volumetric slope positive" +msgstr "최대 체적 기울기 양" + +#: xs/src/libslic3r/PrintConfig.cpp:1035 xs/src/libslic3r/PrintConfig.cpp:1046 +msgid "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." +msgstr "이 실험 설정은 돌출율의 변화 속도를 제한하는데 사용된다. 1.8mm3/s2 값은 1.8mm3/s(0.45mm 압출 폭, 0.2mm 압출 높이, 공급 속도 20mm/s)에서 5.4mm3/s(공급 속도 60mm/s)로 변경하는 데 최소 2초 이상 걸린다." + +#: xs/src/libslic3r/PrintConfig.cpp:1039 xs/src/libslic3r/PrintConfig.cpp:1050 +msgid "mm³/s²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1045 +msgid "Max volumetric slope negative" +msgstr "최대 체적 기울기 음수" + +#: xs/src/libslic3r/PrintConfig.cpp:1056 xs/src/libslic3r/PrintConfig.cpp:1065 +msgid "Min" +msgstr "최소" + +#: xs/src/libslic3r/PrintConfig.cpp:1057 +msgid "This setting represents the minimum PWM your fan needs to work." +msgstr "이 설정은 최소 PWM팬이 활동하는데 필요한를 나타냅니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1066 +msgid "This is the lowest printable layer height for this extruder and limits the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm." +msgstr "이것은 이 압출기에 대한 가장 낮은 인쇄 가능한 층 높이이고 가변 층 높이에 대한 분해능을 제한한다. 대표적인 값은 0.05mm와 0.1mm이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1074 +msgid "Min print speed" +msgstr "최소 인쇄 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1075 +msgid "Slic3r will not scale speed down below this speed." +msgstr "Slic3r는 이 속도 이하로 속도를 낮추지 않을 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1082 +msgid "Minimal filament extrusion length" +msgstr "최소 필라멘트 압출 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1083 +msgid "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder." +msgstr "하단 레이어에서 지정된 양의 필라멘트를 사용하는 데 필요한 스커트 루프의 수 이상으로 생성한다. 멀티 익스트루더의 경우, 이 최소값은 각 추가기기에 적용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1092 +msgid "Configuration notes" +msgstr "구성 노트" + +#: xs/src/libslic3r/PrintConfig.cpp:1093 +msgid "You can put here your personal notes. This text will be added to the G-code header comments." +msgstr "여기에 개인 노트를 넣을 수 있다. 이 텍스트는 G-code 헤더 코멘트에 추가될 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1102 +msgid "Nozzle diameter" +msgstr "노즐 직경:" + +#: xs/src/libslic3r/PrintConfig.cpp:1103 +msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" +msgstr "이 지름은 엑스트루더 노즐의 직경이다(예: 0.5, 0.35 등)." + +#: xs/src/libslic3r/PrintConfig.cpp:1109 +msgid "API Key" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1110 +msgid "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication." +msgstr "Slic3r는 G-code 파일을 OctoPrint에 업로드할 수 있다. 이 필드에는 인증에 필요한 API 키가 포함되어야 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1123 +msgid "Hostname, IP or URL" +msgstr "호스트 이름(Hostname), IP or URL" + +#: xs/src/libslic3r/PrintConfig.cpp:1124 +msgid "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname, IP address or URL of the OctoPrint instance." +msgstr "Slic3r는 G-code 파일을 OctoPrint에 업로드할 수 있다. 이 필드에는 OctoPrint 인스턴스의 호스트 이름, IP 주소 또는 URL이 포함되어야 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1130 +msgid "Only retract when crossing perimeters" +msgstr "둘레를 횡단 할 때만 수축" + +#: xs/src/libslic3r/PrintConfig.cpp:1131 +msgid "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)." +msgstr "이동 경로가 상위 레이어의 경계를 초과하지 않는 경우 리트랙션을 비활성화합니다. 따라서 모든 오즈가 보이지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1138 +msgid "This option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures." +msgstr "이 옵션은 누출을 방지하기 위해 비활성 압출기의 온도를 떨어 뜨립니다. 온도를 변경할 때 키가 큰 스커트를 자동으로 사용하고 스커트 외부로 압출기를 이동합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1145 +msgid "Output filename format" +msgstr "출력 파일이름 형식" + +#: xs/src/libslic3r/PrintConfig.cpp:1146 +msgid "You can use all configuration options as variables inside this template. For example: [layer_height], [fill_density] etc. You can also use [timestamp], [year], [month], [day], [hour], [minute], [second], [version], [input_filename], [input_filename_base]." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1155 +msgid "Detect bridging perimeters" +msgstr "브릿 징 경계선 감지" + +#: xs/src/libslic3r/PrintConfig.cpp:1157 +msgid "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan." +msgstr "오버행에 대한 유량을 조정하는 실험 옵션 (브리지 흐름(flow)이 사용됨)에 브릿지 속도를 적용하고 팬을 활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1163 +msgid "Filament parking position" +msgstr "필라멘트 멈춤 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1164 +msgid "Distance of the extruder tip from the position where the filament is parked when unloaded. This should match the value in printer firmware. " +msgstr "언 로딩시 필라멘트 위치에서 압출기 팁의 거리. 이 값은 프린터 펌웨어의 값과 일치해야합니다" + +#: xs/src/libslic3r/PrintConfig.cpp:1172 +msgid "Extra loading distance" +msgstr "추가 로딩 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1173 +msgid "When set to zero, the distance the filament is moved from parking position during load is exactly the same as it was moved back during unload. When positive, it is loaded further, if negative, the loading move is shorter than unloading. " +msgstr "0으로 설정하면로드 중에 필라멘트가 위치에서 이동 한 거리는 언로드 중에 다시 이동 한 거리와 동일합니다. 양수이면 음수가 더 많이 로드되고 로드가 음수 인 경우 언로드보다 짧습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1181 xs/src/libslic3r/PrintConfig.cpp:1199 +#: xs/src/libslic3r/PrintConfig.cpp:1211 xs/src/libslic3r/PrintConfig.cpp:1221 +msgid "Perimeters" +msgstr "둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:1182 +msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." +msgstr "프린터가 둘레로 사용할 가속도입니다. 9000과 같은 높은 값은 하드웨어가 제대로 작동하면 좋은 결과를 제공합니다. 주변을 가속 제어하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1190 +msgid "Perimeter extruder" +msgstr "주변 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1192 +msgid "The extruder to use when printing perimeters and brim. First extruder is 1." +msgstr "둘레와 가장자리를 인쇄 할 때 사용할 압출기입니다. 첫 번째 압출기는 1입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1201 +msgid "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 200%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하면 수동 압출 폭을 둘레로 설정할 수 있습니다. 보다 정확한 서페이스를 얻으려면 더 얇은 압출 성형품을 사용하는 것이 좋습니다. 0으로 설정하면 설정된 경우 기본 돌출 폭이 사용되고 그렇지 않으면 1.125 x 노즐 직경이 사용됩니다. 백분율 (예 : 200 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1213 +msgid "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." +msgstr "둘레의 속도 (등고선, 일명 세로 셸). 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1223 +msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." +msgstr "이 옵션은 각 레이어에 대해 생성 할 경계 수를 설정합니다. 추가 경계선 옵션을 사용하면 더 큰 주변 수를 사용하는 경사면을 감지 할 때 Slic3r이이 수를 자동으로 증가시킬 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1227 +msgid "(minimum)" +msgstr "(최소)" + +#: xs/src/libslic3r/PrintConfig.cpp:1247 +msgid "Printer type" +msgstr "프린터 타입" + +#: xs/src/libslic3r/PrintConfig.cpp:1248 +msgid "Type of the printer." +msgstr "프린터 유형." + +#: xs/src/libslic3r/PrintConfig.cpp:1252 +msgid "Printer notes" +msgstr "프린터 노트" + +#: xs/src/libslic3r/PrintConfig.cpp:1253 +msgid "You can put your notes regarding the printer here." +msgstr "프린터 관련 메모를 여기에 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1261 +msgid "Printer vendor" +msgstr "제조 회사" + +#: xs/src/libslic3r/PrintConfig.cpp:1262 +msgid "Name of the printer vendor." +msgstr "프린터 공급 업체의 이름입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1266 +msgid "Printer variant" +msgstr "프린터 변형" + +#: xs/src/libslic3r/PrintConfig.cpp:1267 +msgid "Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter." +msgstr "프린터 변종 이름입니다. 예를 들어, 프린터 변형은 노즐 지름으로 구별 될 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1277 +msgid "Raft layers" +msgstr "라프트(Raft) 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1279 +msgid "The object will be raised by this number of layers, and support material will be generated under it." +msgstr "물체는 이 개수의 층에 의해 상승되며, 그 아래에서 서포트 재료가 생성될 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1287 +msgid "Resolution" +msgstr "해결" + +#: xs/src/libslic3r/PrintConfig.cpp:1288 +msgid "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input." +msgstr "잘라내기 작업의 속도를 높이고 메모리 사용량을 줄이기 위해 입력 파일을 단순화하는 데 사용되는 최소 세부 해상도. 고해상도 모델은 종종 프린터가 렌더링할 수 있는 것보다 더 많은 디테일을 가지고 있다. 단순화를 사용하지 않고 입력에서 전체 해상도를 사용하려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1298 +msgid "Minimum travel after retraction" +msgstr "리트랙션 후 최소 이동 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1299 +msgid "Retraction is not triggered when travel moves are shorter than this length." +msgstr "이동 거리가 이 길이보다 짧으면 리트렉션이 트리거되지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1305 +msgid "Retract amount before wipe" +msgstr "닦아 내기 전의 수축량" + +#: xs/src/libslic3r/PrintConfig.cpp:1306 +msgid "With bowden extruders, it may be wise to do some amount of quick retract before doing the wipe movement." +msgstr "보우 덴 압출기를 사용하면 와이퍼 동작을하기 전에 약간의 빠른 리트랙션 를하는 것이 좋습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1313 +msgid "Retract on layer change" +msgstr "레이어 변경 후퇴" + +#: xs/src/libslic3r/PrintConfig.cpp:1314 +msgid "This flag enforces a retraction whenever a Z move is done." +msgstr "이 플래그는 Z 이동이 완료 될 때마다 취소를 강제 실행합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1319 xs/src/libslic3r/PrintConfig.cpp:1328 +msgid "Length" +msgstr "길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1320 +msgid "Retraction Length" +msgstr "리트랙션 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1321 +msgid "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)." +msgstr "후퇴가 트리거되면 필라멘트가 지정된 양만큼 뒤로 당겨집니다 (길이는 압출기에 들어가기 전에 원시 필라멘트에서 측정됩니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:1323 xs/src/libslic3r/PrintConfig.cpp:1333 +msgid "mm (zero to disable)" +msgstr "mm (0은 비활성화)" + +#: xs/src/libslic3r/PrintConfig.cpp:1329 +msgid "Retraction Length (Toolchange)" +msgstr "리트랙션 길이 (툴 체인지)" + +#: xs/src/libslic3r/PrintConfig.cpp:1330 +msgid "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)." +msgstr "공구를 교체하기 전에 후퇴가 트리거되면 필라멘트가 지정된 양만큼 뒤로 당겨집니다 (길이는 압출기에 들어가기 전에 원시 필라멘트에서 측정됩니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:1338 +msgid "Lift Z" +msgstr "Z축 올림" + +#: xs/src/libslic3r/PrintConfig.cpp:1339 +msgid "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered." +msgstr "이 값을 양수 값으로 설정하면 철회가 트리거 될 때마다 Z가 빠르게 올라갑니다. 여러 개의 압출기를 사용하는 경우 첫 번째 압출기의 설정 만 고려됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1347 +msgid "Above Z" +msgstr "Z 위" + +#: xs/src/libslic3r/PrintConfig.cpp:1348 +msgid "Only lift Z above" +msgstr "오직 Z축 위로만" + +#: xs/src/libslic3r/PrintConfig.cpp:1349 +msgid "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers." +msgstr "이것을 양의 값으로 설정하면, Z 리프트는 지정된 절대 Z 위로만 발생한다. 첫 번째 층에서 리프트를 건너뛸 수 있도록 이 설정을 조정할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:1356 +msgid "Below Z" +msgstr "Z 아래" + +#: xs/src/libslic3r/PrintConfig.cpp:1357 +msgid "Only lift Z below" +msgstr "Z값 아래만" + +#: xs/src/libslic3r/PrintConfig.cpp:1358 +msgid "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers." +msgstr "이것을 양수 값으로 설정하면 Z 리프트가 지정된 절대 Z 아래에서만 발생합니다. 첫 번째 레이어로 리프트를 제한하기 위해이 설정을 조정할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1366 xs/src/libslic3r/PrintConfig.cpp:1374 +msgid "Extra length on restart" +msgstr "재시작시 여분의 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1367 +msgid "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed." +msgstr "이동 후 리트렉셔이 보정되면 익스트루더가 추가 양의 필라멘트를 밀어냅니다. 이 설정은 거의 필요하지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1375 +msgid "When the retraction is compensated after changing tool, the extruder will push this additional amount of filament." +msgstr "도구를 교환 한 후 리트렉션를 보정하면 익스트루더가 추가 양의 필라멘트를 밀게됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1382 xs/src/libslic3r/PrintConfig.cpp:1383 +msgid "Retraction Speed" +msgstr "리트랙션 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1384 +msgid "The speed for retractions (it only applies to the extruder motor)." +msgstr "리트랙션 속도 (익스트루더 모터에만 적용됨)." + +#: xs/src/libslic3r/PrintConfig.cpp:1390 xs/src/libslic3r/PrintConfig.cpp:1391 +msgid "Deretraction Speed" +msgstr "감속 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1392 +msgid "The speed for loading of a filament into extruder after retraction (it only applies to the extruder motor). If left to zero, the retraction speed is used." +msgstr "리트랙션 후 압출기에 필라멘트를 로드하는 속도 (압출기 모터에만 적용됨). 0으로 방치하면 리트랙션 속도가 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1399 +msgid "Seam position" +msgstr "재봉선 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1401 +msgid "Position of perimeters starting points." +msgstr "둘레의 시작점의 위치." + +#: xs/src/libslic3r/PrintConfig.cpp:1408 +msgid "Random" +msgstr "무작위" + +#: xs/src/libslic3r/PrintConfig.cpp:1409 +msgid "Nearest" +msgstr "가장 가까운" + +#: xs/src/libslic3r/PrintConfig.cpp:1410 +msgid "Aligned" +msgstr "정렬" + +#: xs/src/libslic3r/PrintConfig.cpp:1411 lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear" +msgstr "뒷면" + +#: xs/src/libslic3r/PrintConfig.cpp:1417 +msgid "Direction" +msgstr "방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1419 +msgid "Preferred direction of the seam" +msgstr "선호하는 심(seam)의 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1420 +msgid "Seam preferred direction" +msgstr "심(Seam) 선호 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1428 +msgid "Jitter" +msgstr "지터(Jitter)" + +#: xs/src/libslic3r/PrintConfig.cpp:1430 +msgid "Seam preferred direction jitter" +msgstr "(Seam) 선호 방향 지터(Jitter)" + +#: xs/src/libslic3r/PrintConfig.cpp:1431 +msgid "Preferred direction of the seam - jitter" +msgstr "재봉선 지터의 선호 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1442 +msgid "USB/serial port for printer connection." +msgstr "프린터 연결을 위한 USB/시리얼 포트." + +#: xs/src/libslic3r/PrintConfig.cpp:1450 +msgid "Serial port speed" +msgstr "시리얼 포트 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1451 +msgid "Speed (baud) of USB/serial port for printer connection." +msgstr "프린터 연결을 위한 USB/시리얼 포트의 속도(보드)" + +#: xs/src/libslic3r/PrintConfig.cpp:1460 +msgid "Distance from object" +msgstr "객체로부터의 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1461 +msgid "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion." +msgstr "스커트와 객체 사이의 거리. 스커트를 객체에 부착하고 접착력을 높이기 위해 이를 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1469 +msgid "Skirt height" +msgstr "스커트(Skirt) 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:1470 +msgid "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts." +msgstr "스커트의 높이 레이어로 표현된다. 이를 높은 값으로 설정하여 스커트를 드래프트에 대한 쉴ㄷ로 활용하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1477 +msgid "Loops (minimum)" +msgstr "루프 (최소)" + +#: xs/src/libslic3r/PrintConfig.cpp:1478 +msgid "Skirt Loops" +msgstr "스커트 루프" + +#: xs/src/libslic3r/PrintConfig.cpp:1479 +msgid "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely." +msgstr "스커트의 루프 수입니다. 최소 압출 길이 옵션을 설정한 경우 여기에 구성된 루프 수보다 클 수 있다. 스커트를 완전히 비활성화하려면 이 값을 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1487 +msgid "Slow down if layer print time is below" +msgstr "레이어 인쇄 시간이 다음과 같은 경우 속도를 낮추십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1488 +msgid "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value." +msgstr "층 인쇄 시간이 이 시간보다 낮게 추정될 경우, 인쇄 이동 속도는 이 값으로 지속되도록 축소된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1498 +msgid "Small perimeters" +msgstr "작은 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:1500 +msgid "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto." +msgstr "이 개별 설정은 반경이 6.5mm 미만인 속도 (일반적으로 구멍)에 영향을줍니다. 백분율로 표시되는 경우 (예 : 80 %) 위의 속도 설정에서 계산됩니다. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1510 +msgid "Solid infill threshold area" +msgstr "솔리드 채우기 임계값 영역" + +#: xs/src/libslic3r/PrintConfig.cpp:1512 +msgid "Force solid infill for regions having a smaller area than the specified threshold." +msgstr "지정된 한계값보다 작은 영역을 가진 영역에 대해 솔리드 인필을 강제 적용" + +#: xs/src/libslic3r/PrintConfig.cpp:1513 +msgid "mm²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1519 +msgid "Solid infill extruder" +msgstr "솔리드 인필 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1521 +msgid "The extruder to use when printing solid infill." +msgstr "꽉찬 면을 인쇄할 때 사용하는 익스트루더." + +#: xs/src/libslic3r/PrintConfig.cpp:1527 +msgid "Solid infill every" +msgstr "솔리드 인필 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1529 +msgid "This feature allows to force a solid layer every given number of layers. Zero to disable. You can set this to any value (for example 9999); Slic3r will automatically choose the maximum possible number of layers to combine according to nozzle diameter and layer height." +msgstr "이 특징은 주어진 개수의 층마다 단단한 층을 강요할 수 있게 한다. 비활성화할 수 없음. 당신은 이것을 어떤 값으로도 설정할 수 있다(예: 9999). Slic3r는 노즐 직경과 층 높이에 따라 결합할 최대 가능한 층 수를 자동으로 선택한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1539 xs/src/libslic3r/PrintConfig.cpp:1549 +#: xs/src/libslic3r/GCode/PreviewData.cpp:167 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:94 +msgid "Solid infill" +msgstr "솔리드 인필" + +#: xs/src/libslic3r/PrintConfig.cpp:1541 +msgid "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하여 솔리드 표면 인필에 대한 수동 압출 폭을 설정하십시오. 0인 경우 기본 압출 너비가 사용되며, 그렇지 않으면 1.125 x 노즐 직경이 사용된다. 백분율(예: 90%)로 표현되는 경우, 계층 높이에 걸쳐 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1551 +msgid "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto." +msgstr "솔리드 영역(상단/하부/내부 수평 셸) 인쇄 속도 이는 위의 기본 주입 속도에 대한 백분율(예: 80%)로 표시할 수 있다. 자동을 위해 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1563 +msgid "Number of solid layers to generate on top and bottom surfaces." +msgstr "상단 및 하단 표면에 생성할 솔리드 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1570 +msgid "Spiral vase" +msgstr "스파이럴 바이스" + +#: xs/src/libslic3r/PrintConfig.cpp:1571 +msgid "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object." +msgstr "이 기능은 단일 벽 물체를 인쇄하는 동안 눈에 보이는 심을 제거하기 위해 Z를 점진적으로 상승시킨다. 이 옵션은 단일 둘레, 주입, 상단 솔리드 레이어 및 지지 재료가 필요하지 않다. 당신은 스커트/브림 루프뿐만 아니라 아래 솔리드 레이어의 수에 상관없이 설정할 수 있다. 그것은 물체보다 더 많이 인쇄할 때는 작동하지 않을 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1580 +msgid "Temperature variation" +msgstr "온도 변화" + +#: xs/src/libslic3r/PrintConfig.cpp:1581 +msgid "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped." +msgstr "돌출부가 활성화되지 않은 경우 적용되는 온도 차이. 노즐을 주기적으로 닦는 전체 높이 \"인공\" 스커트가 가능하다." + +#: xs/src/libslic3r/PrintConfig.cpp:1591 +msgid "This start procedure is inserted at the beginning, after bed has reached the target temperature and extruder just started heating, and before extruder has finished heating. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." +msgstr "이 시작 절차는 침대가 목표 온도에 도달하고 압출기가 막 가열을 시작한 직후 및 압출기가 가열을 완료하기 전에 처음에 삽입됩니다. Slic3r이 사용자 지정 코드에서 M104 또는 M190을 감지하면 이러한 명령은 자동으로 추가되지 않으므로 가열 명령 및 기타 사용자 지정 동작의 순서를 자유롭게 사용자 지정할 수 있습니다. 모든 Slic3r 설정에 자리 표시 자 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1606 +msgid "This start procedure is inserted at the beginning, after any printer start gcode. This is used to override settings for a specific filament. If Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want. If you have multiple extruders, the gcode is processed in extruder order." +msgstr "이 시작 절차는 프린터가 gcode를 시작한 후 처음에 삽입됩니다. 특정 필라멘트의 설정을 무시하는 데 사용됩니다. Slic3r이 사용자 지정 코드에서 M104, M109, M140 또는 M190을 감지하면 이러한 명령은 자동으로 추가되지 않으므로 가열 명령 및 기타 사용자 지정 동작의 순서를 자유롭게 사용자 지정할 수 있습니다. 모든 Slic3r 설정에 자리 표시 자 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다. 여러 개의 압출기가있는 경우 gcode가 압출기 순서로 처리됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1621 +msgid "Single Extruder Multi Material" +msgstr "싱글 익스트루더 멀티메터리얼" + +#: xs/src/libslic3r/PrintConfig.cpp:1622 +msgid "The printer multiplexes filaments into a single hot end." +msgstr "프린터는 필라멘트를 하나의 핫 엔드에 멀티플렉싱합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1627 +msgid "Generate support material" +msgstr "서포트 재료 생성" + +#: xs/src/libslic3r/PrintConfig.cpp:1629 +msgid "Enable support material generation." +msgstr "서포트 재료를 사용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1634 +msgid "XY separation between an object and its support" +msgstr "물체와 그 서포트 사이 XY 분리" + +#: xs/src/libslic3r/PrintConfig.cpp:1636 +msgid "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width." +msgstr "객체와 그 서포트 사이의 XY 분리. 백분율 (예 : 50 %)로 표시되는 경우 외부 둘레 너비를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1646 +msgid "Pattern angle" +msgstr "채움 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1648 +msgid "Use this setting to rotate the support material pattern on the horizontal plane." +msgstr "이 설정을 사용하여지지 평면 패턴을 수평면으로 회전시킵니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1658 +msgid "Only create support if it lies on a build plate. Don't create support on a print." +msgstr "그것이 빌드 플레이트에있는 경우에만 지원을 작성하십시오. 인쇄물에 대한 지원을 작성하지 마십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1664 +msgid "Contact Z distance" +msgstr "Z 거리 문의" + +#: xs/src/libslic3r/PrintConfig.cpp:1666 +msgid "The vertical distance between object and support material interface. Setting this to 0 will also prevent Slic3r from using bridge flow and speed for the first object layer." +msgstr "물체와 서포트 사이의 수직 거리. 이 값을 0으로 설정하면 Slic3r이 첫 번째 객체 레이어에 브리지 흐름과 속도를 사용하지 못하게됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1674 +msgid "soluble" +msgstr "수용성" + +#: xs/src/libslic3r/PrintConfig.cpp:1675 +msgid "detachable" +msgstr "분리 가능" + +#: xs/src/libslic3r/PrintConfig.cpp:1679 +msgid "Enforce support for the first" +msgstr "첫 번째 서포트 더 강화" + +#: xs/src/libslic3r/PrintConfig.cpp:1681 +msgid "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate." +msgstr "일반지지 소재의 활성화 여부와 관계없이 각도 임계 값에 관계없이 하단에서부터 세어 지정된 레이어 수에 대한지지 자료를 생성합니다. 이것은 빌드 플레이트에 매우 얇거나 부족한 풋 프린트를 가진 물체를 더 많이 부착 할 때 유용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1687 +msgid "Enforce support for the first n layers" +msgstr "첫 번째 n 개의 레이어에 대한 서포트 강화" + +#: xs/src/libslic3r/PrintConfig.cpp:1692 +msgid "Support material/raft/skirt extruder" +msgstr "서포트 재료 / 라프트 / 스커트 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1694 +msgid "The extruder to use when printing support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes)." +msgstr "서포트 재료, 라프트 및 스커트를 인쇄 할 때 사용하는 압출기 (도구 변경을 최소화하기 위해 현재 압출기를 사용하려면 1+, 0)." + +#: xs/src/libslic3r/PrintConfig.cpp:1703 +msgid "Set this to a non-zero value to set a manual extrusion width for support material. If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "서포트 재료의 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정하십시오. 0으로 설정하면 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 노즐 지름이 사용됩니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1711 +msgid "Interface loops" +msgstr "인터페이스 루프" + +#: xs/src/libslic3r/PrintConfig.cpp:1713 +msgid "Cover the top contact layer of the supports with loops. Disabled by default." +msgstr "지지대의 상단 접촉 층을 루프로 덮으십시오. 기본적으로 사용 안 함." + +#: xs/src/libslic3r/PrintConfig.cpp:1718 +msgid "Support material/raft interface extruder" +msgstr "서포트 재료/라프트 인터페이스 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1720 +msgid "The extruder to use when printing support material interface (1+, 0 to use the current extruder to minimize tool changes). This affects raft too." +msgstr "서포트 재료 인터페이스를 인쇄 할 때 사용할 익스트루더 (도구 변경을 최소화하기 위해 현재 익스트루더를 사용하려면 1+, 0). 이것은 라프트에도 영향을 미칩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1727 +msgid "Interface layers" +msgstr "인터페이스 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1729 +msgid "Number of interface layers to insert between the object(s) and support material." +msgstr "객체와 서포트 재료 사이에 삽입할 인터페이스 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1736 +msgid "Interface pattern spacing" +msgstr "인터페이스 패턴 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1738 +msgid "Spacing between interface lines. Set zero to get a solid interface." +msgstr "인터페이스 라인 간 간격. 솔리드 인터페이스를 가져오려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1745 +#: xs/src/libslic3r/GCode/PreviewData.cpp:173 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:100 +msgid "Support material interface" +msgstr "서포트 재료 인터페이스" + +#: xs/src/libslic3r/PrintConfig.cpp:1747 +msgid "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed." +msgstr "서포트 재료 인터페이스 레이어 인쇄 속도 백분율(예: 50%)로 표현될 경우 서포트 재료 속도에 따라 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1756 +msgid "Pattern" +msgstr "패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:1758 +msgid "Pattern used to generate support material." +msgstr "서포트 재료를 생성하는 데 사용되는 패턴." + +#: xs/src/libslic3r/PrintConfig.cpp:1765 +msgid "Rectilinear grid" +msgstr "직선 그리드" + +#: xs/src/libslic3r/PrintConfig.cpp:1770 +msgid "Pattern spacing" +msgstr "패턴 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1772 +msgid "Spacing between support material lines." +msgstr "서포트 재료 라인 사이의 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1781 +msgid "Speed for printing support material." +msgstr "서포트 재료를 인쇄하는 속도." + +#: xs/src/libslic3r/PrintConfig.cpp:1788 +msgid "Synchronize with object layers" +msgstr "객체 레이어와 동기화" + +#: xs/src/libslic3r/PrintConfig.cpp:1790 +msgid "Synchronize support layers with the object print layers. This is useful with multi-material printers, where the extruder switch is expensive." +msgstr "서포트 레이어를 프린트 레이어와 동기화하십시오. 이것은 스위치가 비싼 멀티 메터리얼 프린터에서 유용하다." + +#: xs/src/libslic3r/PrintConfig.cpp:1796 +msgid "Overhang threshold" +msgstr "오버행 한계점" + +#: xs/src/libslic3r/PrintConfig.cpp:1798 +msgid "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)." +msgstr "서포트 재료는 경사각(90° = 수직)이 지정된 임계점보다 높은 압출에 대해서는 생성되지 않는다. 즉, 이 값은 서포트 재료 없이 인쇄할 수 있는 가장 수평 경사(수평면에서 측정됨)를 나타낸다. 자동 감지를 위해 0으로 설정하십시오(권장)." + +#: xs/src/libslic3r/PrintConfig.cpp:1810 +msgid "With sheath around the support" +msgstr "서포트 주변이나 외부로" + +#: xs/src/libslic3r/PrintConfig.cpp:1812 +msgid "Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove." +msgstr "기본 서포트 주위에 외장 (단일 주변 선)을 추가하십시오. 이것은 페이스 업을보다 신뢰성있게 만들뿐만 아니라 제거하기도 어렵습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1819 +msgid "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output." +msgstr "첫 번째 것 이후에 레이어에 대한 더 낮은 온도. 이 값을 0으로 설정하면 출력에서 ​​온도 제어 명령을 비활성화 할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1822 +msgid "Temperature" +msgstr "온도 " + +#: xs/src/libslic3r/PrintConfig.cpp:1828 +msgid "Detect thin walls" +msgstr "얇은 벽(walls) 감지" + +#: xs/src/libslic3r/PrintConfig.cpp:1830 +msgid "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)." +msgstr "싱글 너비 벽 (두 부분이 맞지 않는 부분과 무너지는 부분)을 감지합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1836 +msgid "Threads" +msgstr "스레드(Threads)" + +#: xs/src/libslic3r/PrintConfig.cpp:1837 +msgid "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors." +msgstr "스레드는 장기 실행 태스크를 병렬 처리하는 데 사용됩니다. 최적의 스레드 수는 사용 가능한 코어 / 프로세서 수보다 약간 높습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1849 +msgid "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]." +msgstr "이 사용자 정의 코드는 모든 압출기 변경 직전에 삽입됩니다. [previous_extruder] 및 [next_extruder]뿐 아니라 모든 Slic3r 설정에 대해 자리 표시 자 변수를 사용할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1859 xs/src/libslic3r/PrintConfig.cpp:1870 +#: xs/src/libslic3r/GCode/PreviewData.cpp:168 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:95 +msgid "Top solid infill" +msgstr "가장 윗부분 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:1861 +msgid "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하여 상단 서피스에 대한 infill의 수동 압출 폭을 설정합니다. 얇은 압출 성형물을 사용하여 모든 좁은 지역을 채우고 더 매끄러운 마무리를 할 수 있습니다. 0으로 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 노즐 지름이 사용됩니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1872 +msgid "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto." +msgstr "상단 솔리드 레이어 인쇄 속도 (솔리드 레이어가 아닌 최상단 외부 레이어에만 적용) 표면을 더 좋게 마무리하려면 속도를 늦추시기 바랍니다. 이것은 위의 고체 충전 속도에 대한 백분율 (예 : 80 %)로 나타낼 수 있습니다. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1884 lib/Slic3r/GUI/MainFrame.pm:327 +msgid "Top" +msgstr "윗부분" + +#: xs/src/libslic3r/PrintConfig.cpp:1886 +msgid "Number of solid layers to generate on top surfaces." +msgstr "상단 표면에 생성 할 솔리드 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1888 +msgid "Top solid layers" +msgstr "탑 솔리드 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1893 lib/Slic3r/GUI/Plater/3DPreview.pm:105 +msgid "Travel" +msgstr "이송" + +#: xs/src/libslic3r/PrintConfig.cpp:1894 +msgid "Speed for travel moves (jumps between distant extrusion points)." +msgstr "이동 속도 (먼 돌출 점 사이의 점프)." + +#: xs/src/libslic3r/PrintConfig.cpp:1902 +msgid "Use firmware retraction" +msgstr "펌웨어 철회" + +#: xs/src/libslic3r/PrintConfig.cpp:1903 +msgid "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin." +msgstr "이 실험 설정은 G10 및 G11 명령을 사용하여 펌웨어에서 취소를 처리하도록합니다. 이것은 최근의 말린에서만 지원됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1909 +msgid "Use relative E distances" +msgstr "상대적인 E 거리 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1910 +msgid "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values." +msgstr "펌웨어에 상대 E 값이 필요한 경우이 값을 선택하고, 그렇지 않으면 선택하지 마십시오. 대부분의 회사는 절대 값을 사용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1916 +msgid "Use volumetric E" +msgstr "용적 E 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1917 +msgid "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. If your firmware doesn't already know filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' in your start G-code in order to turn volumetric mode on and use the filament diameter associated to the filament selected in Slic3r. This is only supported in recent Marlin." +msgstr "이 실험 설정은 선형 밀리미터 대신에 입방 밀리미터 단위의 E 값을 출력으로 사용합니다. 펌웨어가 필라멘트 직경을 모르는 경우 볼륨 모드를 켜고 선택한 필라멘트와 연결된 필라멘트 직경을 사용하기 위해 시작 G 코드에 'M200 D [filament_diameter_0] T0'과 같은 명령을 입력 할 수 있습니다 Slic3r. 이것은 최근의 말린에서만 지원됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1927 +msgid "Enable variable layer height feature" +msgstr "가변 레이어 높이 기능 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1928 +msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." +msgstr "일부 프린터 또는 프린터 설정은 가변 레이어 높이로 인쇄하는 데 어려움이있을 수 있습니다. 기본적으로 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1934 +msgid "Wipe while retracting" +msgstr "수축시 닦아내십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1935 +msgid "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders." +msgstr "이 플래그는 누출된 리트랙싱의 블럽 가능성을 최소화하기 위해 수축하는 동안 노즐을 이동시킨다." + +#: xs/src/libslic3r/PrintConfig.cpp:1942 +msgid "Multi material printers may need to prime or purge extruders on tool changes. Extrude the excess material into the wipe tower." +msgstr "멀티 메터리알 프린터는 공구 교환 시 익스트루더를 프라이밍하거나 제거해야 할 수 있다. 과도한 물질을 와이퍼 타워에 돌출시킨다." + +#: xs/src/libslic3r/PrintConfig.cpp:1948 +msgid "Purging volumes - load/unload volumes" +msgstr "볼륨 삭제 - 볼륨 로드/언로드" + +#: xs/src/libslic3r/PrintConfig.cpp:1949 +msgid "This vector saves required volumes to change from/to each tool used on the wipe tower. These values are used to simplify creation of the full purging volumes below. " +msgstr "이 벡터는 와이퍼 작동 타워에 사용되는 각 공구와 교환하는 데 필요한 볼륨을 저장한다. 이러한 값은 아래 전체 삭제 볼륨 생성을 단순화하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1956 +msgid "Purging volumes - matrix" +msgstr "볼륨 삭제 - 행렬" + +#: xs/src/libslic3r/PrintConfig.cpp:1957 +msgid "This matrix describes volumes (in cubic milimetres) required to purge the new filament on the wipe tower for any given pair of tools. " +msgstr "이 매트릭스는 주어진 공구 쌍에 대해 새 필라멘트를 지우는 데 필요한 볼륨(입방 밀리미터)을 설명한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1967 +msgid "Position X" +msgstr "X축 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1968 +msgid "X coordinate of the left front corner of a wipe tower" +msgstr "와이프 타워의 좌측 전면 모서리의 X 좌표" + +#: xs/src/libslic3r/PrintConfig.cpp:1974 +msgid "Position Y" +msgstr "Y축 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1975 +msgid "Y coordinate of the left front corner of a wipe tower" +msgstr "와이퍼 작동 타워의 좌측 전방 모서리의 Y 좌표" + +#: xs/src/libslic3r/PrintConfig.cpp:1981 lib/Slic3r/GUI/Plater/3DPreview.pm:76 +msgid "Width" +msgstr "폭" + +#: xs/src/libslic3r/PrintConfig.cpp:1982 +msgid "Width of a wipe tower" +msgstr "와이퍼 타워 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:1988 +msgid "Wipe tower rotation angle" +msgstr "와이퍼 타워 회전각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1989 +msgid "Wipe tower rotation angle with respect to x-axis " +msgstr "X 축에 대한 와이퍼 타워 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1990 +msgid "degrees" +msgstr "각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1996 +msgid "Purging into infill" +msgstr "인필로 제거" + +#: xs/src/libslic3r/PrintConfig.cpp:1997 +msgid "Wiping after toolchange will be preferentially done inside infills. This lowers the amount of waste but may result in longer print time due to additional travel moves." +msgstr "공구 교체 후 닦는 작업은 우선적으로 인필 내부에서 수행됩니다. 이렇게하면 낭비되는 양은 줄어들지만 추가 이동으로 인해 인쇄 시간이 길어질 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:2005 +msgid "Purging into objects" +msgstr "객체로 제거" + +#: xs/src/libslic3r/PrintConfig.cpp:2006 +msgid "Objects will be used to wipe the nozzle after a toolchange to save material that would otherwise end up in the wipe tower and decrease print time. Colours of the objects will be mixed as a result." +msgstr "도구 교환 후 노즐을 닦아내면 닦아내는 재료가 절약되어 인쇄 시간이 단축됩니다. 결과적으로 오브젝트의 색상이 혼합됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:2013 +msgid "Maximal bridging distance" +msgstr "최대 브리징 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:2014 +msgid "Maximal distance between supports on sparse infill sections. " +msgstr "드문드문하 인필 섹션에서 지지대 사이의 최대 거리." + +#: xs/src/libslic3r/PrintConfig.cpp:2020 +msgid "XY Size Compensation" +msgstr "XY 크기 보정" + +#: xs/src/libslic3r/PrintConfig.cpp:2022 +msgid "The object will be grown/shrunk in the XY plane by the configured value (negative = inwards, positive = outwards). This might be useful for fine-tuning hole sizes." +msgstr "XY 평면에서 설정된 값(음수 = 안, 양 = 바깥쪽)에 따라 객체가 증가/정격된다. 이는 구멍 크기를 미세 조정하는데 유용할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:2030 +msgid "Z offset" +msgstr "Z 오프셋" + +#: xs/src/libslic3r/PrintConfig.cpp:2031 +msgid "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop)." +msgstr "이 값은 출력 G-코드의 모든 Z 좌표에서 추가(또는 감산)된다. 예를 들어, 엔드 스톱 0이 실제로 노즐을 프린트 베드에서 0.3mm 떨어진 곳에 둔 경우, 이를 -0.3(또는 엔드 스톱을 고정)으로 설정하십시오." + +#: xs/src/libslic3r/GCode/PreviewData.cpp:163 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:90 +msgid "Perimeter" +msgstr "가장자리" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:164 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:91 +msgid "External perimeter" +msgstr "외부 가장자리" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:165 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:92 +msgid "Overhang perimeter" +msgstr "오버행(Overhang) 둘레" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:166 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:93 +msgid "Internal infill" +msgstr "내부 채움" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:169 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:96 +msgid "Bridge infill" +msgstr "프릿지 채움" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:176 +msgid "Mixed" +msgstr "뒤석음" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:367 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:74 +msgid "Feature type" +msgstr "특색 유형" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:369 +msgid "Height (mm)" +msgstr "높이 (mm)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:371 +msgid "Width (mm)" +msgstr "폭 (mm)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:373 +msgid "Speed (mm/s)" +msgstr "속도 (mm/s)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:375 +msgid "Volumetric flow rate (mm3/s)" +msgstr "용적 유량값 (mm3/s)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:377 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:79 +msgid "Tool" +msgstr "도구" + +#: lib/Slic3r/GUI.pm:308 +msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "파일을 선택하세요 (STL/OBJ/AMF/3MF/PRUSA):" + +#: lib/Slic3r/GUI/MainFrame.pm:66 +msgid "Version " +msgstr "버전" + +#: lib/Slic3r/GUI/MainFrame.pm:66 +msgid " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" +msgstr " -http://github.com/prusa3d/slic3r/releases에서 업데이트를 확인하는 것을 잊지 마십시오" + +#: lib/Slic3r/GUI/MainFrame.pm:135 +msgid "Plater" +msgstr "플레이트" + +#: lib/Slic3r/GUI/MainFrame.pm:137 +msgid "Controller" +msgstr "컨트롤러" + +#: lib/Slic3r/GUI/MainFrame.pm:215 +msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" +msgstr "오픈 STL/OBJ/AMF/3MF…\tCtrl+O" + +#: lib/Slic3r/GUI/MainFrame.pm:215 +msgid "Open a model" +msgstr "오픈 모델" + +#: lib/Slic3r/GUI/MainFrame.pm:218 +msgid "&Load Config…\tCtrl+L" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:218 +msgid "Load exported configuration file" +msgstr "내 보낸 구성 파일로드" + +#: lib/Slic3r/GUI/MainFrame.pm:221 +msgid "&Export Config…\tCtrl+E" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:221 +msgid "Export current configuration to file" +msgstr "현재 구성을 파일로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:224 +msgid "&Load Config Bundle…" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:224 +msgid "Load presets from a bundle" +msgstr "번들에서 미리 설정로드" + +#: lib/Slic3r/GUI/MainFrame.pm:227 +msgid "&Export Config Bundle…" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:227 +msgid "Export all presets to file" +msgstr "모든 이전 설정을 파일로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:232 +msgid "Q&uick Slice…\tCtrl+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:232 +msgid "Slice a file into a G-code" +msgstr "파일을 G 코드로 분할" + +#: lib/Slic3r/GUI/MainFrame.pm:238 +msgid "Quick Slice and Save &As…\tCtrl+Alt+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:238 +msgid "Slice a file into a G-code, save as" +msgstr "파일을 G 코드로 분할하고 다음으로 저장" + +#: lib/Slic3r/GUI/MainFrame.pm:244 +msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:244 +msgid "Repeat last quick slice" +msgstr "마지막으로 빠른 슬라이스 반복" + +#: lib/Slic3r/GUI/MainFrame.pm:251 +msgid "Slice to SV&G…\tCtrl+G" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:251 +msgid "Slice file to a multi-layer SVG" +msgstr "파일을 다중 레이어 SVG로 슬라이스" + +#: lib/Slic3r/GUI/MainFrame.pm:255 +msgid "(&Re)Slice Now\tCtrl+S" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:255 +msgid "Start new slicing process" +msgstr "새로운 슬라이싱 작업 시작" + +#: lib/Slic3r/GUI/MainFrame.pm:258 +msgid "Repair STL file…" +msgstr "STL 파일 복구 ..." + +#: lib/Slic3r/GUI/MainFrame.pm:258 +msgid "Automatically repair an STL file" +msgstr "STL 파일을 자동으로 복구합니다." + +#: lib/Slic3r/GUI/MainFrame.pm:262 +msgid "&Quit" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:262 +msgid "Quit Slic3r" +msgstr "Slic3r 종료" + +#: lib/Slic3r/GUI/MainFrame.pm:272 +msgid "Export G-code..." +msgstr "G-코드 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:272 +msgid "Export current plate as G-code" +msgstr "현재 플레이트를 G 코드로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:275 +msgid "Export plate as STL..." +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:275 +msgid "Export current plate as STL" +msgstr "현재 플레이트를 STL로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:278 +msgid "Export plate as AMF..." +msgstr "AMF로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:278 +msgid "Export current plate as AMF" +msgstr "현재 플레이트를AMF로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:281 +msgid "Export plate as 3MF..." +msgstr "3MF로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:281 +msgid "Export current plate as 3MF" +msgstr "현재 플레이트를 3MF로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:294 +msgid "Select &Plater Tab\tCtrl+1" +msgstr "선택 및 플래이트 탭 Ctrl + 1" + +#: lib/Slic3r/GUI/MainFrame.pm:294 +msgid "Show the plater" +msgstr "플레이트를 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:300 +msgid "Select &Controller Tab\tCtrl+T" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:300 +msgid "Show the printer controller" +msgstr "프린터 컨트롤러 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:308 +msgid "Select P&rint Settings Tab\tCtrl+2" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:308 +msgid "Show the print settings" +msgstr "인쇄 설정 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:311 +msgid "Select &Filament Settings Tab\tCtrl+3" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:311 +msgid "Show the filament settings" +msgstr "필라멘트 설정보기" + +#: lib/Slic3r/GUI/MainFrame.pm:314 +msgid "Select Print&er Settings Tab\tCtrl+4" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:314 +msgid "Show the printer settings" +msgstr "간단한 설정보기" + +#: lib/Slic3r/GUI/MainFrame.pm:326 +msgid "Iso" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:326 +msgid "Iso View" +msgstr "Iso 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:327 +msgid "Top View" +msgstr "위에서 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:328 +msgid "Bottom View" +msgstr "바닥 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:329 +msgid "Front" +msgstr "앞" + +#: lib/Slic3r/GUI/MainFrame.pm:329 +msgid "Front View" +msgstr "앞면 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear View" +msgstr "뒷면 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:331 +msgid "Left" +msgstr "왼쪽" + +#: lib/Slic3r/GUI/MainFrame.pm:331 +msgid "Left View" +msgstr "왼쪽 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:332 +msgid "Right" +msgstr "오른쪽" + +#: lib/Slic3r/GUI/MainFrame.pm:332 +msgid "Right View" +msgstr "오른쪽 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:338 +msgid "Prusa 3D Drivers" +msgstr "푸르사 3D 드라이버" + +#: lib/Slic3r/GUI/MainFrame.pm:338 +msgid "Open the Prusa3D drivers download page in your browser" +msgstr "브라우저에서 Prusa3D 드라이버 다운로드 페이지를 엽니다." + +#: lib/Slic3r/GUI/MainFrame.pm:341 +msgid "Prusa Edition Releases" +msgstr "Prusa 에디션 릴리스" + +#: lib/Slic3r/GUI/MainFrame.pm:341 +msgid "Open the Prusa Edition releases page in your browser" +msgstr "브라우저에서 Prusa Edition 릴리즈 페이지를 엽니 다." + +#: lib/Slic3r/GUI/MainFrame.pm:348 +msgid "Slic3r &Website" +msgstr "Slic3r 및 웹 사이트" + +#: lib/Slic3r/GUI/MainFrame.pm:348 +msgid "Open the Slic3r website in your browser" +msgstr "브라우저에서 Slic3r 웹 사이트 열기" + +#: lib/Slic3r/GUI/MainFrame.pm:351 +msgid "Slic3r &Manual" +msgstr "Slic3r &메뉴얼" + +#: lib/Slic3r/GUI/MainFrame.pm:351 +msgid "Open the Slic3r manual in your browser" +msgstr "브라우저에서 Slic3r 설명서를 엽니다." + +#: lib/Slic3r/GUI/MainFrame.pm:355 +msgid "System Info" +msgstr "시스템 정보" + +#: lib/Slic3r/GUI/MainFrame.pm:355 +msgid "Show system information" +msgstr "시스템 정보 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show &Configuration Folder" +msgstr "폴더 표시 및 구성" + +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show user configuration folder (datadir)" +msgstr "사용자 구성 폴더 표시 (datadir)" + +#: lib/Slic3r/GUI/MainFrame.pm:361 +msgid "Report an Issue" +msgstr "문제보고" + +#: lib/Slic3r/GUI/MainFrame.pm:361 +msgid "Report an issue on the Slic3r Prusa Edition" +msgstr "Slic3r Prusa Edition에 관한 문제점 보고" + +#: lib/Slic3r/GUI/MainFrame.pm:364 +msgid "&About Slic3r" +msgstr "&Slic3r에 대하여" + +#: lib/Slic3r/GUI/MainFrame.pm:364 +msgid "Show about dialog" +msgstr "대화상자 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:374 +msgid "&File" +msgstr "&파일" + +#: lib/Slic3r/GUI/MainFrame.pm:375 +msgid "&Plater" +msgstr "&플레이트" + +#: lib/Slic3r/GUI/MainFrame.pm:376 +msgid "&Object" +msgstr "&객체" + +#: lib/Slic3r/GUI/MainFrame.pm:377 +msgid "&Window" +msgstr "&윈도우" + +#: lib/Slic3r/GUI/MainFrame.pm:378 +msgid "&View" +msgstr "&보다" + +#: lib/Slic3r/GUI/MainFrame.pm:381 +msgid "&Help" +msgstr "&도움말" + +#: lib/Slic3r/GUI/MainFrame.pm:412 +msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "슬라이스 할 파일을 선택하십시오 (STL / OBJ / AMF / 3MF / PRUSA):" + +#: lib/Slic3r/GUI/MainFrame.pm:424 +msgid "No previously sliced file." +msgstr "이전에 분리 된 파일이 없습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:425 lib/Slic3r/GUI/Plater.pm:1405 +msgid "Error" +msgstr "에러" + +#: lib/Slic3r/GUI/MainFrame.pm:429 +msgid "Previously sliced file (" +msgstr "이전에 분리 된 파일 (" + +#: lib/Slic3r/GUI/MainFrame.pm:429 +msgid ") not found." +msgstr ")을 찾을 수 없습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:430 +msgid "File Not Found" +msgstr "파일을 찾을수 없다" + +#: lib/Slic3r/GUI/MainFrame.pm:469 +msgid "SVG" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:469 +msgid "G-code" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:469 lib/Slic3r/GUI/Plater.pm:1795 +msgid " file as:" +msgstr " 다음 파일 :" + +#: lib/Slic3r/GUI/MainFrame.pm:483 +msgid "Slicing…" +msgstr "슬라이싱..." + +#: lib/Slic3r/GUI/MainFrame.pm:483 +msgid "Processing " +msgstr "프로세싱" + +#: lib/Slic3r/GUI/MainFrame.pm:503 +msgid " was successfully sliced." +msgstr "성공적으로 슬라이스." + +#: lib/Slic3r/GUI/MainFrame.pm:505 +msgid "Slicing Done!" +msgstr "슬라이스 완료!" + +#: lib/Slic3r/GUI/MainFrame.pm:521 +msgid "Select the STL file to repair:" +msgstr "복구 할 STL 파일을 선택하십시오." + +#: lib/Slic3r/GUI/MainFrame.pm:535 +msgid "Save OBJ file (less prone to coordinate errors than STL) as:" +msgstr "OBJ 파일을 저장하십시오 (STL보다 오류를 덜 조정할 가능성이 적음)." + +#: lib/Slic3r/GUI/MainFrame.pm:549 +msgid "Your file was repaired." +msgstr "파일이 복구되었습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:549 +msgid "Repair" +msgstr "수정" + +#: lib/Slic3r/GUI/MainFrame.pm:560 +msgid "Save configuration as:" +msgstr "구성을 저장 :" + +#: lib/Slic3r/GUI/MainFrame.pm:578 lib/Slic3r/GUI/MainFrame.pm:622 +msgid "Select configuration to load:" +msgstr "로드 할 구성 선택 :" + +#: lib/Slic3r/GUI/MainFrame.pm:601 +msgid "Save presets bundle as:" +msgstr "이전 설정 번들을 다음과 같이 저장 :" + +#: lib/Slic3r/GUI/MainFrame.pm:642 +#, perl-format +msgid "%d presets successfully imported." +msgstr "% d 사전 설정을 가져 왔습니다." + +#: lib/Slic3r/GUI/Plater.pm:164 lib/Slic3r/GUI/Plater.pm:2323 +msgid "3D" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:206 +msgid "2D" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:224 +msgid "Layers" +msgstr "레이어" + +#: lib/Slic3r/GUI/Plater.pm:250 lib/Slic3r/GUI/Plater.pm:268 +msgid "Add…" +msgstr "추가..." + +#: lib/Slic3r/GUI/Plater.pm:252 lib/Slic3r/GUI/Plater.pm:270 +msgid "Delete All" +msgstr "전부 지움" + +#: lib/Slic3r/GUI/Plater.pm:253 lib/Slic3r/GUI/Plater.pm:271 +msgid "Arrange" +msgstr "정렬" + +#: lib/Slic3r/GUI/Plater.pm:255 +msgid "More" +msgstr "조금 더" + +#: lib/Slic3r/GUI/Plater.pm:256 +msgid "Fewer" +msgstr "적게" + +#: lib/Slic3r/GUI/Plater.pm:258 +msgid "45° ccw" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:259 +msgid "45° cw" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:260 lib/Slic3r/GUI/Plater.pm:276 +msgid "Scale…" +msgstr "크기." + +#: lib/Slic3r/GUI/Plater.pm:261 lib/Slic3r/GUI/Plater.pm:277 +#: lib/Slic3r/GUI/Plater.pm:2293 +msgid "Split" +msgstr "쪼개기" + +#: lib/Slic3r/GUI/Plater.pm:262 lib/Slic3r/GUI/Plater.pm:278 +#: lib/Slic3r/GUI/Plater.pm:2296 +msgid "Cut…" +msgstr "자르기." + +#: lib/Slic3r/GUI/Plater.pm:264 lib/Slic3r/GUI/Plater.pm:279 +#: lib/Slic3r/GUI/Plater.pm:2300 +msgid "Settings…" +msgstr "설정." + +#: lib/Slic3r/GUI/Plater.pm:265 +msgid "Layer Editing" +msgstr "레이어 편집" + +#: lib/Slic3r/GUI/Plater.pm:280 +msgid "Layer editing" +msgstr "레이어 편집" + +#: lib/Slic3r/GUI/Plater.pm:303 +msgid "Name" +msgstr "이름" + +#: lib/Slic3r/GUI/Plater.pm:304 lib/Slic3r/GUI/Plater.pm:992 +msgid "Copies" +msgstr "사본" + +#: lib/Slic3r/GUI/Plater.pm:305 lib/Slic3r/GUI/Plater.pm:1158 +#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:2262 +msgid "Scale" +msgstr "크기" + +#: lib/Slic3r/GUI/Plater.pm:322 +msgid "Export G-code…" +msgstr "G-코드 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:323 +msgid "Slice now" +msgstr "지금 자르기" + +#: lib/Slic3r/GUI/Plater.pm:324 +msgid "Print…" +msgstr "프린트..." + +#: lib/Slic3r/GUI/Plater.pm:325 +msgid "Send to printer" +msgstr "프린터로 보내기" + +#: lib/Slic3r/GUI/Plater.pm:326 +msgid "Export STL…" +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:453 +msgid "Print settings" +msgstr "프린트 설정" + +#: lib/Slic3r/GUI/Plater.pm:455 +msgid "Printer" +msgstr "프린터" + +#: lib/Slic3r/GUI/Plater.pm:488 +msgid "Info" +msgstr "정보" + +#: lib/Slic3r/GUI/Plater.pm:499 +msgid "Volume" +msgstr "크기" + +#: lib/Slic3r/GUI/Plater.pm:500 +msgid "Facets" +msgstr "측면" + +#: lib/Slic3r/GUI/Plater.pm:501 +msgid "Materials" +msgstr "재료" + +#: lib/Slic3r/GUI/Plater.pm:502 +msgid "Manifold" +msgstr "많은" + +#: lib/Slic3r/GUI/Plater.pm:527 +msgid "Sliced Info" +msgstr "슬라이스된 정보" + +#: lib/Slic3r/GUI/Plater.pm:713 +msgid "Loading…" +msgstr "로딩…" + +#: lib/Slic3r/GUI/Plater.pm:713 lib/Slic3r/GUI/Plater.pm:727 +msgid "Processing input file\n" +msgstr "입력 파일 처리\n" + +#: lib/Slic3r/GUI/Plater.pm:750 +msgid "" +"This file contains several objects positioned at multiple heights. Instead of considering them as multiple objects, should I consider\n" +"this file as a single object having multiple parts?\n" +msgstr "" +"이 파일에는 여러 높이에 위치한 여러 객체가 들어 있습니다. 여러 객체로 간주하는 대신,\n" +"이 파일은 여러 부분을 갖는 단일 객체로 보입니까?\n" + +#: lib/Slic3r/GUI/Plater.pm:753 lib/Slic3r/GUI/Plater.pm:770 +msgid "Multi-part object detected" +msgstr "다중 부품 객체가 감지" + +#: lib/Slic3r/GUI/Plater.pm:767 +msgid "" +"Multiple objects were loaded for a multi-material printer.\n" +"Instead of considering them as multiple objects, should I consider\n" +"these files to represent a single object having multiple parts?\n" +msgstr "" +"다중 재료 프린터에 대해 여러 객체가로드되었습니다.\n" +"여러 객체로 간주하는 대신,\n" +"이 파일들은 여러 부분을 갖는 단일 객체를 나타낼 수 있습니까?\n" + +#: lib/Slic3r/GUI/Plater.pm:779 +msgid "Loaded " +msgstr "로드(loaded)" + +#: lib/Slic3r/GUI/Plater.pm:837 +msgid "Your object appears to be too large, so it was automatically scaled down to fit your print bed." +msgstr "개체가 너무 커서 인쇄물에 맞게 자동으로 축소되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:838 +msgid "Object too large?" +msgstr "개체가 너무 큽니까?" + +#: lib/Slic3r/GUI/Plater.pm:992 +msgid "Enter the number of copies of the selected object:" +msgstr "선택한 객체의 사본 수를 입력하십시오 :" + +#: lib/Slic3r/GUI/Plater.pm:1019 +msgid "" +"\n" +"Non-positive value." +msgstr "" +"\n" +"양수가 아닌 값." + +#: lib/Slic3r/GUI/Plater.pm:1020 +msgid "" +"\n" +"Not a numeric value." +msgstr "" +"\n" +"숫자 값이 아닙니다." + +#: lib/Slic3r/GUI/Plater.pm:1021 +msgid "Slic3r Error" +msgstr "Slic3r 에러" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Enter the rotation angle:" +msgstr "회전 각도를 입력하십시오 :" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Rotate around " +msgstr "회전" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Invalid rotation angle entered" +msgstr "잘못된 회전 각도가 입력되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1132 +#, perl-format +msgid "Enter the new size for the selected object (print bed: %smm):" +msgstr "선택한 객체의 새 크기를 입력하십시오 (인쇄 침대 : % smm)." + +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +msgid "Scale along " +msgstr "전체 크기" + +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +#: lib/Slic3r/GUI/Plater.pm:1158 lib/Slic3r/GUI/Plater.pm:1163 +msgid "Invalid scaling value entered" +msgstr "잘못된 배율 값이 입력되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1137 lib/Slic3r/GUI/Plater.pm:1163 +#, no-perl-format +msgid "Enter the scale % for the selected object:" +msgstr "선택한 객체의 비율 %를 입력하십시오." + +#: lib/Slic3r/GUI/Plater.pm:1158 +msgid "Enter the new max size for the selected object:" +msgstr "선택한 객체의 새로운 최대 크기를 입력하십시오." + +#: lib/Slic3r/GUI/Plater.pm:1218 +msgid "The selected object can't be split because it contains more than one volume/material." +msgstr "선택한 객체는 둘 이상의 볼륨 / 재료가 포함되어 있기 때문에 분할 할 수 없습니다." + +#: lib/Slic3r/GUI/Plater.pm:1227 +msgid "The selected object couldn't be split because it contains only one part." +msgstr "선택한 오브젝트는 파트가 하나만 포함되어 있기 때문에 분할 할 수 없습니다." + +#: lib/Slic3r/GUI/Plater.pm:1391 +msgid "Slicing cancelled" +msgstr "슬라이싱 취소됨" + +#: lib/Slic3r/GUI/Plater.pm:1405 +msgid "Another export job is currently running." +msgstr "다른 내보내기 작업이 현재 실행 중입니다." + +#: lib/Slic3r/GUI/Plater.pm:1555 +msgid "File added to print queue" +msgstr "파일이 인쇄 대기열에 추가되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1561 +msgid "G-code file exported to " +msgstr "G 코드 파일을 내보냈습니다." + +#: lib/Slic3r/GUI/Plater.pm:1564 +msgid "Export failed" +msgstr "내보내기 실패" + +#: lib/Slic3r/GUI/Plater.pm:1576 +msgid "OctoPrint upload finished." +msgstr "OctoPrint 업로드가 완료되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1610 +msgid "Used Filament (m)" +msgstr "사용자 필라멘트 (m)" + +#: lib/Slic3r/GUI/Plater.pm:1612 +msgid "Used Filament (mm³)" +msgstr "사용자 필라멘트 (mm³)" + +#: lib/Slic3r/GUI/Plater.pm:1614 +msgid "Used Filament (g)" +msgstr "사용자 필라멘트 (g)" + +#: lib/Slic3r/GUI/Plater.pm:1618 +msgid "Estimated printing time (normal mode)" +msgstr "예상 인쇄 시간 (일반 모드)" + +#: lib/Slic3r/GUI/Plater.pm:1620 +msgid "Estimated printing time (silent mode)" +msgstr "예상 인쇄 시간 (무으 모드)" + +#: lib/Slic3r/GUI/Plater.pm:1659 lib/Slic3r/GUI/Plater.pm:1701 +msgid "STL file exported to " +msgstr "내보낸 STL 파일" + +#: lib/Slic3r/GUI/Plater.pm:1740 +msgid "AMF file exported to " +msgstr "내보낸 AMF 파일" + +#: lib/Slic3r/GUI/Plater.pm:1744 +msgid "Error exporting AMF file " +msgstr "AMF 파일 내보내기 오류" + +#: lib/Slic3r/GUI/Plater.pm:1756 +msgid "3MF file exported to " +msgstr "3MF 파일을 내보냈습니다" + +#: lib/Slic3r/GUI/Plater.pm:1760 +msgid "Error exporting 3MF file " +msgstr "3MF 파일 내보내기 오류" + +#: lib/Slic3r/GUI/Plater.pm:2140 +#, perl-format +msgid "%d (%d shells)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2142 +#, perl-format +msgid "Auto-repaired (%d errors)" +msgstr "오류자동수정 (%d errors)" + +#: lib/Slic3r/GUI/Plater.pm:2147 +#, perl-format +msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" +msgstr "%d 면 고정, %d 모서리 고정, %d 면 제거, %d 면 추가, %d 면 반전, %d 후방 모서리" + +#: lib/Slic3r/GUI/Plater.pm:2152 +msgid "Yes" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2215 +msgid "Remove the selected object" +msgstr "선택한 객체 제거" + +#: lib/Slic3r/GUI/Plater.pm:2218 +msgid "Increase copies" +msgstr "복사본 늘리기" + +#: lib/Slic3r/GUI/Plater.pm:2218 +msgid "Place one more copy of the selected object" +msgstr "선택한 객체를 하나 더 복사합니다." + +#: lib/Slic3r/GUI/Plater.pm:2221 +msgid "Decrease copies" +msgstr "복사본 감소" + +#: lib/Slic3r/GUI/Plater.pm:2221 +msgid "Remove one copy of the selected object" +msgstr "선택한 객체 복사본 하나 삭제" + +#: lib/Slic3r/GUI/Plater.pm:2224 +msgid "Set number of copies…" +msgstr "복사될 수량 설정 ..." + +#: lib/Slic3r/GUI/Plater.pm:2224 +msgid "Change the number of copies of the selected object" +msgstr "선택한 개체의 복사본 수 변경" + +#: lib/Slic3r/GUI/Plater.pm:2228 +msgid "Rotate 45° clockwise" +msgstr "시계 방향으로 45도 회전" + +#: lib/Slic3r/GUI/Plater.pm:2228 +msgid "Rotate the selected object by 45° clockwise" +msgstr "객체를 시계 방향으로 45 ° 회전합니다" + +#: lib/Slic3r/GUI/Plater.pm:2231 +msgid "Rotate 45° counter-clockwise" +msgstr "반 시계 방향으로 45 ° 회전" + +#: lib/Slic3r/GUI/Plater.pm:2231 +msgid "Rotate the selected object by 45° counter-clockwise" +msgstr "객체를 시계 방향으로 45 ° 회전합니다" + +#: lib/Slic3r/GUI/Plater.pm:2236 +msgid "Rotate" +msgstr "회전" + +#: lib/Slic3r/GUI/Plater.pm:2236 +msgid "Rotate the selected object by an arbitrary angle" +msgstr "선택한 객체를 임의의 각도로 회전" + +#: lib/Slic3r/GUI/Plater.pm:2238 +msgid "Around X axis…" +msgstr "X축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2238 +msgid "Rotate the selected object by an arbitrary angle around X axis" +msgstr "선택한 객체를 X 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2241 +msgid "Around Y axis…" +msgstr "Y축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2241 +msgid "Rotate the selected object by an arbitrary angle around Y axis" +msgstr "선택한 객체를 Y 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2244 +msgid "Around Z axis…" +msgstr "Z축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2244 +msgid "Rotate the selected object by an arbitrary angle around Z axis" +msgstr "선택한 객체를 Z 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2249 +msgid "Mirror" +msgstr "반전(Mirror)" + +#: lib/Slic3r/GUI/Plater.pm:2249 +msgid "Mirror the selected object" +msgstr "반전할 객제를 선택" + +#: lib/Slic3r/GUI/Plater.pm:2251 lib/Slic3r/GUI/Plater.pm:2267 +#: lib/Slic3r/GUI/Plater.pm:2283 +msgid "Along X axis…" +msgstr "X 축을 따라..." + +#: lib/Slic3r/GUI/Plater.pm:2251 +msgid "Mirror the selected object along the X axis" +msgstr "선택한 객체를 X 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2254 lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2286 +msgid "Along Y axis…" +msgstr "Y 축을 따라 ..." + +#: lib/Slic3r/GUI/Plater.pm:2254 +msgid "Mirror the selected object along the Y axis" +msgstr "선택한 객체를 Y 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2257 lib/Slic3r/GUI/Plater.pm:2273 +#: lib/Slic3r/GUI/Plater.pm:2289 +msgid "Along Z axis…" +msgstr "Z 축을 따라 ..." + +#: lib/Slic3r/GUI/Plater.pm:2257 +msgid "Mirror the selected object along the Z axis" +msgstr "선택한 객체를 Z 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2262 lib/Slic3r/GUI/Plater.pm:2278 +msgid "Scale the selected object along a single axis" +msgstr "선택한 객체를 단일 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 +msgid "Uniformly…" +msgstr "균등하게..." + +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 +msgid "Scale the selected object along the XYZ axes" +msgstr "선택한 객체를 XYZ 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2267 lib/Slic3r/GUI/Plater.pm:2283 +msgid "Scale the selected object along the X axis" +msgstr "선택한 객체를 X 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2270 lib/Slic3r/GUI/Plater.pm:2286 +msgid "Scale the selected object along the Y axis" +msgstr "선택한 객체를 Y 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2273 lib/Slic3r/GUI/Plater.pm:2289 +msgid "Scale the selected object along the Z axis" +msgstr "선택한 객체를 Z 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2278 +msgid "Scale to size" +msgstr "크기 조정" + +#: lib/Slic3r/GUI/Plater.pm:2293 +msgid "Split the selected object into individual parts" +msgstr "선택한 객체를 개별 부분으로 분할합니다." + +#: lib/Slic3r/GUI/Plater.pm:2296 +msgid "Open the 3D cutting tool" +msgstr "3차원 컷팅 도구 열기" + +#: lib/Slic3r/GUI/Plater.pm:2300 +msgid "Open the object editor dialog" +msgstr "오브젝트 편집 상자 열기" + +#: lib/Slic3r/GUI/Plater.pm:2304 +msgid "Reload from Disk" +msgstr "디스크에서 다시 불러오기" + +#: lib/Slic3r/GUI/Plater.pm:2304 +msgid "Reload the selected file from Disk" +msgstr "디스크에서 다시 불러오기" + +#: lib/Slic3r/GUI/Plater.pm:2307 +msgid "Export object as STL…" +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:2307 +msgid "Export this single object as STL file" +msgstr "이 객체를 STL 파일로 내 보냅니다." + +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "Fix STL through Netfabb" +msgstr "Netfabb를 통해 STL 수정" + +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "Fix the model by sending it to a Netfabb cloud service through Windows 10 API" +msgstr "Windows 10 API를 통해 Netfabb 클라우드 서비스로 보내 모델 수정" + +#: lib/Slic3r/GUI/Plater/2D.pm:131 +msgid "What do you want to print today? ™" +msgstr "오늘 무엇을 인쇄하고 싶습니까? ™" + +#: lib/Slic3r/GUI/Plater/2D.pm:132 +msgid "Drag your objects here" +msgstr "개체를 여기로 드래그하십시오" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:69 +msgid "1 Layer" +msgstr "1레이어" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:71 +msgid "View" +msgstr "View" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:78 +msgid "Volumetric flow rate" +msgstr "용적의 유량값" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:85 +msgid "Show" +msgstr "보다" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:88 lib/Slic3r/GUI/Plater/3DPreview.pm:89 +msgid "Feature types" +msgstr "특색 유형" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:106 +msgid "Retractions" +msgstr "리트랙션" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:107 +msgid "Unretractions" +msgstr "리트랙션 취소" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:108 +msgid "Shells" +msgstr "쉘" diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index f6befe005..e440b35df 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,17 +1,6 @@ -min_slic3r_version = 1.41.1 -0.3.2 New MK2.5 and MK3 FW versions -0.3.1 New MK2.5 and MK3 FW versions -0.3.0 New MK2.5 and MK3 FW version +min_slic3r_version = 1.42.0-alpha +0.4.0-alpha2 First SLA profiles min_slic3r_version = 1.41.0-alpha -0.2.9 New MK2.5 and MK3 FW versions -0.2.8 New MK2.5 and MK3 FW version -min_slic3r_version = 1.41.1 -0.2.7 New MK2.5 and MK3 FW version -0.2.6 Added MMU2 MK2.5 settings -min_slic3r_version = 1.41.0-alpha -0.2.5 Prusament is out - added prusament settings -0.2.4 Added soluble support profiles for MMU2 -0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit 0.2.2 Edited MMU2 Single mode purge line 0.2.1 Added PET and BVOH settings for MMU2 0.2.0-beta5 Fixed MMU1 ramming parameters diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index bcffd1308..4dae3e3fb 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.3.2 +config_version = 0.4.0-alpha2 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -39,6 +39,10 @@ variants = 0.4; 0.6 name = Original Prusa i3 MK2.5 MMU 2.0 variants = 0.4 +[printer_model:SL1] +name = Original Prusa SL1 +variants = default; dummy + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -1136,7 +1140,22 @@ min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +[sla_material:*common*] +layer_height = 0.05 +initial_layer_height = 0.3 +exposure_time = 10 +initial_exposure_time = 15 +material_correction_printing = 1, 1, 1 +material_correction_curing = 1, 1, 1 + +[sla_material:Material 1] +inherits = *common* + +[sla_material:Material 2] +inherits = *common* + [printer:*common*] +printer_technology = FFF bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\n\n between_objects_gcode = @@ -1447,7 +1466,25 @@ extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F start_gcode = M107\nM115 U3.4.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n +[printer:Original Prusa SL1] +printer_technology = SLA +printer_model = SL1 +printer_variant = default +default_sla_material_profile = Material 1 +bed_shape = 0x0,150x0,150x100,0x100 +max_print_height = 100 +display_width = 150 +display_height = 100 +display_pixels_x = 2000 +display_pixels_y = 1000 +printer_correction = 1,1,1 + +[printer:Original Prusa SL1 dummy] +inherits = Original Prusa SL1 +printer_variant = dummy +default_sla_material_profile = Material 2 + # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" -filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" \ No newline at end of file +filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 9edc8fa76..f6116eec7 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -8,10 +8,18 @@ varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; +varying float world_z; + uniform vec4 uniform_color; +// x = min z, y = max z; +uniform vec2 z_range; + void main() { + if ((world_z < z_range.x) || (z_range.y < world_z)) + discard; + // if the fragment is outside the print volume -> use darker color vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index ea7e46e79..84ae51391 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -34,6 +34,8 @@ varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; +varying float world_z; + void main() { // First transform the normal into camera space and normalize the result. @@ -67,4 +69,5 @@ void main() } gl_Position = ftransform(); + world_z = vec3(print_box.volume_world_matrix * gl_Vertex).z; } diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt new file mode 100644 index 000000000..0e1f52d6c --- /dev/null +++ b/sandboxes/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(slabasebed) diff --git a/sandboxes/slabasebed/CMakeLists.txt b/sandboxes/slabasebed/CMakeLists.txt new file mode 100644 index 000000000..bff5ca588 --- /dev/null +++ b/sandboxes/slabasebed/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(slabasebed EXCLUDE_FROM_ALL slabasebed.cpp) +target_link_libraries(slabasebed libslic3r) \ No newline at end of file diff --git a/src/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp similarity index 79% rename from src/slabasebed.cpp rename to sandboxes/slabasebed/slabasebed.cpp index 255ce2cc3..9804ea3c9 100644 --- a/src/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -1,10 +1,10 @@ #include #include -#include -#include "TriangleMesh.hpp" -#include "SLABasePool.hpp" -#include "benchmark.h" +#include +#include +#include +#include const std::string USAGE_STR = { "Usage: slabasebed stlfilename.stl" @@ -28,7 +28,7 @@ int main(const int argc, const char *argv[]) { ExPolygons ground_slice; TriangleMesh basepool; - sla::ground_layer(model, ground_slice, 0.1f); + sla::base_plate(model, ground_slice, 0.1f); bench.start(); sla::create_base_pool(ground_slice, basepool); diff --git a/serial.txt b/serial.txt deleted file mode 100644 index 7c6816d43..000000000 --- a/serial.txt +++ /dev/null @@ -1,642 +0,0 @@ -<< start -<< echo: 3.1.1-RC5-150z -<< echo: Last Updated: Feb 7 2018 15:28:23 | Author: (none, default config) -<< Compiled: Feb 7 2018 -<< echo: Free Memory: 1777 PlannerBufferBytes: 1312 -<< echo:Hardcoded Default Settings Loaded -<< adc_init ->> N0 M105*39 -<< CrashDetect ENABLED! -<< tmc2130_init(), mode=NORMAL -<< PAT9125_init:1 -<< FSensor -<< ENABLED -<< echo:SD card ok -<< echo:busy: processing -<< Error:Line Number is not Last Line Number+1, Last Line: 0 -<< Resend: 1 -<< ok ->> N1 M107*36 -<< ok ->> N2 M115 U3.1.1-RC5*107 -<< ok ->> N3 M201 X1000 Y1000 Z200 E5000*10 -<< ok ->> N4 M203 X200 Y200 Z12 E120*8 -<< ok ->> N5 M204 S1250 T1250*39 -<< ok ->> N6 M205 X10 Y10 Z0.4 E2.5*63 -<< ok ->> N7 M205 S0 T0*36 -<< ok ->> N8 M83*16 -<< ok ->> N9 M104 S215*106 -<< ok ->> N10 M140 S60*98 -<< ok ->> N11 M190 S60*110 -<< T:158.08 E:0 B:57.1 -<< T:157.04 E:0 B:57.1 -<< T:156.77 E:0 B:56.9 -<< T:156.97 E:0 B:57.0 -<< T:158.14 E:0 B:57.0 -<< T:159.62 E:0 B:56.9 -<< T:161.25 E:0 B:56.8 -<< T:163.64 E:0 B:56.8 -<< T:165.94 E:0 B:56.7 -<< T:168.40 E:0 B:56.8 -<< T:170.79 E:0 B:56.7 -<< T:173.68 E:0 B:56.7 -<< T:175.53 E:0 B:56.6 -<< T:178.40 E:0 B:56.6 -<< T:180.94 E:0 B:56.5 -<< T:183.92 E:0 B:56.4 -<< T:186.73 E:0 B:56.4 -<< T:189.20 E:0 B:56.4 -<< T:191.32 E:0 B:56.3 -<< T:193.91 E:0 B:56.3 -<< T:196.38 E:0 B:56.2 -<< T:198.75 E:0 B:56.2 -<< T:201.65 E:0 B:56.3 -<< T:203.57 E:0 B:56.4 -<< T:206.38 E:0 B:56.5 -<< T:208.71 E:0 B:56.6 -<< T:211.04 E:0 B:56.6 -<< T:212.86 E:0 B:56.8 -<< T:214.84 E:0 B:57.0 -<< T:215.52 E:0 B:57.2 -<< T:215.78 E:0 B:57.4 -<< T:216.30 E:0 B:57.6 -<< T:216.51 E:0 B:57.7 -<< T:215.73 E:0 B:58.0 -<< T:215.47 E:0 B:58.2 -<< T:214.95 E:0 B:58.5 -<< T:214.22 E:0 B:58.7 -<< T:213.65 E:0 B:59.0 -<< T:212.24 E:0 B:59.2 -<< T:212.14 E:0 B:59.4 -<< T:212.03 E:0 B:59.7 -<< T:211.51 E:0 B:59.8 -<< ok ->> N12 M105*20 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N13 M105*21 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N14 M105*18 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N15 M105*19 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N16 M105*16 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N17 M105*17 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N18 M105*30 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N19 M105*31 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N20 M105*21 -<< ok T:211.0 /215.0 B:60.0 /60.0 T0:211.0 /215.0 @:60 B@:0 P:46.3 A:36.2 ->> N21 M109 S215*93 -<< T:211.3 E:0 W:? -<< T:211.8 E:0 W:? -<< T:211.8 E:0 W:? -<< T:212.1 E:0 W:? -<< T:212.4 E:0 W:? -<< T:213.3 E:0 W:? -<< T:213.3 E:0 W:? -<< T:213.8 E:0 W:? -<< T:214.1 E:0 W:2 -<< T:214.1 E:0 W:1 -<< T:214.2 E:0 W:0 -<< ok ->> N22 M105*23 -<< ok T:214.3 /215.0 B:60.8 /60.0 T0:214.3 /215.0 @:20 B@:7 P:46.4 A:36.0 ->> N23 M105*22 -<< ok T:214.3 /215.0 B:60.8 /60.0 T0:214.3 /215.0 @:20 B@:7 P:46.4 A:36.0 ->> N24 G28 W*82 -<< 0 step=62 mscnt= 993 -<< tmc2130_goto_step 0 0 2 1000 -<< step 61 mscnt = 984 -<< dir=0 steps=-61 -<< dir=1 steps=61 -<< dir=0 steps=3 -<< cnt 2 step 61 mscnt = 986 -<< cnt 1 step 62 mscnt = 1005 -<< cnt 0 step 63 mscnt = 1021 -<< echo:busy: processing -<< echo:busy: processing -<< 0 step=34 mscnt= 547 -<< tmc2130_goto_step 1 0 2 1000 -<< step 34 mscnt = 552 -<< dir=1 steps=-34 -<< dir=0 steps=34 -<< dir=1 steps=30 -<< cnt 29 step 34 mscnt = 554 -<< cnt 28 step 35 mscnt = 572 -<< cnt 27 step 36 mscnt = 588 -<< cnt 26 step 37 mscnt = 604 -<< cnt 25 step 38 mscnt = 620 -<< cnt 24 step 39 mscnt = 637 -<< cnt 23 step 40 mscnt = 653 -<< cnt 22 step 41 mscnt = 668 -<< cnt 21 step 42 mscnt = 684 -<< cnt 20 step 43 mscnt = 701 -<< cnt 19 step 44 mscnt = 717 -<< cnt 18 step 45 mscnt = 733 -<< cnt 17 step 46 mscnt = 748 -<< cnt 16 step 47 mscnt = 765 -<< cnt 15 step 48 mscnt = 780 -<< cnt 14 step 49 mscnt = 796 -<< cnt 13 step 50 mscnt = 812 -<< cnt 12 step 51 mscnt = 828 -<< cnt 11 step 52 mscnt = 844 -<< cnt 10 step 53 mscnt = 860 -<< cnt 9 step 54 mscnt = 876 -<< cnt 8 step 55 mscnt = 893 -<< cnt 7 step 56 mscnt = 909 -<< cnt 6 step 57 mscnt = 925 -<< cnt 5 step 58 mscnt = 941 -<< cnt 4 step 59 mscnt = 956 -<< cnt 3 step 60 mscnt = 972 -<< cnt 2 step 61 mscnt = 988 -<< cnt 1 step 62 mscnt = 1005 -<< cnt 0 step 63 mscnt = 1021 -<< echo:busy: processing -<< ok ->> N25 M105*16 -<< ok T:213.1 /215.0 B:60.8 /60.0 T0:213.1 /215.0 @:44 B@:35 P:46.5 A:35.6 ->> N26 G80*37 -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< ok ->> N27 M105*18 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N28 M105*29 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N29 M105*28 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N30 M105*20 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N31 M105*21 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N32 M105*22 -<< ok T:214.8 /215.0 B:60.9 /60.0 T0:214.8 /215.0 @:31 B@:25 P:46.3 A:35.7 ->> N33 G1 Y-3.0 F1000.0*24 -<< ok ->> N34 G92 E0.0*110 -<< ok ->> N35 G1 X60.0 E9.0 F1000.0*101 -<< ok ->> N36 G1 X100.0 E12.5 F1000.0*110 -<< ok ->> N37 G92 E0.0*109 -<< ok ->> N38 M221 S95*102 -<< ok ->> N39 M900 K30*120 -<< Invalid M code. -<< ok ->> N40 G21*46 -<< ok ->> N41 G90*37 -<< ok ->> N42 M83*46 -<< ok ->> N43 G1 E-0.80000 F2100.00000*10 -<< ok ->> N44 G1 Z0.600 F10200.000*1 -<< ok ->> N45 G1 X112.437 Y93.991 F10200.000*106 -<< ok ->> N46 G1 Z0.200 F10200.000*7 -<< ok ->> N47 G1 E0.80000 F2100.00000*35 -<< ok ->> N48 M204 S1000*107 -<< ok ->> N49 G1 F1800*122 -<< ok ->> N50 G1 X112.930 Y93.183 E0.02968*106 -<< ok ->> N51 G1 X113.335 Y92.806 E0.01733*99 -<< ok ->> N52 G1 X113.810 Y92.516 E0.01745*97 -<< ok ->> N53 G1 X114.334 Y92.328 E0.01745*97 -<< ok ->> N54 G1 X114.885 Y92.248 E0.01745*96 -<< ok ->> N55 M105*23 -<< ok T:214.9 /215.0 B:60.8 /60.0 T0:214.9 /215.0 @:30 B@:28 P:46.2 A:35.7 ->> N56 G1 X135.004 Y92.246 E0.63084*96 -<< ok ->> N57 G1 X136.005 Y92.436 E0.03195*101 -<< ok ->> N58 G1 X136.866 Y92.974 E0.03183*107 -<< ok ->> N59 G1 X137.473 Y93.788 E0.03183*111 -<< ok ->> N60 G1 X137.745 Y94.770 E0.03195*100 -<< ok ->> N61 G1 X137.753 Y115.086 E0.63700*88 -<< ok ->> N62 G1 X137.563 Y116.009 E0.02955*87 -<< ok ->> N63 G1 X137.070 Y116.817 E0.02968*88 -<< ok ->> N64 G1 X136.646 Y117.208 E0.01809*93 -<< ok ->> N65 G1 X136.149 Y117.503 E0.01809*88 -<< ok ->> N66 G1 X135.603 Y117.687 E0.01809*94 -<< ok ->> N67 G1 X135.029 Y117.754 E0.01809*94 -<< ok ->> N68 G1 X114.914 Y117.753 E0.63071*81 -<< ok ->> N69 G1 X113.991 Y117.563 E0.02955*83 -<< ok ->> N70 G1 X113.183 Y117.070 E0.02968*89 -<< ok ->> N71 G1 X112.792 Y116.646 E0.01809*88 -<< ok ->> N72 M105*18 -<< ok T:214.0 /215.0 B:60.8 /60.0 T0:214.0 /215.0 @:45 B@:16 P:46.2 A:35.5 ->> N73 G1 X112.497 Y116.149 E0.01809*84 -<< ok ->> N74 G1 X112.313 Y115.603 E0.01809*82 -<< ok ->> N75 G1 X112.246 Y115.029 E0.01809*92 -<< ok ->> N76 G1 X112.247 Y94.914 E0.63071*98 -<< ok ->> N77 G1 X112.425 Y94.050 E0.02767*111 -<< ok ->> N78 G1 F8160*126 -<< ok ->> N79 G1 X112.930 Y93.183 E-0.24526*78 -<< ok ->> N80 G1 F8160*121 -<< ok ->> N81 G1 X113.335 Y92.806 E-0.13510*67 -<< ok ->> N82 G1 F8160*123 -<< ok ->> N83 G1 X113.810 Y92.516 E-0.13609*74 -<< ok ->> N84 G1 F8160*125 -<< ok ->> N85 G1 X114.334 Y92.328 E-0.13609*77 -<< ok ->> N86 G1 F8160*127 -<< ok ->> N87 G1 X114.769 Y92.265 E-0.10746*66 -<< ok ->> N88 G1 E-0.04000 F2100.00000*1 -<< ok ->> N89 G1 Z0.800 F10200.000*14 -<< ok ->> N90 G1 X113.989 Y92.849 F10200.000*110 -<< ok ->> N91 G1 Z0.200 F10200.000*13 -<< ok ->> N92 G1 E0.80000 F2100.00000*43 -<< ok ->> N93 G1 F1800*125 -<< ok ->> N94 G1 X114.911 Y92.625 E0.02977*99 -<< ok ->> N95 G1 X135.004 Y92.623 E0.62999*108 -<< ok ->> N96 G1 X135.871 Y92.788 E0.02767*108 -<< ok ->> N97 G1 X136.617 Y93.258 E0.02767*105 -<< ok ->> N98 G1 X137.141 Y93.968 E0.02767*107 -<< ok ->> N99 G1 X137.371 Y94.824 E0.02778*107 -<< ok ->> N100 G1 X137.376 Y115.065 E0.63464*97 -<< ok ->> N101 G1 X137.209 Y115.878 E0.02602*104 -<< ok ->> N102 G1 X136.773 Y116.584 E0.02602*111 -<< ok ->> N103 G1 X136.407 Y116.916 E0.01550*110 -<< ok ->> N104 G1 X135.980 Y117.166 E0.01550*102 -<< ok ->> N105 G1 X135.511 Y117.321 E0.01550*98 -<< ok ->> N106 G1 X135.020 Y117.377 E0.01550*101 -<< ok ->> N107 G1 X114.935 Y117.376 E0.62975*101 -<< ok ->> N108 G1 X114.122 Y117.209 E0.02602*100 -<< ok ->> N109 G1 X113.416 Y116.773 E0.02602*105 -<< ok ->> N110 G1 X113.084 Y116.407 E0.01550*105 -<< ok ->> N111 G1 X112.834 Y115.980 E0.01550*107 -<< ok ->> N112 G1 X112.679 Y115.511 E0.01550*107 -<< ok ->> N113 G1 X112.623 Y115.020 E0.01550*98 -<< ok ->> N114 G1 X112.624 Y94.935 E0.62975*89 -<< ok ->> N115 G1 X112.791 Y94.122 E0.02602*80 -<< ok ->> N116 G1 X113.227 Y93.416 E0.02602*95 -<< ok ->> N117 G1 X113.940 Y92.885 E0.02789*81 -<< ok ->> N118 G1 F8160*73 -<< ok ->> N119 G1 X114.911 Y92.625 E-0.24574*113 -<< ok ->> N120 G1 F8160*66 -<< ok ->> N121 G1 X117.015 Y92.624 E-0.51426*113 -<< ok ->> N122 G1 E-0.04000 F2100.00000*48 -<< ok ->> N123 G1 Z0.800 F10200.000*63 -<< ok ->> N124 G1 X115.587 Y95.587 F10200.000*92 -<< ok ->> N125 M105*33 -<< ok T:214.2 /215.0 B:60.7 /60.0 T0:214.2 /215.0 @:41 B@:24 P:46.2 A:36.0 ->> N126 G1 Z0.200 F10200.000*48 -<< ok ->> N127 G1 E0.80000 F2100.00000*20 -<< ok ->> N128 G1 F1800*76 -<< ok ->> N129 G1 X134.413 Y95.587 E0.59027*87 -<< ok ->> N130 G1 X134.413 Y114.413 E0.59027*107 -<< ok ->> N131 G1 X115.587 Y114.413 E0.59027*101 -<< ok ->> N132 G1 X115.587 Y95.647 E0.58839*91 -<< ok ->> N133 G1 X115.210 Y95.210 F10200.000*90 -<< ok ->> N134 G1 F1800*65 -<< ok ->> N135 G1 X134.790 Y95.210 E0.61392*93 -<< ok ->> N136 G1 X134.790 Y114.790 E0.61392*107 -<< ok ->> N137 G1 X115.210 Y114.790 E0.61392*100 -<< ok ->> N138 G1 X115.210 Y95.270 E0.61204*86 -<< ok ->> N139 G1 X115.596 Y95.314 F10200.000*92 -<< ok ->> N140 G1 F8160*68 -<< ok ->> N141 G1 X118.319 Y95.260 E-0.76000*113 -<< ok ->> N142 G1 E-0.04000 F2100.00000*54 -<< ok ->> N143 G1 Z0.800 F10200.000*57 -<< ok ->> N144 G1 X115.700 Y113.527 F10200.000*98 -<< ok ->> N145 G1 Z0.200 F10200.000*53 -<< ok ->> N146 G1 E0.80000 F2100.00000*19 -<< ok ->> N147 G1 F1800*69 -<< ok ->> N148 G1 X116.303 Y114.130 E0.02708*98 -<< ok ->> N149 G1 X116.843 Y114.130 E0.01716*96 -<< ok ->> N150 G1 X115.870 Y113.157 E0.04372*110 -<< ok ->> N151 G1 X115.870 Y112.617 E0.01716*110 -<< ok ->> N152 G1 X117.383 Y114.130 E0.06799*108 -<< ok ->> N153 G1 X117.924 Y114.130 E0.01716*106 -<< ok ->> N154 M105*39 -<< ok T:215.1 /215.0 B:60.7 /60.0 T0:215.1 /215.0 @:29 B@:12 P:46.2 A:35.7 ->> N155 G1 X115.870 Y112.076 E0.09225*102 -<< ok ->> N156 G1 X115.870 Y111.536 E0.01716*106 -<< ok ->> N157 G1 X118.464 Y114.130 E0.11652*104 -<< ok ->> N158 G1 X119.004 Y114.130 E0.01716*100 -<< ok ->> N159 G1 X115.870 Y110.996 E0.14079*104 -<< ok ->> N160 G1 X115.870 Y110.456 E0.01716*105 -<< ok ->> N161 G1 X119.544 Y114.130 E0.16505*105 -<< ok ->> N162 G1 X120.085 Y114.130 E0.01716*110 -<< ok ->> N163 G1 X115.870 Y109.915 E0.18932*104 -<< ok ->> N164 G1 X115.870 Y109.375 E0.01716*99 -<< ok ->> N165 G1 X120.625 Y114.130 E0.21358*105 -<< ok ->> N166 G1 X121.165 Y114.130 E0.01716*100 -<< ok ->> N167 G1 X115.870 Y108.835 E0.23785*100 -<< ok ->> N168 G1 X115.870 Y108.295 E0.01716*97 -<< ok ->> N169 G1 X121.705 Y114.130 E0.26212*111 -<< ok ->> N170 G1 X122.245 Y114.130 E0.01716*97 -<< ok ->> N171 G1 X115.870 Y107.755 E0.28638*105 -<< ok ->> N172 G1 X115.870 Y107.214 E0.01716*108 -<< ok ->> N173 G1 X122.786 Y114.130 E0.31065*104 -<< ok ->> N174 G1 X123.326 Y114.130 E0.01716*96 -<< ok ->> N175 G1 X115.870 Y106.674 E0.33491*101 -<< ok ->> N176 G1 X115.870 Y106.134 E0.01716*104 -<< ok ->> N177 G1 X123.866 Y114.130 E0.35918*107 -<< ok ->> N178 G1 X124.406 Y114.130 E0.01716*110 -<< ok ->> N179 G1 X115.870 Y105.594 E0.38344*99 -<< ok ->> N180 G1 X115.870 Y105.054 E0.01716*101 -<< ok ->> N181 G1 X124.946 Y114.130 E0.40771*101 -<< ok ->> N182 G1 X125.487 Y114.130 E0.01716*99 -<< ok ->> N183 G1 X115.870 Y104.513 E0.43198*103 -<< ok ->> N184 G1 X115.870 Y103.973 E0.01716*107 -<< ok ->> N185 G1 X126.027 Y114.130 E0.45624*105 -<< ok ->> N186 G1 X126.567 Y114.130 E0.01716*107 -<< ok ->> N187 G1 X115.870 Y103.433 E0.48051*104 -<< ok ->> N188 G1 X115.870 Y102.893 E0.01716*105 -<< ok ->> N189 G1 X127.107 Y114.130 E0.50477*103 -<< ok ->> N190 M105*47 -<< ok T:215.3 /215.0 B:60.7 /60.0 T0:215.3 /215.0 @:28 B@:18 P:46.3 A:35.5 ->> N191 G1 X127.648 Y114.130 E0.01716*98 -<< ok ->> N192 G1 X115.870 Y102.352 E0.52904*111 -<< ok ->> N193 G1 X115.870 Y101.812 E0.01716*105 -<< ok ->> N194 G1 X128.188 Y114.130 E0.55330*98 -<< ok ->> N195 G1 X128.728 Y114.130 E0.01716*110 -<< ok ->> N196 G1 X115.870 Y101.272 E0.57757*102 -<< ok ->> N197 G1 X115.870 Y100.732 E0.01716*97 -<< ok ->> N198 G1 X129.268 Y114.130 E0.60184*105 -<< ok ->> N199 G1 X129.808 Y114.130 E0.01716*110 -<< ok ->> N200 G1 X115.870 Y100.192 E0.62610*98 -<< ok ->> N201 G1 X115.870 Y99.651 E0.01716*88 -<< ok ->> N202 G1 X130.349 Y114.130 E0.65037*111 -<< ok ->> N203 G1 X130.889 Y114.130 E0.01716*111 -<< ok ->> N204 G1 X115.870 Y99.111 E0.67463*95 -<< ok ->> N205 G1 X115.870 Y98.571 E0.01716*92 -<< ok ->> N206 G1 X131.429 Y114.130 E0.69890*98 -<< ok ->> N207 G1 X131.969 Y114.130 E0.01716*101 -<< ok ->> N208 M105*45 -<< ok T:214.7 /215.0 B:60.6 /60.0 T0:214.7 /215.0 @:38 B@:12 P:46.2 A:35.9 ->> N209 G1 X115.870 Y98.031 E0.72316*81 -<< ok ->> N210 G1 X115.870 Y97.491 E0.01716*88 -<< ok ->> N211 G1 X132.509 Y114.130 E0.74743*105 -<< ok ->> N212 G1 X133.050 Y114.130 E0.01716*96 -<< ok ->> N213 M105*39 -<< ok T:215.1 /215.0 B:60.4 /60.0 T0:215.1 /215.0 @:32 B@:39 P:46.3 A:35.8 ->> N214 M105*32 -<< ok T:214.6 /215.0 B:60.5 /60.0 T0:214.6 /215.0 @:39 B@:14 P:46.3 A:36.3 ->> N215 G28*21 -<< echo:busy: processing -<< 0 step=61 mscnt= 989 -<< tmc2130_goto_step 0 0 2 1000 -<< step 61 mscnt = 984 -<< dir=0 steps=-61 -<< dir=1 steps=61 -<< dir=0 steps=3 -<< cnt 2 step 61 mscnt = 986 -<< cnt 1 step 62 mscnt = 1004 -<< cnt 0 step 63 mscnt = 1021 -<< echo:busy: processing -<< echo:busy: processing -<< 0 step=34 mscnt= 547 -<< tmc2130_goto_step 1 0 2 1000 -<< step 34 mscnt = 552 -<< dir=1 steps=-34 -<< dir=0 steps=34 -<< dir=1 steps=30 -<< cnt 29 step 34 mscnt = 554 -<< cnt 28 step 35 mscnt = 573 -<< cnt 27 step 36 mscnt = 589 -<< cnt 26 step 37 mscnt = 604 -<< cnt 25 step 38 mscnt = 621 -<< cnt 24 step 39 mscnt = 636 -<< cnt 23 step 40 mscnt = 652 -<< cnt 22 step 41 mscnt = 668 -<< cnt 21 step 42 mscnt = 684 -<< cnt 20 step 43 mscnt = 701 -<< cnt 19 step 44 mscnt = 717 -<< cnt 18 step 45 mscnt = 733 -<< cnt 17 step 46 mscnt = 749 -<< cnt 16 step 47 mscnt = 765 -<< cnt 15 step 48 mscnt = 781 -<< cnt 14 step 49 mscnt = 796 -<< cnt 13 step 50 mscnt = 812 -<< cnt 12 step 51 mscnt = 829 -<< cnt 11 step 52 mscnt = 844 -<< cnt 10 step 53 mscnt = 860 -<< cnt 9 step 54 mscnt = 876 -<< cnt 8 step 55 mscnt = 893 -<< cnt 7 step 56 mscnt = 909 -<< cnt 6 step 57 mscnt = 925 -<< cnt 5 step 58 mscnt = 941 -<< cnt 4 step 59 mscnt = 957 -<< cnt 3 step 60 mscnt = 973 -<< cnt 2 step 61 mscnt = 989 -<< cnt 1 step 62 mscnt = 1005 -<< cnt 0 step 63 mscnt = 1021 -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< echo:busy: processing -<< ok ->> N216 M105*34 -<< ok T:214.8 /215.0 B:60.2 /60.0 T0:214.8 /215.0 @:33 B@:27 P:46.0 A:36.1 ->> N217 M105*35 -<< ok T:214.8 /215.0 B:60.2 /60.0 T0:214.8 /215.0 @:33 B@:27 P:46.0 A:36.1 ->> N218 M105*44 -<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1 ->> N219 M105*45 -<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1 ->> N220 M105*39 -<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1 ->> N221 M105*38 -<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1 ->> N222 M105*37 -<< ok T:214.9 /215.0 B:60.1 /60.0 T0:214.9 /215.0 @:31 B@:33 P:45.9 A:36.1 ->> N223 M105*36 -<< ok T:214.5 /215.0 B:60.1 /60.0 T0:214.5 /215.0 @:38 B@:31 P:46.0 A:36.3 -DISCONNECTED diff --git a/slic3r.pl b/slic3r.pl deleted file mode 100755 index 3496daf16..000000000 --- a/slic3r.pl +++ /dev/null @@ -1,553 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/lib"; - use local::lib '--no-create', "$FindBin::Bin/local-lib"; -} - -use File::Basename qw(basename); -use Getopt::Long qw(:config no_auto_abbrev); -use List::Util qw(first); -#use POSIX qw(setlocale LC_NUMERIC); -use Slic3r; -use Slic3r::Geometry qw(deg2rad); -use Time::HiRes qw(gettimeofday tv_interval); -$|++; -binmode STDOUT, ':utf8'; - -# Convert all parameters from the local code page to utf8 on Windows. -@ARGV = map Slic3r::decode_path($_), @ARGV if $^O eq 'MSWin32'; - -our %opt = (); -my %cli_options = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'version' => sub { print "$Slic3r::VERSION\n"; exit 0 }, - - 'debug' => \$Slic3r::debug, - 'gui' => \$opt{gui}, - 'no-gui' => \$opt{no_gui}, - 'o|output=s' => \$opt{output}, - - 'save=s' => \$opt{save}, - 'load=s@' => \$opt{load}, - 'autosave=s' => \$opt{autosave}, - 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, - 'no-plater' => \$opt{no_plater}, - 'gui-mode=s' => \$opt{obsolete_ignore_this_option_gui_mode}, - 'datadir=s' => \$opt{datadir}, - 'export-png' => \$opt{export_png}, - 'merge|m' => \$opt{merge}, - 'repair' => \$opt{repair}, - 'cut=f' => \$opt{cut}, - 'split' => \$opt{split}, - 'info' => \$opt{info}, - - 'scale=f' => \$opt{scale}, - 'rotate=f' => \$opt{rotate}, - 'duplicate=i' => \$opt{duplicate}, - 'duplicate-grid=s' => \$opt{duplicate_grid}, - 'print-center=s' => \$opt{print_center}, - 'dont-arrange' => \$opt{dont_arrange}, - ); - foreach my $opt_key (keys %{$Slic3r::Config::Options}) { - my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; - # allow both the dash-separated option name and the full opt_key - $options{ "$opt_key|$cli" } = \$cli_options{$opt_key}; - } - - @ARGV = grep !/^-psn_\d/, @ARGV if $^O eq 'darwin'; - GetOptions(%options) or usage(1); -} - -# load configuration files -my @external_configs = (); -if ($opt{load}) { - foreach my $configfile (@{$opt{load}}) { - if (-e $configfile) { - push @external_configs, Slic3r::Config::load($configfile); - } elsif (-e "$FindBin::Bin/$configfile") { - printf STDERR "Loading $FindBin::Bin/$configfile\n"; - push @external_configs, Slic3r::Config::load("$FindBin::Bin/$configfile"); - } else { - $opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n"; - } - } -} - -# process command line options -my $cli_config = Slic3r::Config->new; -foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) { - $c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults - $cli_config->apply($c); -} - -# save configuration -if ($opt{save}) { - if (@{$cli_config->get_keys} > 0) { - $cli_config->save($opt{save}); - } else { - Slic3r::Config::new_from_defaults->save($opt{save}); - } -} - -# apply command line config on top of default config -my $config = Slic3r::Config::new_from_defaults; -$config->apply($cli_config); - -# launch GUI -my $gui; -if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3r::GUI; 1") { - { - no warnings 'once'; - $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); - $Slic3r::GUI::no_plater = $opt{no_plater}; - $Slic3r::GUI::autosave = $opt{autosave}; - } - $gui = Slic3r::GUI->new; - #setlocale(LC_NUMERIC, 'C'); - $gui->{mainframe}->load_config_file($_) for @{$opt{load}}; - $gui->{mainframe}->load_config($cli_config); - my @input_files = @ARGV; - $gui->{mainframe}{plater}->load_files(\@input_files) unless $opt{no_plater}; - $gui->MainLoop; - exit; -} -die $@ if $@ && $opt{gui}; - -if (@ARGV) { # slicing from command line - $config->validate; - - if ($opt{repair}) { - foreach my $file (@ARGV) { - die "Repair is currently supported only on STL files\n" - if $file !~ /\.[sS][tT][lL]$/; - - my $output_file = $file; - $output_file =~ s/\.([sS][tT][lL])$/_fixed.obj/; - my $tmesh = Slic3r::TriangleMesh->new; - $tmesh->ReadSTLFile($file); - $tmesh->repair; - $tmesh->WriteOBJFile($output_file); - } - exit; - } - - if ($opt{cut}) { - foreach my $file (@ARGV) { - my $model = Slic3r::Model->read_from_file($file); - my $mesh = $model->mesh; - $mesh->translate(0, 0, -$mesh->bounding_box->z_min); - my $upper = Slic3r::TriangleMesh->new; - my $lower = Slic3r::TriangleMesh->new; - $mesh->cut($opt{cut}, $upper, $lower); - $upper->repair; - $lower->repair; - $upper->write_ascii("${file}_upper.stl") - if $upper->facets_count > 0; - $lower->write_ascii("${file}_lower.stl") - if $lower->facets_count > 0; - } - exit; - } - - if ($opt{split}) { - foreach my $file (@ARGV) { - my $model = Slic3r::Model->read_from_file($file); - my $mesh = $model->mesh; - $mesh->repair; - - my $part_count = 0; - foreach my $new_mesh (@{$mesh->split}) { - my $output_file = sprintf '%s_%02d.stl', $file, ++$part_count; - printf "Writing to %s\n", basename($output_file); - $new_mesh->write_binary($output_file); - } - } - exit; - } - - while (my $input_file = shift @ARGV) { - my $model; - if ($opt{merge}) { - my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); - $model = Slic3r::Model->merge(@models); - } else { - $model = Slic3r::Model->read_from_file($input_file); - } - - if ($opt{info}) { - $model->print_info; - next; - } - - if (defined $opt{duplicate_grid}) { - $opt{duplicate_grid} = [ split /[,x]/, $opt{duplicate_grid}, 2 ]; - } - if (defined $opt{print_center}) { - $opt{print_center} = Slic3r::Pointf->new(split /[,x]/, $opt{print_center}, 2); - } - - my $sprint = Slic3r::Print::Simple->new( - scale => $opt{scale} // 1, - rotate => deg2rad($opt{rotate} // 0), - duplicate => $opt{duplicate} // 1, - duplicate_grid => $opt{duplicate_grid} // [1,1], - print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), - dont_arrange => $opt{dont_arrange} // 0, - output_file => $opt{output}, - ); - - # This is delegated to C++ PrintObject::apply_config(). - $sprint->apply_config($config); - $sprint->set_model($model); - # Do the apply_config once again to validate the layer height profiles at all the newly added PrintObjects. - $sprint->apply_config($config); - - if ($opt{export_png}) { - $sprint->export_png; - } else { - my $t0 = [gettimeofday]; - # The following call may die if the output_filename_format template substitution fails, - # if the file cannot be written into, or if the post processing scripts cannot be executed. - $sprint->export_gcode; - - # output some statistics - { - my $duration = tv_interval($t0); - printf "Done. Process took %d minutes and %.3f seconds\n", - int($duration/60), ($duration - int($duration/60)*60); # % truncates to integer - } - printf "Filament required: %.1fmm (%.1fcm3)\n", - $sprint->total_used_filament, $sprint->total_extruded_volume/1000; - } - } -} else { - usage(1) unless $opt{save}; -} - -sub usage { - my ($exit_code) = @_; - my $config = Slic3r::Config::new_from_defaults->as_hash; - print <<"EOF"; -Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers -written by Alessandro Ranellucci - http://slic3r.org/ - -Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... - - --help Output this usage screen and exit - --version Output the version of Slic3r and exit - --save Save configuration to the specified file - --load Load configuration from the specified file. It can be used - more than once to load options from multiple files. - -o, --output File to output gcode to (by default, the file will be saved - into the same directory as the input file using the - --output-filename-format to generate the filename.) If a - directory is specified for this option, the output will - be saved under that directory, and the filename will be - generated by --output-filename-format. - - Non-slicing actions (no G-code will be generated): - --repair Repair given STL files and save them as _fixed.obj - --cut Cut given input files at given Z (relative) and export - them as _upper.stl and _lower.stl - --split Split the shells contained in given STL file into several STL files - --info Output information about the supplied file(s) and exit - -j, --threads Number of threads to use (1+, default: $config->{threads}) - - GUI options: - --gui Forces the GUI launch instead of command line slicing (if you - supply a model file, it will be loaded into the plater) - --no-plater Disable the plater tab - --no-gui Forces the command line slicing instead of gui. - This takes precedence over --gui if both are present. - --autosave Automatically export current configuration to the specified file - - Output options: - --output-filename-format - Output file name format; all config options enclosed in brackets - will be replaced by their values, as well as [input_filename_base] - and [input_filename] (default: $config->{output_filename_format}) - --post-process Generated G-code will be processed with the supplied script; - call this more than once to process through multiple scripts. - --export-png Export zipped PNG files containing slices instead of G-code. - -m, --merge If multiple files are supplied, they will be composed into a single - print rather than processed individually. - - Printer options: - --nozzle-diameter Diameter of nozzle in mm (default: $config->{nozzle_diameter}->[0]) - --print-center Coordinates in mm of the point to center the print around - (default: 100,100) - --z-offset Additional height in mm to add to vertical coordinates - (+/-, default: $config->{z_offset}) - --gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, - default: $config->{gcode_flavor}) - --use-relative-e-distances Enable this to get relative E values (default: no) - --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) - --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) - --set-and-wait-temperatures Use M190 instead of M140 for temperature changes past the first (default: no) - --gcode-comments Make G-code verbose by adding comments (default: no) - - Filament options: - --filament-diameter Diameter in mm of your raw filament (default: $config->{filament_diameter}->[0]) - --extrusion-multiplier - Change this to alter the amount of plastic extruded. There should be - very little need to change this value, which is only useful to - compensate for filament packing (default: $config->{extrusion_multiplier}->[0]) - --temperature Extrusion temperature in degree Celsius, set 0 to disable (default: $config->{temperature}->[0]) - --first-layer-temperature Extrusion temperature for the first layer, in degree Celsius, - set 0 to disable (default: same as --temperature) - --bed-temperature Heated bed temperature in degree Celsius, set 0 to disable (default: $config->{bed_temperature}[0]) - --first-layer-bed-temperature Heated bed temperature for the first layer, in degree Celsius, - set 0 to disable (default: same as --bed-temperature) - - Speed options: - --travel-speed Speed of non-print moves in mm/s (default: $config->{travel_speed}) - --perimeter-speed Speed of print moves for perimeters in mm/s (default: $config->{perimeter_speed}) - --small-perimeter-speed - Speed of print moves for small perimeters in mm/s or % over perimeter speed - (default: $config->{small_perimeter_speed}) - --external-perimeter-speed - Speed of print moves for the external perimeter in mm/s or % over perimeter speed - (default: $config->{external_perimeter_speed}) - --infill-speed Speed of print moves in mm/s (default: $config->{infill_speed}) - --solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed - (default: $config->{solid_infill_speed}) - --top-solid-infill-speed Speed of print moves for top surfaces in mm/s or % over solid infill speed - (default: $config->{top_solid_infill_speed}) - --support-material-speed - Speed of support material print moves in mm/s (default: $config->{support_material_speed}) - --support-material-interface-speed - Speed of support material interface print moves in mm/s or % over support material - speed (default: $config->{support_material_interface_speed}) - --bridge-speed Speed of bridge print moves in mm/s (default: $config->{bridge_speed}) - --gap-fill-speed Speed of gap fill print moves in mm/s (default: $config->{gap_fill_speed}) - --first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute - value or as a percentage over normal speeds (default: $config->{first_layer_speed}) - - Acceleration options: - --perimeter-acceleration - Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero - to disable; default: $config->{perimeter_acceleration}) - --infill-acceleration - Overrides firmware's default acceleration for infill. (mm/s^2, set zero - to disable; default: $config->{infill_acceleration}) - --bridge-acceleration - Overrides firmware's default acceleration for bridges. (mm/s^2, set zero - to disable; default: $config->{bridge_acceleration}) - --first-layer-acceleration - Overrides firmware's default acceleration for first layer. (mm/s^2, set zero - to disable; default: $config->{first_layer_acceleration}) - --default-acceleration - Acceleration will be reset to this value after the specific settings above - have been applied. (mm/s^2, set zero to disable; default: $config->{default_acceleration}) - - Accuracy options: - --layer-height Layer height in mm (default: $config->{layer_height}) - --first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height}) - --infill-every-layers - Infill every N layers (default: $config->{infill_every_layers}) - --solid-infill-every-layers - Force a solid layer every N layers (default: $config->{solid_infill_every_layers}) - - Print options: - --perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters}) - --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: $config->{top_solid_layers}) - --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: $config->{bottom_solid_layers}) - --solid-layers Shortcut for setting the two options above at once - --fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%) - --fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle}) - --fill-pattern Pattern to use to fill non-solid layers (default: $config->{fill_pattern}) - --top-fill-pattern Pattern to use to fill solid layers (default: $config->{top_fill_pattern}) - --bottom-fill-pattern Pattern to use to fill solid layers (default: $config->{bottom_fill_pattern}) - --start-gcode Load initial G-code from the supplied file. This will overwrite - the default command (home all axes [G28]). - --end-gcode Load final G-code from the supplied file. This will overwrite - the default commands (turn off temperature [M104 S0], - home X axis [G28 X], disable motors [M84]). - --before-layer-gcode Load before-layer-change G-code from the supplied file (default: nothing). - --layer-gcode Load layer-change G-code from the supplied file (default: nothing). - --toolchange-gcode Load tool-change G-code from the supplied file (default: nothing). - --seam-position Position of loop starting points (random/nearest/aligned, default: $config->{seam_position}). - --external-perimeters-first Reverse perimeter order. (default: no) - --spiral-vase Experimental option to raise Z gradually when printing single-walled vases - (default: no) - --only-retract-when-crossing-perimeters - Disable retraction when travelling between infill paths inside the same island. - (default: no) - --solid-infill-below-area - Force solid infill when a region has a smaller area than this threshold - (mm^2, default: $config->{solid_infill_below_area}) - --infill-only-where-needed - Only infill under ceilings (default: no) - --infill-first Make infill before perimeters (default: no) - - Quality options (slower slicing): - --extra-perimeters Add more perimeters when needed (default: yes) - --one-top-perimeter Only draw 1 perimeter for the top layers (default: yes) - --ensure-vertical-shell-thickness Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers). (default: no) - --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) - --thin-walls Detect single-width walls (default: yes) - --overhangs Experimental option to use bridge flow, speed and fan for overhangs - (default: yes) - - Support material options: - --support-material Generate support material for overhangs - --support-material-threshold - Overhang threshold angle (range: 0-90, set 0 for automatic detection, - default: $config->{support_material_threshold}) - --support-material-pattern - Pattern to use for support material (default: $config->{support_material_pattern}) - --support-material-with-sheath - Add a sheath (a single perimeter line) around the base support. - This makes the support more reliable, but also more difficult to remove. (default: yes) - --support-material-spacing - Spacing between pattern lines (mm, default: $config->{support_material_spacing}) - --support-material-angle - Support material angle in degrees (range: 0-90, default: $config->{support_material_angle}) - --support-material-contact-distance - Vertical distance between object and support material (0+, default: $config->{support_material_contact_distance}) - --support-material-xy-spacing - "XY separation between an object and its support. If expressed as percentage (for example 50%), - it will be calculated over external perimeter width (default: half of exteral perimeter width) - --support-material-interface-layers - Number of perpendicular layers between support material and object (0+, default: $config->{support_material_interface_layers}) - --support-material-interface-spacing - Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: $config->{support_material_interface_spacing}) - --raft-layers Number of layers to raise the printed objects by (range: 0+, default: $config->{raft_layers}) - --support-material-enforce-layers - Enforce support material on the specified number of layers from bottom, - regardless of --support-material and threshold (0+, default: $config->{support_material_enforce_layers}) - --support-material-buildplate-only - Only create support if it lies on a build plate. Don't create support on a print. (default: no) - --dont-support-bridges - Experimental option for preventing support material from being generated under bridged areas (default: yes) - - Retraction options: - --retract-length Length of retraction in mm when pausing extrusion (default: $config->{retract_length}[0]) - --retract-speed Speed for retraction in mm/s (default: $config->{retract_speed}[0]) - --deretract-speed Speed for deretraction (loading of filament after a retract) in mm/s (default: $config->{retract_speed}[0]) - --retract-restart-extra - Additional amount of filament in mm to push after - compensating retraction (default: $config->{retract_restart_extra}[0]) - --retract-before-travel - Only retract before travel moves of this length in mm (default: $config->{retract_before_travel}[0]) - --retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0]) - --retract-lift-above Only lift Z when above the specified height (default: $config->{retract_lift_above}[0]) - --retract-lift-below Only lift Z when below the specified height (default: $config->{retract_lift_below}[0]) - --retract-layer-change - Enforce a retraction before each Z move (default: no) - --wipe Wipe the nozzle while doing a retraction (default: no) - - Retraction options for multi-extruder setups: - --retract-length-toolchange - Length of retraction in mm when disabling tool (default: $config->{retract_length_toolchange}[0]) - --retract-restart-extra-toolchange - Additional amount of filament in mm to push after - switching tool (default: $config->{retract_restart_extra_toolchange}[0]) - - Cooling options: - --cooling Enable fan and cooling control - --min-fan-speed Minimum fan speed (default: $config->{min_fan_speed}[0]%) - --max-fan-speed Maximum fan speed (default: $config->{max_fan_speed}[0]%) - --bridge-fan-speed Fan speed to use when bridging (default: $config->{bridge_fan_speed}[0]%) - --fan-below-layer-time Enable fan if layer print time is below this approximate number - of seconds (default: $config->{fan_below_layer_time}[0]) - --slowdown-below-layer-time Slow down if layer print time is below this approximate number - of seconds (default: $config->{slowdown_below_layer_time}[0]) - --min-print-speed Minimum print speed (mm/s, default: $config->{min_print_speed}[0]) - --disable-fan-first-layers Disable fan for the first N layers (default: $config->{disable_fan_first_layers}[0]) - --fan-always-on Keep fan always on at min fan speed, even for layers that don't need - cooling - - Skirt options: - --skirts Number of skirts to draw (0+, default: $config->{skirts}) - --skirt-distance Distance in mm between innermost skirt and object - (default: $config->{skirt_distance}) - --skirt-height Height of skirts to draw (expressed in layers, 0+, default: $config->{skirt_height}) - --min-skirt-length Generate no less than the number of loops required to consume this length - of filament on the first layer, for each extruder (mm, 0+, default: $config->{min_skirt_length}) - --brim-width Width of the brim that will get added to each object to help adhesion - (mm, default: $config->{brim_width}) - - Transform options: - --scale Factor for scaling input object (default: 1) - --rotate Rotation angle in degrees (0-360, default: 0) - --duplicate Number of items with auto-arrange (1+, default: 1) - --duplicate-grid Number of items with grid arrangement (default: 1,1) - --duplicate-distance Distance in mm between copies (default: $config->{duplicate_distance}) - --dont-arrange Don't arrange the objects on the build plate. The model coordinates - define the absolute positions on the build plate. - The option --print-center will be ignored. - --clip_multipart_objects - When printing multi-material objects, this settings will make slic3r to clip the overlapping - object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped - by the 1st and 2nd etc). (default: $config->{clip_multipart_objects})"; - --elefant-foot-compensation - Shrink the first layer by the configured value to compensate for the 1st layer squish - aka an Elefant Foot effect (mm, default: $config->{elefant_foot_compensation}) - --xy-size-compensation - Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation}) - - Sequential printing options: - --complete-objects When printing multiple objects and/or copies, complete each one before - starting the next one; watch out for extruder collisions (default: no) - --extruder-clearance-radius Radius in mm above which extruder won't collide with anything - (default: $config->{extruder_clearance_radius}) - --extruder-clearance-height Maximum vertical extruder depth; i.e. vertical distance from - extruder tip and carriage bottom (default: $config->{extruder_clearance_height}) - - Miscellaneous options: - --notes Notes to be added as comments to the output file - --resolution Minimum detail resolution (mm, set zero for full resolution, default: $config->{resolution}) - - Flow options (advanced): - --extrusion-width Set extrusion width manually; it accepts either an absolute value in mm - (like 0.65) or a percentage over layer height (like 200%) - --first-layer-extrusion-width - Set a different extrusion width for first layer - --perimeter-extrusion-width - Set a different extrusion width for perimeters - --external-perimeter-extrusion-width - Set a different extrusion width for external perimeters - --infill-extrusion-width - Set a different extrusion width for infill - --solid-infill-extrusion-width - Set a different extrusion width for solid infill - --top-infill-extrusion-width - Set a different extrusion width for top infill - --support-material-extrusion-width - Set a different extrusion width for support material - --infill-overlap Overlap between infill and perimeters (default: $config->{infill_overlap}) - --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: $config->{bridge_flow_ratio}) - --over-bridge-flow-ratio Multiplier for extrusion when solid-filling over a bridge (> 0, default: $config->{over_bridge_flow_ratio}) - - Multiple extruder options: - --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement - (can be specified multiple times, default: 0x0) - --perimeter-extruder - Extruder to use for perimeters and brim (1+, default: $config->{perimeter_extruder}) - --infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder}) - --solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder}) - --support-material-extruder - Extruder to use for support material, raft and skirt - (1+, 0 to use the current extruder to minimize tool changes, default: $config->{support_material_extruder}) - --support-material-interface-extruder - Extruder to use for support material interface - (1+, 0 to use the current extruder to minimize tool changes, default: $config->{support_material_interface_extruder}) - --ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping - (default: no) - --standby-temperature-delta - Temperature difference to be applied when an extruder is not active and - --ooze-prevention is enabled (default: $config->{standby_temperature_delta}) - -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/slic3r.sublime-project b/slic3r.sublime-project index 3f2fc36dc..785dddba4 100644 --- a/slic3r.sublime-project +++ b/slic3r.sublime-project @@ -76,7 +76,7 @@ { "path": ".", // "folder_exclude_patterns": [".svn", "._d", ".metadata", ".settings"], - "file_exclude_patterns": ["XS.c"] + "file_exclude_patterns": ["XS.c", "*.pch", "*.ilk", "*.js" ] } ], diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0c008293..31c801379 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(poly2tri) add_subdirectory(qhull) add_subdirectory(Shiny) add_subdirectory(semver) +add_subdirectory(imgui) # Adding libnest2d project for bin packing... set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") @@ -19,7 +20,6 @@ add_subdirectory(libnest2d) include_directories(${LIBDIR}/qhull/src) #message(STATUS ${LIBDIR}/qhull/src) - # ############################################################################## # Configure rasterizer target # ############################################################################## @@ -36,23 +36,90 @@ else() add_subdirectory(png/zlib) set(ZLIB_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/png/zlib ${CMAKE_CURRENT_BINARY_DIR}/png/zlib) include_directories(${ZLIB_INCLUDE_DIR}) - message(STATUS "ZLIB_INCLUDE_DIR ${ZLIB_INCLUDE_DIR}") add_subdirectory(png/libpng) set_target_properties(zlibstatic PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(png_static PROPERTIES POSITION_INDEPENDENT_CODE ON) -# target_include_directories(png_static PRIVATE ${ZLIB_INCLUDE_DIR}) set(PNG_LIBRARIES png_static zlibstatic) set(PNG_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/png/libpng ${CMAKE_CURRENT_BINARY_DIR}/png/libpng) endif() add_subdirectory(libslic3r) + +if (SLIC3R_GUI) + if(WIN32) + message(STATUS "WXWIN environment set to: $ENV{WXWIN}") + elseif(UNIX) + message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}") + set(wxWidgets_USE_UNICODE ON) + if(SLIC3R_STATIC) + set(wxWidgets_USE_STATIC ON) + else() + set(wxWidgets_USE_STATIC OFF) + endif() + endif() + + find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl) + include(${wxWidgets_USE_FILE}) +endif() + add_subdirectory(slic3r) # Create a slic3r executable -add_executable(slic3r slic3r.cpp) -target_link_libraries(slic3r libslic3r libslic3r_gui ${wxWidgets_LIBRARIES} ${CURL_LIBRARIES}) -# Add the OpenGL and GLU libraries. +# Process mainfests for various platforms. +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/slic3r.rc.in ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/slic3r.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/slic3r.manifest @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) +if (MSVC) + add_library(slic3r SHARED slic3r.cpp) +else () + add_executable(slic3r slic3r.cpp) +endif () +if (NOT MSVC) + if(SLIC3R_GUI) + set_target_properties(slic3r PROPERTIES OUTPUT_NAME "slic3r-gui") + else() + set_target_properties(slic3r PROPERTIES OUTPUT_NAME "slic3r-console") + endif() +endif () + +target_link_libraries(slic3r libslic3r) +if (APPLE) +# add_compile_options(-stdlib=libc++) +# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) + # -liconv: boost links to libiconv by default + target_link_libraries(slic3r "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) +elseif (MSVC) + # Manifest is provided through slic3r.rc, don't generate your own. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") +else () + target_link_libraries(slic3r ${CMAKE_DL_LIBS} -lstdc++) +endif () + +# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries. if (SLIC3R_GUI) + target_link_libraries(slic3r libslic3r_gui ${wxWidgets_LIBRARIES}) + + # Configure libcurl & OpenSSL + find_package(CURL REQUIRED) + target_include_directories(slic3r PRIVATE ${CURL_INCLUDE_DIRS}) + target_link_libraries(slic3r CURL::libcurl) + if (SLIC3R_STATIC) + if (NOT APPLE) + # libcurl is always linked dynamically to the system libcurl on OSX. + # On other systems, libcurl is linked statically if SLIC3R_STATIC is set. + target_compile_definitions(slic3r PRIVATE CURL_STATICLIB) + endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + # As of now, our build system produces a statically linked libcurl, + # which links the OpenSSL library dynamically. + find_package(OpenSSL REQUIRED) + message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") + message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") + target_include_directories(slic3r PRIVATE ${OPENSSL_INCLUDE_DIR}) + target_link_libraries(slic3r ${OPENSSL_LIBRARIES}) + endif() + endif() + if (MSVC) target_link_libraries(slic3r user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib) elseif (MINGW) @@ -60,6 +127,64 @@ if (SLIC3R_GUI) elseif (APPLE) target_link_libraries(slic3r "-framework OpenGL") else () - target_link_libraries(slic3r -lGL -lGLU) + target_link_libraries(slic3r -ldl -lGL -lGLU) endif () endif () + +# On Windows, a shim application is required to produce a console / non console version of the Slic3r application. +# Also the shim may load the Mesa software OpenGL renderer if the default renderer does not support OpenGL 2.0 and higher. +if (MSVC) + add_executable(slic3r_app_gui WIN32 slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) + target_compile_definitions(slic3r_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_GUI) + add_dependencies(slic3r_app_gui slic3r) + set_target_properties(slic3r_app_gui PROPERTIES OUTPUT_NAME "slic3r") + + add_executable(slic3r_app_console slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) + target_compile_definitions(slic3r_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE -DSLIC3R_WRAPPER_NOGUI) + add_dependencies(slic3r_app_console slic3r) + set_target_properties(slic3r_app_console PROPERTIES OUTPUT_NAME "slic3r-console") + + add_executable(slic3r_app_noconsole WIN32 slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) + target_compile_definitions(slic3r_app_noconsole PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_NOGUI) + add_dependencies(slic3r_app_noconsole slic3r) + set_target_properties(slic3r_app_noconsole PROPERTIES OUTPUT_NAME "slic3r-noconsole") +endif () + +# Link the resources dir to where Slic3r GUI expects it +if (MSVC) + if (CMAKE_CONFIGURATION_TYPES) + foreach (CONF ${CMAKE_CONFIGURATION_TYPES}) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}" WIN_CONF_OUTPUT_DIR) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}/resources" WIN_RESOURCES_SYMLINK) + add_custom_command(TARGET slic3r POST_BUILD + COMMAND if exist "${WIN_CONF_OUTPUT_DIR}" "(" + if not exist "${WIN_RESOURCES_SYMLINK}" "(" + mklink /J "${WIN_RESOURCES_SYMLINK}" "${SLIC3R_RESOURCES_DIR_WIN}" + ")" + ")" + COMMENT "Symlinking the resources directory into the build tree" + VERBATIM + ) + endforeach () + else () + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources" WIN_RESOURCES_SYMLINK) + add_custom_command(TARGET slic3r POST_BUILD + COMMAND if not exist "${WIN_RESOURCES_SYMLINK}" "(" mklink /J "${WIN_RESOURCES_SYMLINK}" "${SLIC3R_RESOURCES_DIR_WIN}" ")" + COMMENT "Symlinking the resources directory into the build tree" + VERBATIM + ) + endif () +elseif (XCODE) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + add_custom_command(TARGET slic3r POST_BUILD + COMMAND ln -sf "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources" + COMMENT "Symlinking the resources directory into the build tree" + VERBATIM + ) +else () + add_custom_command(TARGET slic3r POST_BUILD + COMMAND ln -sf "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources" + COMMENT "Symlinking the resources directory into the build tree" + VERBATIM + ) +endif() diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index da5b66720..166eec330 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -25,6 +25,9 @@ #include #include +#include +#include + #include #include "stl.h" @@ -125,11 +128,11 @@ stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, std::swap(a, b); edge->which_edge += 3; /* this edge is loaded backwards */ } - memcpy(&edge->key[0], a->data(), sizeof(stl_vertex)); - memcpy(&edge->key[sizeof(stl_vertex)], b->data(), sizeof(stl_vertex)); + memcpy(&edge->key[0], a->data(), sizeof(stl_vertex)); + memcpy(&edge->key[3], b->data(), sizeof(stl_vertex)); // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. for (size_t i = 0; i < 6; ++ i) { - unsigned char *p = edge->key + i * 4; + unsigned char *p = (unsigned char*)(edge->key + i); #ifdef BOOST_LITTLE_ENDIAN if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) // Negative zero, switch to positive zero. @@ -142,6 +145,16 @@ stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, } } +static inline size_t hash_size_from_nr_faces(const size_t nr_faces) +{ + // Good primes for addressing a cca. 30 bit space. + // https://planetmath.org/goodhashtableprimes + static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; + // Find a prime number for 50% filling of the shared triangle edges in the mesh. + auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); + return (it == primes.end()) ? primes.back() : *it; +} + static void stl_initialize_facet_check_exact(stl_file *stl) { int i; @@ -152,10 +165,9 @@ stl_initialize_facet_check_exact(stl_file *stl) { stl->stats.freed = 0; stl->stats.collisions = 0; + stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); - stl->M = 81397; - - for(i = 0; i < stl->stats.number_of_facets ; i++) { + for (i = 0; i < stl->stats.number_of_facets ; i++) { /* initialize neighbors list to -1 to mark unconnected edges */ stl->neighbors_start[i].neighbor[0] = -1; stl->neighbors_start[i].neighbor[1] = -1; @@ -296,11 +308,11 @@ static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex * ((vertex1[1] != vertex2[1]) ? (vertex1[1] < vertex2[1]) : (vertex1[2] < vertex2[2]))) { - memcpy(&edge->key[0], vertex1.data(), sizeof(stl_vertex)); - memcpy(&edge->key[sizeof(stl_vertex)], vertex2.data(), sizeof(stl_vertex)); + memcpy(&edge->key[0], vertex1.data(), sizeof(stl_vertex)); + memcpy(&edge->key[3], vertex2.data(), sizeof(stl_vertex)); } else { - memcpy(&edge->key[0], vertex2.data(), sizeof(stl_vertex)); - memcpy(&edge->key[sizeof(stl_vertex)], vertex1.data(), sizeof(stl_vertex)); + memcpy(&edge->key[0], vertex2.data(), sizeof(stl_vertex)); + memcpy(&edge->key[3], vertex1.data(), sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } return 1; @@ -338,7 +350,7 @@ static void stl_initialize_facet_check_nearby(stl_file *stl) /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ /* tolerance *= 0.5;*/ - stl->M = 81397; + stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 096430d15..9c71f00f6 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -65,11 +65,11 @@ typedef struct { typedef struct stl_hash_edge { // Key of a hash edge: sorted vertices of the edge. - unsigned char key[2 * sizeof(stl_vertex)]; + uint32_t key[6]; // Compare two keys. bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } - int hash(int M) const { return ((key[0] / 23 + key[1] / 19 + key[2] / 17 + key[3] /13 + key[4] / 11 + key[5] / 7 ) % M); } + int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } // Index of a facet owning this edge. int facet_number; // Index of this edge inside the facet with an index of facet_number. @@ -173,7 +173,7 @@ extern void stl_mirror_xy(stl_file *stl); extern void stl_mirror_yz(stl_file *stl); extern void stl_mirror_xz(stl_file *stl); extern void stl_transform(stl_file *stl, float *trafo3x4); -extern void stl_transform(stl_file *stl, const Eigen::Transform& t); +extern void stl_transform(stl_file *stl, const Eigen::Transform& t); extern void stl_open_merge(stl_file *stl, char *file); extern void stl_invalidate_shared_vertices(stl_file *stl); extern void stl_generate_shared_vertices(stl_file *stl); @@ -189,7 +189,7 @@ inline void stl_normalize_vector(stl_normal &normal) { if (length < 0.000000000001) normal = stl_normal::Zero(); else - normal *= (1.0 / length); + normal *= float(1.0 / length); } inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { return (a(0) != b(0)) ? (a(0) < b(0)) : diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index cc104fdd1..7cb69bccd 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -155,7 +155,7 @@ void stl_transform(stl_file *stl, float *trafo3x4) { calculate_normals(stl); } -void stl_transform(stl_file *stl, const Eigen::Transform& t) +void stl_transform(stl_file *stl, const Eigen::Transform& t) { if (stl->error) return; @@ -178,7 +178,7 @@ void stl_transform(stl_file *stl, const Eigen::Transform() * src_vertices.colwise().homogeneous(); facet_ptr = stl->facet_start; v_id = 0; diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index d88563368..ad0835ec0 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -66,7 +66,7 @@ set(AVRDUDE_SOURCES avrdude-slic3r.hpp avrdude-slic3r.cpp ) -if (WIN32) +if (MSVC) set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES} windows/unistd.cpp windows/getopt.c diff --git a/src/avrdude/main.c b/src/avrdude/main.c index ebda0ba19..9ada27be3 100644 --- a/src/avrdude/main.c +++ b/src/avrdude/main.c @@ -43,7 +43,7 @@ #include #include -#if !defined(WIN32NATIVE) +#if !defined(WIN32NATIVE) || defined(__GNUC__) # include # include # include diff --git a/src/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h b/src/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h index 822cf32c3..6f1ae0037 100644 --- a/src/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h +++ b/src/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h @@ -146,7 +146,7 @@ void SparseLUImpl::panel_bmod(const Index m, const Index w, Index ldl = internal::first_multiple(nrow, PacketSize); Index offset = (PacketSize-internal::first_default_aligned(B.data(), PacketSize)) % PacketSize; - MappedMatrixBlock L(tempv.data()+w*ldu+offset, nrow, u_cols, OuterStride<>(ldl)); + auto L = MappedMatrixBlock(tempv.data()+w*ldu+offset, nrow, u_cols, OuterStride<>(ldl)); L.setZero(); internal::sparselu_gemm(L.rows(), L.cols(), B.cols(), B.data(), B.outerStride(), U.data(), U.outerStride(), L.data(), L.outerStride()); diff --git a/src/igl/AABB.cpp b/src/igl/AABB.cpp new file mode 100644 index 000000000..1b88e9faa --- /dev/null +++ b/src/igl/AABB.cpp @@ -0,0 +1,1076 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "AABB.h" +#include "EPS.h" +#include "barycenter.h" +#include "colon.h" +#include "doublearea.h" +#include "point_simplex_squared_distance.h" +#include "project_to_line_segment.h" +#include "sort.h" +#include "volume.h" +#include "ray_box_intersect.h" +#include "parallel_for.h" +#include "ray_mesh_intersect.h" +#include +#include +#include +#include +#include +#include + +template +template +IGL_INLINE void igl::AABB::init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & bb_mins, + const Eigen::MatrixBase & bb_maxs, + const Eigen::MatrixBase & elements, + const int i) +{ + using namespace std; + using namespace Eigen; + deinit(); + if(bb_mins.size() > 0) + { + assert(bb_mins.rows() == bb_maxs.rows() && "Serial tree arrays must match"); + assert(bb_mins.cols() == V.cols() && "Serial tree array dim must match V"); + assert(bb_mins.cols() == bb_maxs.cols() && "Serial tree arrays must match"); + assert(bb_mins.rows() == elements.rows() && + "Serial tree arrays must match"); + // construct from serialization + m_box.extend(bb_mins.row(i).transpose()); + m_box.extend(bb_maxs.row(i).transpose()); + m_primitive = elements(i); + // Not leaf then recurse + if(m_primitive == -1) + { + m_left = new AABB(); + m_left->init( V,Ele,bb_mins,bb_maxs,elements,2*i+1); + m_right = new AABB(); + m_right->init( V,Ele,bb_mins,bb_maxs,elements,2*i+2); + //m_depth = std::max( m_left->m_depth, m_right->m_depth)+1; + } + }else + { + VectorXi allI = colon(0,Ele.rows()-1); + MatrixXDIMS BC; + if(Ele.cols() == 1) + { + // points + BC = V; + }else + { + // Simplices + barycenter(V,Ele,BC); + } + MatrixXi SI(BC.rows(),BC.cols()); + { + MatrixXDIMS _; + MatrixXi IS; + igl::sort(BC,1,true,_,IS); + // Need SI(i) to tell which place i would be sorted into + const int dim = IS.cols(); + for(int i = 0;i +template +void igl::AABB::init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele) +{ + using namespace Eigen; + // deinit will be immediately called... + return init(V,Ele,MatrixXDIMS(),MatrixXDIMS(),VectorXi(),0); +} + + template +template < + typename DerivedEle, + typename DerivedSI, + typename DerivedI> +IGL_INLINE void igl::AABB::init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & SI, + const Eigen::MatrixBase & I) +{ + using namespace Eigen; + using namespace std; + deinit(); + if(V.size() == 0 || Ele.size() == 0 || I.size() == 0) + { + return; + } + assert(DIM == V.cols() && "V.cols() should matched declared dimension"); + //const Scalar inf = numeric_limits::infinity(); + m_box = AlignedBox(); + // Compute bounding box + for(int i = 0;iint + { + size_t n = (A.size()-1)/2; + nth_element(A.data(),A.data()+n,A.data()+A.size()); + return A(n); + }; + const int med = median(SIdI); + VectorXi LI((I.rows()+1)/2),RI(I.rows()/2); + assert(LI.rows()+RI.rows() == I.rows()); + // Distribute left and right + { + int li = 0; + int ri = 0; + for(int i = 0;i0) + { + m_left = new AABB(); + m_left->init(V,Ele,SI,LI); + //m_depth = std::max(m_depth, m_left->m_depth+1); + } + if(RI.rows()>0) + { + m_right = new AABB(); + m_right->init(V,Ele,SI,RI); + //m_depth = std::max(m_depth, m_right->m_depth+1); + } + } + } +} + +template +IGL_INLINE bool igl::AABB::is_leaf() const +{ + return m_primitive != -1; +} + +template +template +IGL_INLINE std::vector igl::AABB::find( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & q, + const bool first) const +{ + using namespace std; + using namespace Eigen; + assert(q.size() == DIM && + "Query dimension should match aabb dimension"); + assert(Ele.cols() == V.cols()+1 && + "AABB::find only makes sense for (d+1)-simplices"); + const Scalar epsilon = igl::EPS(); + // Check if outside bounding box + bool inside = m_box.contains(q.transpose()); + if(!inside) + { + return std::vector(); + } + assert(m_primitive==-1 || (m_left == NULL && m_right == NULL)); + if(is_leaf()) + { + // Initialize to some value > -epsilon + Scalar a1=0,a2=0,a3=0,a4=0; + switch(DIM) + { + case 3: + { + // Barycentric coordinates + typedef Eigen::Matrix RowVector3S; + const RowVector3S V1 = V.row(Ele(m_primitive,0)); + const RowVector3S V2 = V.row(Ele(m_primitive,1)); + const RowVector3S V3 = V.row(Ele(m_primitive,2)); + const RowVector3S V4 = V.row(Ele(m_primitive,3)); + a1 = volume_single(V2,V4,V3,(RowVector3S)q); + a2 = volume_single(V1,V3,V4,(RowVector3S)q); + a3 = volume_single(V1,V4,V2,(RowVector3S)q); + a4 = volume_single(V1,V2,V3,(RowVector3S)q); + break; + } + case 2: + { + // Barycentric coordinates + typedef Eigen::Matrix Vector2S; + const Vector2S V1 = V.row(Ele(m_primitive,0)); + const Vector2S V2 = V.row(Ele(m_primitive,1)); + const Vector2S V3 = V.row(Ele(m_primitive,2)); + // Hack for now to keep templates simple. If becomes bottleneck + // consider using std::enable_if_t + const Vector2S q2 = q.head(2); + a1 = doublearea_single(V1,V2,q2); + a2 = doublearea_single(V2,V3,q2); + a3 = doublearea_single(V3,V1,q2); + break; + } + default:assert(false); + } + // Normalization is important for correcting sign + Scalar sum = a1+a2+a3+a4; + a1 /= sum; + a2 /= sum; + a3 /= sum; + a4 /= sum; + if( + a1>=-epsilon && + a2>=-epsilon && + a3>=-epsilon && + a4>=-epsilon) + { + return std::vector(1,m_primitive); + }else + { + return std::vector(); + } + } + std::vector left = m_left->find(V,Ele,q,first); + if(first && !left.empty()) + { + return left; + } + std::vector right = m_right->find(V,Ele,q,first); + if(first) + { + return right; + } + left.insert(left.end(),right.begin(),right.end()); + return left; +} + +template +IGL_INLINE int igl::AABB::subtree_size() const +{ + // 1 for self + int n = 1; + int n_left = 0,n_right = 0; + if(m_left != NULL) + { + n_left = m_left->subtree_size(); + } + if(m_right != NULL) + { + n_right = m_right->subtree_size(); + } + n += 2*std::max(n_left,n_right); + return n; +} + + +template +template +IGL_INLINE void igl::AABB::serialize( + Eigen::PlainObjectBase & bb_mins, + Eigen::PlainObjectBase & bb_maxs, + Eigen::PlainObjectBase & elements, + const int i) const +{ + using namespace std; + using namespace Eigen; + // Calling for root then resize output + if(i==0) + { + const int m = subtree_size(); + //cout<<"m: "<serialize(bb_mins,bb_maxs,elements,2*i+1); + } + if(m_right != NULL) + { + m_right->serialize(bb_mins,bb_maxs,elements,2*i+2); + } +} + +template +template +IGL_INLINE typename igl::AABB::Scalar +igl::AABB::squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + int & i, + Eigen::PlainObjectBase & c) const +{ + return squared_distance(V,Ele,p,std::numeric_limits::infinity(),i,c); +} + + +template +template +IGL_INLINE typename igl::AABB::Scalar +igl::AABB::squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + Scalar low_sqr_d, + Scalar up_sqr_d, + int & i, + Eigen::PlainObjectBase & c) const +{ + using namespace Eigen; + using namespace std; + //assert(low_sqr_d <= up_sqr_d); + if(low_sqr_d > up_sqr_d) + { + return low_sqr_d; + } + Scalar sqr_d = up_sqr_d; + //assert(DIM == 3 && "Code has only been tested for DIM == 3"); + assert((Ele.cols() == 3 || Ele.cols() == 2 || Ele.cols() == 1) + && "Code has only been tested for simplex sizes 3,2,1"); + + assert(m_primitive==-1 || (m_left == NULL && m_right == NULL)); + if(is_leaf()) + { + leaf_squared_distance(V,Ele,p,low_sqr_d,sqr_d,i,c); + }else + { + bool looked_left = false; + bool looked_right = false; + const auto & look_left = [&]() + { + int i_left; + RowVectorDIMS c_left = c; + Scalar sqr_d_left = + m_left->squared_distance(V,Ele,p,low_sqr_d,sqr_d,i_left,c_left); + this->set_min(p,sqr_d_left,i_left,c_left,sqr_d,i,c); + looked_left = true; + }; + const auto & look_right = [&]() + { + int i_right; + RowVectorDIMS c_right = c; + Scalar sqr_d_right = + m_right->squared_distance(V,Ele,p,low_sqr_d,sqr_d,i_right,c_right); + this->set_min(p,sqr_d_right,i_right,c_right,sqr_d,i,c); + looked_right = true; + }; + + // must look left or right if in box + if(m_left->m_box.contains(p.transpose())) + { + look_left(); + } + if(m_right->m_box.contains(p.transpose())) + { + look_right(); + } + // if haven't looked left and could be less than current min, then look + Scalar left_up_sqr_d = + m_left->m_box.squaredExteriorDistance(p.transpose()); + Scalar right_up_sqr_d = + m_right->m_box.squaredExteriorDistance(p.transpose()); + if(left_up_sqr_d < right_up_sqr_d) + { + if(!looked_left && left_up_sqr_d +template +IGL_INLINE typename igl::AABB::Scalar +igl::AABB::squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + Scalar up_sqr_d, + int & i, + Eigen::PlainObjectBase & c) const +{ + return squared_distance(V,Ele,p,0.0,up_sqr_d,i,c); +} + +template +template < + typename DerivedEle, + typename DerivedP, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::AABB::squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & P, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const +{ + assert(P.cols() == V.cols() && "cols in P should match dim of cols in V"); + sqrD.resize(P.rows(),1); + I.resize(P.rows(),1); + C.resizeLike(P); + // O( #P * log #Ele ), where log #Ele is really the depth of this AABB + // hierarchy + //for(int p = 0;p +template < + typename DerivedEle, + typename Derivedother_V, + typename Derivedother_Ele, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::AABB::squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const AABB & other, + const Eigen::MatrixBase & other_V, + const Eigen::MatrixBase & other_Ele, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const +{ + assert(other_Ele.cols() == 1 && + "Only implemented for other as list of points"); + assert(other_V.cols() == V.cols() && "other must match this dimension"); + sqrD.setConstant(other_Ele.rows(),1,std::numeric_limits::infinity()); + I.resize(other_Ele.rows(),1); + C.resize(other_Ele.rows(),other_V.cols()); + // All points in other_V currently think they need to check against root of + // this. The point of using another AABB is to quickly prune chunks of + // other_V so that most points just check some subtree of this. + + // This holds a conservative estimate of max(sqr_D) where sqr_D is the + // current best minimum squared distance for all points in this subtree + double up_sqr_d = std::numeric_limits::infinity(); + squared_distance_helper( + V,Ele,&other,other_V,other_Ele,0,up_sqr_d,sqrD,I,C); +} + +template +template < + typename DerivedEle, + typename Derivedother_V, + typename Derivedother_Ele, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE typename igl::AABB::Scalar + igl::AABB::squared_distance_helper( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const AABB * other, + const Eigen::MatrixBase & other_V, + const Eigen::MatrixBase & other_Ele, + const Scalar /*up_sqr_d*/, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const +{ + using namespace std; + using namespace Eigen; + + // This implementation is a bit disappointing. There's no major speed up. Any + // performance gains seem to come from accidental cache coherency and + // diminish for larger "other" (the opposite of what was intended). + + // Base case + if(other->is_leaf() && this->is_leaf()) + { + Scalar sqr_d = sqrD(other->m_primitive); + int i = I(other->m_primitive); + RowVectorDIMS c = C.row( other->m_primitive); + RowVectorDIMS p = other_V.row(other->m_primitive); + leaf_squared_distance(V,Ele,p,sqr_d,i,c); + sqrD( other->m_primitive) = sqr_d; + I( other->m_primitive) = i; + C.row(other->m_primitive) = c; + //cout<<"leaf: "<m_low_sqr_d = sqr_d; + return sqr_d; + } + + if(other->is_leaf()) + { + Scalar sqr_d = sqrD(other->m_primitive); + int i = I(other->m_primitive); + RowVectorDIMS c = C.row( other->m_primitive); + RowVectorDIMS p = other_V.row(other->m_primitive); + sqr_d = squared_distance(V,Ele,p,sqr_d,i,c); + sqrD( other->m_primitive) = sqr_d; + I( other->m_primitive) = i; + C.row(other->m_primitive) = c; + //other->m_low_sqr_d = sqr_d; + return sqr_d; + } + + //// Exact minimum squared distance between arbitrary primitives inside this and + //// othre's bounding boxes + //const auto & min_squared_distance = [&]( + // const AABB * A, + // const AABB * B)->Scalar + //{ + // return A->m_box.squaredExteriorDistance(B->m_box); + //}; + + if(this->is_leaf()) + { + //if(min_squared_distance(this,other) < other->m_low_sqr_d) + if(true) + { + this->squared_distance_helper( + V,Ele,other->m_left,other_V,other_Ele,0,sqrD,I,C); + this->squared_distance_helper( + V,Ele,other->m_right,other_V,other_Ele,0,sqrD,I,C); + }else + { + // This is never reached... + } + //// we know other is not a leaf + //other->m_low_sqr_d = std::max(other->m_left->m_low_sqr_d,other->m_right->m_low_sqr_d); + return 0; + } + + // FORCE DOWN TO OTHER LEAF EVAL + //if(min_squared_distance(this,other) < other->m_low_sqr_d) + if(true) + { + if(true) + { + this->squared_distance_helper( + V,Ele,other->m_left,other_V,other_Ele,0,sqrD,I,C); + this->squared_distance_helper( + V,Ele,other->m_right,other_V,other_Ele,0,sqrD,I,C); + }else // this direction never seems to be faster + { + this->m_left->squared_distance_helper( + V,Ele,other,other_V,other_Ele,0,sqrD,I,C); + this->m_right->squared_distance_helper( + V,Ele,other,other_V,other_Ele,0,sqrD,I,C); + } + }else + { + // this is never reached ... :-( + } + //// we know other is not a leaf + //other->m_low_sqr_d = std::max(other->m_left->m_low_sqr_d,other->m_right->m_low_sqr_d); + + return 0; +#if 0 // False + + // _Very_ conservative approximation of maximum squared distance between + // primitives inside this and other's bounding boxes + const auto & max_squared_distance = []( + const AABB * A, + const AABB * B)->Scalar + { + AlignedBox combo = A->m_box; + combo.extend(B->m_box); + return combo.diagonal().squaredNorm(); + }; + + //// other base-case + //if(other->is_leaf()) + //{ + // double sqr_d = sqrD(other->m_primitive); + // int i = I(other->m_primitive); + // RowVectorDIMS c = C.row(m_primitive); + // RowVectorDIMS p = other_V.row(m_primitive); + // leaf_squared_distance(V,Ele,p,sqr_d,i,c); + // sqrD(other->m_primitive) = sqr_d; + // I(other->m_primitive) = i; + // C.row(m_primitive) = c; + // return; + //} + std::vector * > this_list; + if(this->is_leaf()) + { + this_list.push_back(this); + }else + { + assert(this->m_left); + this_list.push_back(this->m_left); + assert(this->m_right); + this_list.push_back(this->m_right); + } + std::vector *> other_list; + if(other->is_leaf()) + { + other_list.push_back(other); + }else + { + assert(other->m_left); + other_list.push_back(other->m_left); + assert(other->m_right); + other_list.push_back(other->m_right); + } + + //const std::function * other) + // > low_sqr_d = [&sqrD,&low_sqr_d](const AABB * other)->Scalar + // { + // if(other->is_leaf()) + // { + // return sqrD(other->m_primitive); + // }else + // { + // return std::max(low_sqr_d(other->m_left),low_sqr_d(other->m_right)); + // } + // }; + + //// Potentially recurse on all pairs, if minimum distance is less than running + //// bound + //Eigen::Matrix other_low_sqr_d = + // Eigen::Matrix::Constant(other_list.size(),1,up_sqr_d); + for(size_t child = 0;child this_low_sqr_d(this_list.size(),1); + for(size_t t = 0;t this_low_sqr_d(1)) + ) + { + std::swap(this_list[0],this_list[1]); + //std::swap(this_low_sqr_d(0),this_low_sqr_d(1)); + } + const Scalar sqr_d = this_low_sqr_d.minCoeff(); + + + for(size_t t = 0;tm_low_sqr_d) + { + //cout<<"before: "<squared_distance_helper( + // V,Ele,other_tree,other_V,other_Ele,other_low_sqr_d(child),sqrD,I,C)); + //cout<<"after: "<squared_distance_helper( + V,Ele,other_tree,other_V,other_Ele,0,sqrD,I,C); + } + } + } + //const Scalar ret = other_low_sqr_d.maxCoeff(); + //const auto mm = low_sqr_d(other); + //assert(mm == ret); + //cout<<"non-leaf: "<is_leaf()) + { + other->m_low_sqr_d = std::max(other->m_left->m_low_sqr_d,other->m_right->m_low_sqr_d); + } + return 0; +#endif +} + +template +template +IGL_INLINE void igl::AABB::leaf_squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + const Scalar low_sqr_d, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const +{ + using namespace Eigen; + using namespace std; + if(low_sqr_d > sqr_d) + { + sqr_d = low_sqr_d; + return; + } + RowVectorDIMS c_candidate; + Scalar sqr_d_candidate; + igl::point_simplex_squared_distance( + p,V,Ele,m_primitive,sqr_d_candidate,c_candidate); + set_min(p,sqr_d_candidate,m_primitive,c_candidate,sqr_d,i,c); +} + +template +template +IGL_INLINE void igl::AABB::leaf_squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const +{ + return leaf_squared_distance(V,Ele,p,0,sqr_d,i,c); +} + + +template +IGL_INLINE void igl::AABB::set_min( + const RowVectorDIMS & +#ifndef NDEBUG + p +#endif + , + const Scalar sqr_d_candidate, + const int i_candidate, + const RowVectorDIMS & c_candidate, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const +{ +#ifndef NDEBUG + //std::cout< +template +IGL_INLINE bool +igl::AABB::intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + std::vector & hits) const +{ + hits.clear(); + const Scalar t0 = 0; + const Scalar t1 = std::numeric_limits::infinity(); + { + Scalar _1,_2; + if(!ray_box_intersect(origin,dir,m_box,t0,t1,_1,_2)) + { + return false; + } + } + if(this->is_leaf()) + { + // Actually process elements + assert((Ele.size() == 0 || Ele.cols() == 3) && "Elements should be triangles"); + // Cheesecake way of hitting element + bool ret = ray_mesh_intersect(origin,dir,V,Ele.row(m_primitive),hits); + // Since we only gave ray_mesh_intersect a single face, it will have set + // any hits to id=0. Set these to this primitive's id + for(auto & hit : hits) + { + hit.id = m_primitive; + } + return ret; + } + std::vector left_hits; + std::vector right_hits; + const bool left_ret = m_left->intersect_ray(V,Ele,origin,dir,left_hits); + const bool right_ret = m_right->intersect_ray(V,Ele,origin,dir,right_hits); + hits.insert(hits.end(),left_hits.begin(),left_hits.end()); + hits.insert(hits.end(),right_hits.begin(),right_hits.end()); + return left_ret || right_ret; +} + +template +template +IGL_INLINE bool +igl::AABB::intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + igl::Hit & hit) const +{ + // FIXME: Needs a proper path +#if /*false*/ 0 + // BFS + std::queue Q; + // Or DFS + //std::stack Q; + Q.push(this); + bool any_hit = false; + hit.t = std::numeric_limits::infinity(); + while(!Q.empty()) + { + const AABB * tree = Q.front(); + //const AABB * tree = Q.top(); + Q.pop(); + { + Scalar _1,_2; + if(!ray_box_intersect( + origin,dir,tree->m_box,Scalar(0),Scalar(hit.t),_1,_2)) + { + continue; + } + } + if(tree->is_leaf()) + { + // Actually process elements + assert((Ele.size() == 0 || Ele.cols() == 3) && "Elements should be triangles"); + igl::Hit leaf_hit; + if( + ray_mesh_intersect(origin,dir,V,Ele.row(tree->m_primitive),leaf_hit)&& + leaf_hit.t < hit.t) + { + // correct the id + leaf_hit.id = tree->m_primitive; + hit = leaf_hit; + } + continue; + } + // Add children to queue + Q.push(tree->m_left); + Q.push(tree->m_right); + } + return any_hit; +#else + // DFS + return intersect_ray( + V,Ele,origin,dir,std::numeric_limits::infinity(),hit); +#endif +} + +template +template +IGL_INLINE bool +igl::AABB::intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + const Scalar _min_t, + igl::Hit & hit) const +{ + //// Naive, slow + //std::vector hits; + //intersect_ray(V,Ele,origin,dir,hits); + //if(hits.size() > 0) + //{ + // hit = hits.front(); + // return true; + //}else + //{ + // return false; + //} + Scalar min_t = _min_t; + const Scalar t0 = 0; + { + Scalar _1,_2; + if(!ray_box_intersect(origin,dir,m_box,t0,min_t,_1,_2)) + { + return false; + } + } + if(this->is_leaf()) + { + // Actually process elements + assert((Ele.size() == 0 || Ele.cols() == 3) && "Elements should be triangles"); + // Cheesecake way of hitting element + bool ret = ray_mesh_intersect(origin,dir,V,Ele.row(m_primitive),hit); + hit.id = m_primitive; + return ret; + } + + // Doesn't seem like smartly choosing left before/after right makes a + // differnce + igl::Hit left_hit; + igl::Hit right_hit; + bool left_ret = m_left->intersect_ray(V,Ele,origin,dir,min_t,left_hit); + if(left_ret && left_hit.tintersect_ray(V,Ele,origin,dir,min_t,right_hit); + if(right_ret && right_hit.t template<> IGL_INLINE float AABB, 2>::squared_distance( Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const { assert(false);return -1;}; + template<> template<> IGL_INLINE float igl::AABB, 2>::squared_distance( Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, float, float, int&, Eigen::PlainObjectBase >&) const { assert(false);return -1;}; + template<> template<> IGL_INLINE void igl::AABB, 2>::init (Eigen::MatrixBase > const&, Eigen::MatrixBase > const&) { assert(false);}; + template<> template<> IGL_INLINE double AABB, 2>::squared_distance( Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const { assert(false);return -1;}; + template<> template<> IGL_INLINE double igl::AABB, 2>::squared_distance( Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, double, int&, Eigen::PlainObjectBase >&) const { assert(false);return -1;}; + template<> template<> IGL_INLINE void igl::AABB, 2>::init (Eigen::MatrixBase > const&, Eigen::MatrixBase > const&) { assert(false);}; + template<> template<> IGL_INLINE void igl::AABB, 2>::init(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&) {assert(false);}; + template<> template<> IGL_INLINE float igl::AABB, 2>::squared_distance(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, float, float, int&, Eigen::PlainObjectBase >&) const { assert(false);return -1;}; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, double, int&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template float igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, float, float, int&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template void igl::AABB, 3>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template void igl::AABB, 3>::serialize, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, int) const; +// generated by autoexplicit.sh +template std::vector > igl::AABB, 3>::find, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool) const; +// generated by autoexplicit.sh +template void igl::AABB, 2>::serialize, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, int) const; +// generated by autoexplicit.sh +template std::vector > igl::AABB, 2>::find, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::init, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int); +// generated by autoexplicit.sh +template void igl::AABB, 2>::init, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int); +// generated by autoexplicit.sh +template float igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 2>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 2>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 2>::squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template double igl::AABB, 2>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; +// generated by autoexplicit.sh +template void igl::AABB, 3>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template void igl::AABB, 3>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template void igl::AABB, 2>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, int&, Eigen::PlainObjectBase >&) const; +template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::Hit&) const; +#endif diff --git a/src/igl/AABB.h b/src/igl/AABB.h new file mode 100644 index 000000000..69bc60c2e --- /dev/null +++ b/src/igl/AABB.h @@ -0,0 +1,416 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AABB_H +#define IGL_AABB_H + +#include "Hit.h" +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Implementation of semi-general purpose axis-aligned bounding box hierarchy. + // The mesh (V,Ele) is stored and managed by the caller and each routine here + // simply takes it as references (it better not change between calls). + // + // It's a little annoying that the Dimension is a template parameter and not + // picked up at run time from V. This leads to duplicated code for 2d/3d (up to + // dim). + template + class AABB + { +public: + typedef typename DerivedV::Scalar Scalar; + typedef Eigen::Matrix RowVectorDIMS; + typedef Eigen::Matrix VectorDIMS; + typedef Eigen::Matrix MatrixXDIMS; + // Shared pointers are slower... + AABB * m_left; + AABB * m_right; + Eigen::AlignedBox m_box; + // -1 non-leaf + int m_primitive; + //Scalar m_low_sqr_d; + //int m_depth; + AABB(): + m_left(NULL), m_right(NULL), + m_box(), m_primitive(-1) + //m_low_sqr_d(std::numeric_limits::infinity()), + //m_depth(0) + {} + // http://stackoverflow.com/a/3279550/148668 + AABB(const AABB& other): + m_left(other.m_left ? new AABB(*other.m_left) : NULL), + m_right(other.m_right ? new AABB(*other.m_right) : NULL), + m_box(other.m_box), + m_primitive(other.m_primitive) + //m_low_sqr_d(other.m_low_sqr_d), + //m_depth(std::max( + // m_left ? m_left->m_depth + 1 : 0, + // m_right ? m_right->m_depth + 1 : 0)) + { + } + // copy-swap idiom + friend void swap(AABB& first, AABB& second) + { + // Enable ADL + using std::swap; + swap(first.m_left,second.m_left); + swap(first.m_right,second.m_right); + swap(first.m_box,second.m_box); + swap(first.m_primitive,second.m_primitive); + //swap(first.m_low_sqr_d,second.m_low_sqr_d); + //swap(first.m_depth,second.m_depth); + } + AABB& operator=(const AABB &other) + { + this->deinit(); + m_left = other.m_left ? new AABB(*other.m_left) : NULL; + m_right = other.m_right ? new AABB(*other.m_right) : NULL; + m_box = other.m_box; + m_primitive = other.m_primitive; + return *this; + } + AABB(AABB&& other): + // initialize via default constructor + AABB() + { + swap(*this,other); + } + // Seems like there should have been an elegant solution to this using + // the copy-swap idiom above: + IGL_INLINE void deinit() + { + m_primitive = -1; + m_box = Eigen::AlignedBox(); + delete m_left; + m_left = NULL; + delete m_right; + m_right = NULL; + } + ~AABB() + { + deinit(); + } + // Build an Axis-Aligned Bounding Box tree for a given mesh and given + // serialization of a previous AABB tree. + // + // Inputs: + // V #V by dim list of mesh vertex positions. + // Ele #Ele by dim+1 list of mesh indices into #V. + // bb_mins max_tree by dim list of bounding box min corner positions + // bb_maxs max_tree by dim list of bounding box max corner positions + // elements max_tree list of element or (not leaf id) indices into Ele + // i recursive call index {0} + template < + typename DerivedEle, + typename Derivedbb_mins, + typename Derivedbb_maxs, + typename Derivedelements> + IGL_INLINE void init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & bb_mins, + const Eigen::MatrixBase & bb_maxs, + const Eigen::MatrixBase & elements, + const int i = 0); + // Wrapper for root with empty serialization + template + IGL_INLINE void init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele); + // Build an Axis-Aligned Bounding Box tree for a given mesh. + // + // Inputs: + // V #V by dim list of mesh vertex positions. + // Ele #Ele by dim+1 list of mesh indices into #V. + // SI #Ele by dim list revealing for each coordinate where Ele's + // barycenters would be sorted: SI(e,d) = i --> the dth coordinate of + // the barycenter of the eth element would be placed at position i in a + // sorted list. + // I #I list of indices into Ele of elements to include (for recursive + // calls) + // + template + IGL_INLINE void init( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & SI, + const Eigen::MatrixBase& I); + // Return whether at leaf node + IGL_INLINE bool is_leaf() const; + // Find the indices of elements containing given point: this makes sense + // when Ele is a co-dimension 0 simplex (tets in 3D, triangles in 2D). + // + // Inputs: + // V #V by dim list of mesh vertex positions. **Should be same as used to + // construct mesh.** + // Ele #Ele by dim+1 list of mesh indices into #V. **Should be same as used to + // construct mesh.** + // q dim row-vector query position + // first whether to only return first element containing q + // Returns: + // list of indices of elements containing q + template + IGL_INLINE std::vector find( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & q, + const bool first=false) const; + + // If number of elements m then total tree size should be 2*h where h is + // the deepest depth 2^ceil(log(#Ele*2-1)) + IGL_INLINE int subtree_size() const; + + // Serialize this class into 3 arrays (so we can pass it pack to matlab) + // + // Outputs: + // bb_mins max_tree by dim list of bounding box min corner positions + // bb_maxs max_tree by dim list of bounding box max corner positions + // elements max_tree list of element or (not leaf id) indices into Ele + // i recursive call index into these arrays {0} + template < + typename Derivedbb_mins, + typename Derivedbb_maxs, + typename Derivedelements> + IGL_INLINE void serialize( + Eigen::PlainObjectBase & bb_mins, + Eigen::PlainObjectBase & bb_maxs, + Eigen::PlainObjectBase & elements, + const int i = 0) const; + // Compute squared distance to a query point + // + // Inputs: + // V #V by dim list of vertex positions + // Ele #Ele by dim list of simplex indices + // p dim-long query point + // Outputs: + // i facet index corresponding to smallest distances + // c closest point + // Returns squared distance + // + // Known bugs: currently assumes Elements are triangles regardless of + // dimension. + template + IGL_INLINE Scalar squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + int & i, + Eigen::PlainObjectBase & c) const; +//private: + // Compute squared distance to a query point + // + // Inputs: + // V #V by dim list of vertex positions + // Ele #Ele by dim list of simplex indices + // p dim-long query point + // low_sqr_d lower bound on squared distance, specified maximum squared + // distance + // up_sqr_d current upper bounded on squared distance, current minimum + // squared distance (only consider distances less than this), see + // output. + // Outputs: + // up_sqr_d updated current minimum squared distance + // i facet index corresponding to smallest distances + // c closest point + // Returns squared distance + // + // Known bugs: currently assumes Elements are triangles regardless of + // dimension. + template + IGL_INLINE Scalar squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + const Scalar low_sqr_d, + const Scalar up_sqr_d, + int & i, + Eigen::PlainObjectBase & c) const; + // Default low_sqr_d + template + IGL_INLINE Scalar squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + const Scalar up_sqr_d, + int & i, + Eigen::PlainObjectBase & c) const; + // All hits + template + IGL_INLINE bool intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + std::vector & hits) const; + // First hit + template + IGL_INLINE bool intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + igl::Hit & hit) const; +//private: + template + IGL_INLINE bool intersect_ray( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & origin, + const RowVectorDIMS & dir, + const Scalar min_t, + igl::Hit & hit) const; + + +public: + // Compute the squared distance from all query points in P to the + // _closest_ points on the primitives stored in the AABB hierarchy for + // the mesh (V,Ele). + // + // Inputs: + // V #V by dim list of vertex positions + // Ele #Ele by dim list of simplex indices + // P #P by dim list of query points + // Outputs: + // sqrD #P list of squared distances + // I #P list of indices into Ele of closest primitives + // C #P by dim list of closest points + template < + typename DerivedEle, + typename DerivedP, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE void squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const Eigen::MatrixBase & P, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const; + + // Compute the squared distance from all query points in P already stored + // in its own AABB hierarchy to the _closest_ points on the primitives + // stored in the AABB hierarchy for the mesh (V,Ele). + // + // Inputs: + // V #V by dim list of vertex positions + // Ele #Ele by dim list of simplex indices + // other AABB hierarchy of another set of primitives (must be points) + // other_V #other_V by dim list of query points + // other_Ele #other_Ele by ss list of simplex indices into other_V + // (must be simple list of points: ss == 1) + // Outputs: + // sqrD #P list of squared distances + // I #P list of indices into Ele of closest primitives + // C #P by dim list of closest points + template < + typename DerivedEle, + typename Derivedother_V, + typename Derivedother_Ele, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE void squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const AABB & other, + const Eigen::MatrixBase & other_V, + const Eigen::MatrixBase & other_Ele, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const; +private: + template < + typename DerivedEle, + typename Derivedother_V, + typename Derivedother_Ele, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE Scalar squared_distance_helper( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const AABB * other, + const Eigen::MatrixBase & other_V, + const Eigen::MatrixBase& other_Ele, + const Scalar up_sqr_d, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) const; + // Compute the squared distance to the primitive in this node: assumes + // that this is indeed a leaf node. + // + // Inputs: + // V #V by dim list of vertex positions + // Ele #Ele by dim list of simplex indices + // p dim-long query point + // sqr_d current minimum distance for this query, see output + // i current index into Ele of closest point, see output + // c dim-long current closest point, see output + // Outputs: + // sqr_d minimum of initial value and squared distance to this + // primitive + // i possibly updated index into Ele of closest point + // c dim-long possibly updated closest point + template + IGL_INLINE void leaf_squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + const Scalar low_sqr_d, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const; + // Default low_sqr_d + template + IGL_INLINE void leaf_squared_distance( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const RowVectorDIMS & p, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const; + // If new distance (sqr_d_candidate) is less than current distance + // (sqr_d), then update this distance and its associated values + // _in-place_: + // + // Inputs: + // p dim-long query point (only used in DEBUG mode) + // sqr_d candidate minimum distance for this query, see output + // i candidate index into Ele of closest point, see output + // c dim-long candidate closest point, see output + // sqr_d current minimum distance for this query, see output + // i current index into Ele of closest point, see output + // c dim-long current closest point, see output + // Outputs: + // sqr_d minimum of initial value and squared distance to this + // primitive + // i possibly updated index into Ele of closest point + // c dim-long possibly updated closest point + IGL_INLINE void set_min( + const RowVectorDIMS & p, + const Scalar sqr_d_candidate, + const int i_candidate, + const RowVectorDIMS & c_candidate, + Scalar & sqr_d, + int & i, + Eigen::PlainObjectBase & c) const; +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + }; +} + + +#ifndef IGL_STATIC_LIBRARY +# include "AABB.cpp" +#endif + +#endif diff --git a/src/igl/ARAPEnergyType.h b/src/igl/ARAPEnergyType.h new file mode 100644 index 000000000..68be24f5d --- /dev/null +++ b/src/igl/ARAPEnergyType.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ARAPENERGYTYPE_H +#define IGL_ARAPENERGYTYPE_H +namespace igl +{ + // ARAP_ENERGY_TYPE_SPOKES "As-rigid-as-possible Surface Modeling" by [Sorkine and + // Alexa 2007], rotations defined at vertices affecting incident edges, + // default + // ARAP_ENERGY_TYPE_SPOKES-AND-RIMS Adapted version of "As-rigid-as-possible Surface + // Modeling" by [Sorkine and Alexa 2007] presented in section 4.2 of or + // "A simple geometric model for elastic deformation" by [Chao et al. + // 2010], rotations defined at vertices affecting incident edges and + // opposite edges + // ARAP_ENERGY_TYPE_ELEMENTS "A local-global approach to mesh parameterization" by + // [Liu et al. 2010] or "A simple geometric model for elastic + // deformation" by [Chao et al. 2010], rotations defined at elements + // (triangles or tets) + // ARAP_ENERGY_TYPE_DEFAULT Choose one automatically: spokes and rims + // for surfaces, elements for planar meshes and tets (not fully + // supported) + enum ARAPEnergyType + { + ARAP_ENERGY_TYPE_SPOKES = 0, + ARAP_ENERGY_TYPE_SPOKES_AND_RIMS = 1, + ARAP_ENERGY_TYPE_ELEMENTS = 2, + ARAP_ENERGY_TYPE_DEFAULT = 3, + NUM_ARAP_ENERGY_TYPES = 4 + }; +} +#endif diff --git a/src/igl/AtA_cached.cpp b/src/igl/AtA_cached.cpp new file mode 100644 index 000000000..af7d0ad86 --- /dev/null +++ b/src/igl/AtA_cached.cpp @@ -0,0 +1,130 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "AtA_cached.h" + +#include +#include +#include + +template +IGL_INLINE void igl::AtA_cached_precompute( + const Eigen::SparseMatrix& A, + igl::AtA_cached_data& data, + Eigen::SparseMatrix& AtA) +{ + // 1 Compute At (this could be avoided, but performance-wise it will not make a difference) + std::vector > Col_RowPtr; + std::vector > Col_IndexPtr; + + Col_RowPtr.resize(A.cols()); + Col_IndexPtr.resize(A.cols()); + + for (unsigned k=0; k= 0); + assert(row < A.rows()); + assert(row >= 0); + assert(value_index >= 0); + assert(value_index < A.nonZeros()); + + Col_RowPtr[col].push_back(row); + Col_IndexPtr[col].push_back(value_index); + } + } + + Eigen::SparseMatrix At = A.transpose(); + At.makeCompressed(); + AtA = At * A; + AtA.makeCompressed(); + + assert(AtA.isCompressed()); + + // If weights are not provided, use 1 + if (data.W.size() == 0) + data.W = Eigen::VectorXd::Ones(A.rows()); + assert(data.W.size() == A.rows()); + + data.I_outer.reserve(AtA.outerSize()); + data.I_row.reserve(2*AtA.nonZeros()); + data.I_col.reserve(2*AtA.nonZeros()); + data.I_w.reserve(2*AtA.nonZeros()); + + // 2 Construct the rules + for (unsigned k=0; k= 0); + assert(row < AtA.rows()); + assert(row >= 0); + assert(value_index >= 0); + assert(value_index < AtA.nonZeros()); + + data.I_outer.push_back(data.I_row.size()); + + // Find correspondences + unsigned i=0; + unsigned j=0; + while (i Col_RowPtr[col][j]) + ++j; + else + ++i; + + } + } + } + data.I_outer.push_back(data.I_row.size()); // makes it more efficient to iterate later on + + igl::AtA_cached(A,data,AtA); +} + +template +IGL_INLINE void igl::AtA_cached( + const Eigen::SparseMatrix& A, + const igl::AtA_cached_data& data, + Eigen::SparseMatrix& AtA) +{ + for (unsigned i=0; i(Eigen::SparseMatrix const&, igl::AtA_cached_data const&, Eigen::SparseMatrix&); +template void igl::AtA_cached_precompute(Eigen::SparseMatrix const&, igl::AtA_cached_data&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/AtA_cached.h b/src/igl/AtA_cached.h new file mode 100644 index 000000000..776825411 --- /dev/null +++ b/src/igl/AtA_cached.h @@ -0,0 +1,70 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ATA_CACHED_H +#define IGL_ATA_CACHED_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + struct AtA_cached_data + { + // Weights + Eigen::VectorXd W; + + // Flatten composition rules + std::vector I_row; + std::vector I_col; + std::vector I_w; + + // For each entry of AtA, points to the beginning + // of the composition rules + std::vector I_outer; + }; + + // Computes At * W * A, where A is sparse and W is diagonal. Divides the + // construction in two phases, one + // for fixing the sparsity pattern, and one to populate it with values. Compared to + // evaluating it directly, this version is slower for the first time (since it requires a + // precomputation), but faster to the subsequent evaluations. + // + // Input: + // A m x n sparse matrix + // data stores the precomputed sparsity pattern, data.W contains the optional diagonal weights (stored as a dense vector). If W is not provided, it is replaced by the identity. + // Outputs: + // AtA m by m matrix computed as AtA * W * A + // + // Example: + // AtA_data = igl::AtA_cached_data(); + // AtA_data.W = W; + // if (s.AtA.rows() == 0) + // igl::AtA_cached_precompute(s.A,s.AtA_data,s.AtA); + // else + // igl::AtA_cached(s.A,s.AtA_data,s.AtA); + template + IGL_INLINE void AtA_cached_precompute( + const Eigen::SparseMatrix& A, + AtA_cached_data& data, + Eigen::SparseMatrix& AtA + ); + + template + IGL_INLINE void AtA_cached( + const Eigen::SparseMatrix& A, + const AtA_cached_data& data, + Eigen::SparseMatrix& AtA + ); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "AtA_cached.cpp" +#endif + +#endif diff --git a/src/igl/C_STR.h b/src/igl/C_STR.h new file mode 100644 index 000000000..9844b35a5 --- /dev/null +++ b/src/igl/C_STR.h @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_C_STR_H +#define IGL_C_STR_H +// http://stackoverflow.com/a/2433143/148668 +// Suppose you have a function: +// void func(const char * c); +// Then you can write: +// func(C_STR("foo"<<1<<"bar")); +#include +#include +#define C_STR(X) static_cast(std::ostringstream().flush() << X).str().c_str() +#endif diff --git a/src/igl/Camera.h b/src/igl/Camera.h new file mode 100644 index 000000000..79ebf603e --- /dev/null +++ b/src/igl/Camera.h @@ -0,0 +1,359 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CAMERA_H +#define IGL_CAMERA_H + +// you're idiot, M$! +#if defined(_WIN32) +#undef far +#undef near +#endif + +#include +#include +#include + +#define IGL_CAMERA_MIN_ANGLE 5.0 +namespace igl +{ + + // A simple camera class. The camera stores projection parameters (field of + // view angle, aspect ratio, near and far clips) as well as a rigid + // transformation *of the camera as if it were also a scene object*. Thus, the + // **inverse** of this rigid transformation is the modelview transformation. + class Camera + { + public: + // On windows you might need: -fno-delayed-template-parsing + //static constexpr double IGL_CAMERA_MIN_ANGLE = 5.; + // m_angle Field of view angle in degrees {45} + // m_aspect Aspect ratio {1} + // m_near near clipping plane {1e-2} + // m_far far clipping plane {100} + // m_at_dist distance of looking at point {1} + // m_orthographic whether to use othrographic projection {false} + // m_rotation_conj Conjugate of rotation part of rigid transformation of + // camera {identity}. Note: we purposefully store the conjugate because + // this is what TW_TYPE_QUAT4D is expecting. + // m_translation Translation part of rigid transformation of camera + // {(0,0,1)} + double m_angle, m_aspect, m_near, m_far, m_at_dist; + bool m_orthographic; + Eigen::Quaterniond m_rotation_conj; + Eigen::Vector3d m_translation; + public: + inline Camera(); + inline virtual ~Camera(){} + // Return projection matrix that takes relative camera coordinates and + // transforms it to viewport coordinates + // + // Note: + // + // if(m_angle > 0) + // { + // gluPerspective(m_angle,m_aspect,m_near,m_at_dist+m_far); + // }else + // { + // gluOrtho(-0.5*aspect,0.5*aspect,-0.5,0.5,m_at_dist+m_near,m_far); + // } + // + // Is equivalent to + // + // glMultMatrixd(projection().data()); + // + inline Eigen::Matrix4d projection() const; + // Return an Affine transformation (rigid actually) that + // takes relative coordinates and tramsforms them into world 3d + // coordinates: moves the camera into the scene. + inline Eigen::Affine3d affine() const; + // Return an Affine transformation (rigid actually) that puts the takes a + // world 3d coordinate and transforms it into the relative camera + // coordinates: moves the scene in front of the camera. + // + // Note: + // + // gluLookAt( + // eye()(0), eye()(1), eye()(2), + // at()(0), at()(1), at()(2), + // up()(0), up()(1), up()(2)); + // + // Is equivalent to + // + // glMultMatrixd(camera.inverse().matrix().data()); + // + // See also: affine, eye, at, up + inline Eigen::Affine3d inverse() const; + // Returns world coordinates position of center or "eye" of camera. + inline Eigen::Vector3d eye() const; + // Returns world coordinate position of a point "eye" is looking at. + inline Eigen::Vector3d at() const; + // Returns world coordinate unit vector of "up" vector + inline Eigen::Vector3d up() const; + // Return top right corner of unit plane in relative coordinates, that is + // (w/2,h/2,1) + inline Eigen::Vector3d unit_plane() const; + // Move dv in the relative coordinate frame of the camera (move the FPS) + // + // Inputs: + // dv (x,y,z) displacement vector + // + inline void dolly(const Eigen::Vector3d & dv); + // "Scale zoom": Move `eye`, but leave `at` + // + // Input: + // s amount to scale distance to at + inline void push_away(const double s); + // Aka "Hitchcock", "Vertigo", "Spielberg" or "Trombone" zoom: + // simultaneously dolly while changing angle so that `at` not only stays + // put in relative coordinates but also projected coordinates. That is + // + // Inputs: + // da change in angle in degrees + inline void dolly_zoom(const double da); + // Turn around eye so that rotation is now q + // + // Inputs: + // q new rotation as quaternion + inline void turn_eye(const Eigen::Quaterniond & q); + // Orbit around at so that rotation is now q + // + // Inputs: + // q new rotation as quaternion + inline void orbit(const Eigen::Quaterniond & q); + // Rotate and translate so that camera is situated at "eye" looking at "at" + // with "up" pointing up. + // + // Inputs: + // eye (x,y,z) coordinates of eye position + // at (x,y,z) coordinates of at position + // up (x,y,z) coordinates of up vector + inline void look_at( + const Eigen::Vector3d & eye, + const Eigen::Vector3d & at, + const Eigen::Vector3d & up); + // Needed any time Eigen Structures are used as class members + // http://eigen.tuxfamily.org/dox-devel/group__TopicStructHavingEigenMembers.html + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + }; +} + +// Implementation +#include "PI.h" +#include "EPS.h" +#include +#include +#include + +inline igl::Camera::Camera(): + m_angle(45.0),m_aspect(1),m_near(1e-2),m_far(100),m_at_dist(1), + m_orthographic(false), + m_rotation_conj(1,0,0,0), + m_translation(0,0,1) +{ +} + +inline Eigen::Matrix4d igl::Camera::projection() const +{ + Eigen::Matrix4d P; + using namespace std; + const double far = m_at_dist + m_far; + const double near = m_near; + // http://stackoverflow.com/a/3738696/148668 + if(m_orthographic) + { + const double f = 0.5; + const double left = -f*m_aspect; + const double right = f*m_aspect; + const double bottom = -f; + const double top = f; + const double tx = (right+left)/(right-left); + const double ty = (top+bottom)/(top-bottom); + const double tz = (far+near)/(far-near); + const double z_fix = 0.5 /m_at_dist / tan(m_angle*0.5 * (igl::PI/180.) ); + P<< + z_fix*2./(right-left), 0, 0, -tx, + 0, z_fix*2./(top-bottom), 0, -ty, + 0, 0, -z_fix*2./(far-near), -tz, + 0, 0, 0, 1; + }else + { + const double yScale = tan(PI*0.5 - 0.5*m_angle*PI/180.); + // http://stackoverflow.com/a/14975139/148668 + const double xScale = yScale/m_aspect; + P<< + xScale, 0, 0, 0, + 0, yScale, 0, 0, + 0, 0, -(far+near)/(far-near), -1, + 0, 0, -2.*near*far/(far-near), 0; + P = P.transpose().eval(); + } + return P; +} + +inline Eigen::Affine3d igl::Camera::affine() const +{ + using namespace Eigen; + Affine3d t = Affine3d::Identity(); + t.rotate(m_rotation_conj.conjugate()); + t.translate(m_translation); + return t; +} + +inline Eigen::Affine3d igl::Camera::inverse() const +{ + using namespace Eigen; + Affine3d t = Affine3d::Identity(); + t.translate(-m_translation); + t.rotate(m_rotation_conj); + return t; +} + +inline Eigen::Vector3d igl::Camera::eye() const +{ + using namespace Eigen; + return affine() * Vector3d(0,0,0); +} + +inline Eigen::Vector3d igl::Camera::at() const +{ + using namespace Eigen; + return affine() * (Vector3d(0,0,-1)*m_at_dist); +} + +inline Eigen::Vector3d igl::Camera::up() const +{ + using namespace Eigen; + Affine3d t = Affine3d::Identity(); + t.rotate(m_rotation_conj.conjugate()); + return t * Vector3d(0,1,0); +} + +inline Eigen::Vector3d igl::Camera::unit_plane() const +{ + // Distance of center pixel to eye + const double d = 1.0; + const double a = m_aspect; + const double theta = m_angle*PI/180.; + const double w = + 2.*sqrt(-d*d/(a*a*pow(tan(0.5*theta),2.)-1.))*a*tan(0.5*theta); + const double h = w/a; + return Eigen::Vector3d(w*0.5,h*0.5,-d); +} + +inline void igl::Camera::dolly(const Eigen::Vector3d & dv) +{ + m_translation += dv; +} + +inline void igl::Camera::push_away(const double s) +{ + using namespace Eigen; +#ifndef NDEBUG + Vector3d old_at = at(); +#endif + const double old_at_dist = m_at_dist; + m_at_dist = old_at_dist * s; + dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist)); + assert((old_at-at()).squaredNorm() < DOUBLE_EPS); +} + +inline void igl::Camera::dolly_zoom(const double da) +{ + using namespace std; + using namespace Eigen; +#ifndef NDEBUG + Vector3d old_at = at(); +#endif + const double old_angle = m_angle; + if(old_angle + da < IGL_CAMERA_MIN_ANGLE) + { + m_orthographic = true; + }else if(old_angle + da > IGL_CAMERA_MIN_ANGLE) + { + m_orthographic = false; + } + if(!m_orthographic) + { + m_angle += da; + m_angle = min(89.,max(IGL_CAMERA_MIN_ANGLE,m_angle)); + // change in distance + const double s = + (2.*tan(old_angle/2./180.*igl::PI)) / + (2.*tan(m_angle/2./180.*igl::PI)) ; + const double old_at_dist = m_at_dist; + m_at_dist = old_at_dist * s; + dolly(Vector3d(0,0,1)*(m_at_dist - old_at_dist)); + assert((old_at-at()).squaredNorm() < DOUBLE_EPS); + } +} + +inline void igl::Camera::turn_eye(const Eigen::Quaterniond & q) +{ + using namespace Eigen; + Vector3d old_eye = eye(); + // eye should be fixed + // + // eye_1 = R_1 * t_1 = eye_0 + // t_1 = R_1' * eye_0 + m_rotation_conj = q.conjugate(); + m_translation = m_rotation_conj * old_eye; + assert((old_eye - eye()).squaredNorm() < DOUBLE_EPS); +} + +inline void igl::Camera::orbit(const Eigen::Quaterniond & q) +{ + using namespace Eigen; + Vector3d old_at = at(); + // at should be fixed + // + // at_1 = R_1 * t_1 - R_1 * z = at_0 + // t_1 = R_1' * (at_0 + R_1 * z) + m_rotation_conj = q.conjugate(); + m_translation = + m_rotation_conj * + (old_at + + m_rotation_conj.conjugate() * Vector3d(0,0,1) * m_at_dist); + assert((old_at - at()).squaredNorm() < DOUBLE_EPS); +} + +inline void igl::Camera::look_at( + const Eigen::Vector3d & eye, + const Eigen::Vector3d & at, + const Eigen::Vector3d & up) +{ + using namespace Eigen; + using namespace std; + // http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml + // Normalize vector from at to eye + Vector3d F = eye-at; + m_at_dist = F.norm(); + F.normalize(); + // Project up onto plane orthogonal to F and normalize + assert(up.cross(F).norm() > DOUBLE_EPS && "(eye-at) x up ≈ 0"); + const Vector3d proj_up = (up-(up.dot(F))*F).normalized(); + Quaterniond a,b; + a.setFromTwoVectors(Vector3d(0,0,-1),-F); + b.setFromTwoVectors(a*Vector3d(0,1,0),proj_up); + m_rotation_conj = (b*a).conjugate(); + m_translation = m_rotation_conj * eye; + //cout<<"m_at_dist: "<eye().transpose()<at().transpose()<eye()-this->at()).normalized().transpose()<eye(): "<<(eye-this->eye()).squaredNorm()<eye()).squaredNorm() < DOUBLE_EPS); + //assert((F-(this->eye()-this->at()).normalized()).squaredNorm() < + // DOUBLE_EPS); + assert( (at-this->at()).squaredNorm() < DOUBLE_EPS); + //assert( (proj_up-this->up()).squaredNorm() < DOUBLE_EPS); +} + +#endif diff --git a/src/igl/EPS.cpp b/src/igl/EPS.cpp new file mode 100644 index 000000000..fc592cc2a --- /dev/null +++ b/src/igl/EPS.cpp @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "EPS.h" + +template <> IGL_INLINE float igl::EPS() +{ + return igl::FLOAT_EPS; +} +template <> IGL_INLINE double igl::EPS() +{ + return igl::DOUBLE_EPS; +} + +template <> IGL_INLINE float igl::EPS_SQ() +{ + return igl::FLOAT_EPS_SQ; +} +template <> IGL_INLINE double igl::EPS_SQ() +{ + return igl::DOUBLE_EPS_SQ; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/EPS.h b/src/igl/EPS.h new file mode 100644 index 000000000..17f3b8c25 --- /dev/null +++ b/src/igl/EPS.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EPS_H +#define IGL_EPS_H +#include "igl_inline.h" +namespace igl +{ + // Define a standard value for double epsilon + const double DOUBLE_EPS = 1.0e-14; + const double DOUBLE_EPS_SQ = 1.0e-28; + const float FLOAT_EPS = 1.0e-7; + const float FLOAT_EPS_SQ = 1.0e-14; + // Function returning EPS for corresponding type + template IGL_INLINE S_type EPS(); + template IGL_INLINE S_type EPS_SQ(); + // Template specializations for float and double + template <> IGL_INLINE float EPS(); + template <> IGL_INLINE double EPS(); + template <> IGL_INLINE float EPS_SQ(); + template <> IGL_INLINE double EPS_SQ(); +} + +#ifndef IGL_STATIC_LIBRARY +# include "EPS.cpp" +#endif + +#endif diff --git a/src/igl/HalfEdgeIterator.cpp b/src/igl/HalfEdgeIterator.cpp new file mode 100644 index 000000000..7c3b9a886 --- /dev/null +++ b/src/igl/HalfEdgeIterator.cpp @@ -0,0 +1,158 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "HalfEdgeIterator.h" + +template +IGL_INLINE igl::HalfEdgeIterator::HalfEdgeIterator( + const Eigen::PlainObjectBase& _F, + const Eigen::PlainObjectBase& _FF, + const Eigen::PlainObjectBase& _FFi, + int _fi, + int _ei, + bool _reverse +) +: fi(_fi), ei(_ei), reverse(_reverse), F(_F), FF(_FF), FFi(_FFi) +{} + +template +IGL_INLINE void igl::HalfEdgeIterator::flipF() +{ + if (isBorder()) + return; + + int fin = (FF)(fi,ei); + int ein = (FFi)(fi,ei); + + fi = fin; + ei = ein; + reverse = !reverse; +} + + +// Change Edge +template +IGL_INLINE void igl::HalfEdgeIterator::flipE() +{ + if (!reverse) + ei = (ei+2)%3; // ei-1 + else + ei = (ei+1)%3; + + reverse = !reverse; +} + +// Change Vertex +template +IGL_INLINE void igl::HalfEdgeIterator::flipV() +{ + reverse = !reverse; +} + +template +IGL_INLINE bool igl::HalfEdgeIterator::isBorder() +{ + return (FF)(fi,ei) == -1; +} + +/*! + * Returns the next edge skipping the border + * _________ + * /\ c | b /\ + * / \ | / \ + * / d \ | / a \ + * /______\|/______\ + * v + * In this example, if a and d are of-border and the pos is iterating counterclockwise, this method iterate through the faces incident on vertex v, + * producing the sequence a, b, c, d, a, b, c, ... + */ +template +IGL_INLINE bool igl::HalfEdgeIterator::NextFE() +{ + if ( isBorder() ) // we are on a border + { + do + { + flipF(); + flipE(); + } while (!isBorder()); + flipE(); + return false; + } + else + { + flipF(); + flipE(); + return true; + } +} + +// Get vertex index +template +IGL_INLINE int igl::HalfEdgeIterator::Vi() +{ + assert(fi >= 0); + assert(fi < F.rows()); + assert(ei >= 0); + assert(ei <= 2); + + if (!reverse) + return (F)(fi,ei); + else + return (F)(fi,(ei+1)%3); +} + +// Get face index +template +IGL_INLINE int igl::HalfEdgeIterator::Fi() +{ + return fi; +} + +// Get edge index +template +IGL_INLINE int igl::HalfEdgeIterator::Ei() +{ + return ei; +} + + +template +IGL_INLINE bool igl::HalfEdgeIterator::operator==(HalfEdgeIterator& p2) +{ + return + ( + (fi == p2.fi) && + (ei == p2.ei) && + (reverse == p2.reverse) && + (F == p2.F) && + (FF == p2.FF) && + (FFi == p2.FFi) + ); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template igl::HalfEdgeIterator ,Eigen::Matrix ,Eigen::Matrix >::HalfEdgeIterator(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, int, bool); +template igl::HalfEdgeIterator, Eigen::Matrix, Eigen::Matrix >::HalfEdgeIterator(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, int, bool); +template bool igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::NextFE(); +template int igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::Ei(); +template int igl::HalfEdgeIterator ,Eigen::Matrix,Eigen::Matrix >::Ei(); +template int igl::HalfEdgeIterator ,Eigen::Matrix ,Eigen::Matrix >::Ei(); +template int igl::HalfEdgeIterator ,Eigen::Matrix ,Eigen::Matrix >::Fi(); +template bool igl::HalfEdgeIterator ,Eigen::Matrix ,Eigen::Matrix >::NextFE(); +template int igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::Vi(); +template igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::HalfEdgeIterator(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, int, bool); +template int igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::Fi(); +template void igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::flipE(); +template void igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::flipF(); +template void igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::flipV(); +template bool igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >::operator==(igl::HalfEdgeIterator,Eigen::Matrix,Eigen::Matrix >&); +template int igl::HalfEdgeIterator, Eigen::Matrix, Eigen::Matrix >::Fi(); +template bool igl::HalfEdgeIterator, Eigen::Matrix, Eigen::Matrix >::NextFE(); +#endif diff --git a/src/igl/HalfEdgeIterator.h b/src/igl/HalfEdgeIterator.h new file mode 100644 index 000000000..3c429e1d1 --- /dev/null +++ b/src/igl/HalfEdgeIterator.h @@ -0,0 +1,114 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HALFEDGEITERATOR_H +#define IGL_HALFEDGEITERATOR_H + +#include + +#include +#include + +// This file violates many of the libigl style guidelines. + +namespace igl +{ + // HalfEdgeIterator - Fake halfedge for fast and easy navigation + // on triangle meshes with vertex_triangle_adjacency and + // triangle_triangle adjacency + // + // Note: this is different to classical Half Edge data structure. + // Instead, it follows cell-tuple in [Brisson, 1989] + // "Representing geometric structures in d dimensions: topology and order." + // This class can achieve local navigation similar to half edge in OpenMesh + // But the logic behind each atom operation is different. + // So this should be more properly called TriangleTupleIterator. + // + // Each tuple contains information on (face, edge, vertex) + // and encoded by (face, edge \in {0,1,2}, bool reverse) + // + // Inputs: + // F #F by 3 list of "faces" + // FF #F by 3 list of triangle-triangle adjacency. + // FFi #F by 3 list of FF inverse. For FF and FFi, refer to + // "triangle_triangle_adjacency.h" + // Usages: + // FlipF/E/V changes solely one actual face/edge/vertex resp. + // NextFE iterates through one-ring of a vertex robustly. + // + template < + typename DerivedF, + typename DerivedFF, + typename DerivedFFi> + class HalfEdgeIterator + { + public: + // Init the HalfEdgeIterator by specifying Face,Edge Index and Orientation + IGL_INLINE HalfEdgeIterator( + const Eigen::PlainObjectBase& _F, + const Eigen::PlainObjectBase& _FF, + const Eigen::PlainObjectBase& _FFi, + int _fi, + int _ei, + bool _reverse = false + ); + + // Change Face + IGL_INLINE void flipF(); + + // Change Edge + IGL_INLINE void flipE(); + + // Change Vertex + IGL_INLINE void flipV(); + + IGL_INLINE bool isBorder(); + + /*! + * Returns the next edge skipping the border + * _________ + * /\ c | b /\ + * / \ | / \ + * / d \ | / a \ + * /______\|/______\ + * v + * In this example, if a and d are of-border and the pos is iterating + counterclockwise, this method iterate through the faces incident on vertex + v, + * producing the sequence a, b, c, d, a, b, c, ... + */ + IGL_INLINE bool NextFE(); + + // Get vertex index + IGL_INLINE int Vi(); + + // Get face index + IGL_INLINE int Fi(); + + // Get edge index + IGL_INLINE int Ei(); + + IGL_INLINE bool operator==(HalfEdgeIterator& p2); + + private: + int fi; + int ei; + bool reverse; + + // All the same type? This is likely to break. + const Eigen::PlainObjectBase & F; + const Eigen::PlainObjectBase & FF; + const Eigen::PlainObjectBase & FFi; + }; + +} + +#ifndef IGL_STATIC_LIBRARY +# include "HalfEdgeIterator.cpp" +#endif + +#endif diff --git a/src/igl/Hit.h b/src/igl/Hit.h new file mode 100644 index 000000000..8bffcc843 --- /dev/null +++ b/src/igl/Hit.h @@ -0,0 +1,25 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HIT_H +#define IGL_HIT_H + +namespace igl +{ + // Reimplementation of the embree::Hit struct from embree1.0 + // + // TODO: template on floating point type + struct Hit + { + int id; // primitive id + int gid; // geometry id + float u,v; // barycentric coordinates + float t; // distance = direction*t to intersection + }; +} +#endif diff --git a/src/igl/IO b/src/igl/IO new file mode 100644 index 000000000..f054d8c99 --- /dev/null +++ b/src/igl/IO @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IO +#define IGL_IO +// Input and output functions +#include "read_triangle_mesh.h" +#include "readDMAT.h" +#include "readMESH.h" +#include "readNODE.h" +#include "readOBJ.h" +#include "readOFF.h" +#include "readTGF.h" +#include "readWRL.h" +#include "readCSV.h" +#include "file_contents_as_string.h" +#include "write_triangle_mesh.h" +#include "writeDMAT.h" +#include "writeMESH.h" +#include "writeOBJ.h" +#include "writeOFF.h" +#include "writeTGF.h" + +#endif diff --git a/src/igl/IndexComparison.h b/src/igl/IndexComparison.h new file mode 100644 index 000000000..44515df51 --- /dev/null +++ b/src/igl/IndexComparison.h @@ -0,0 +1,117 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INDEXCOMPARISON_H +#define IGL_INDEXCOMPARISON_H +#include +namespace igl{ + // Comparison struct used by sort + // http://bytes.com/topic/c/answers/132045-sort-get-index + + // For use with functions like std::sort + template struct IndexLessThan + { + IndexLessThan(const T arr) : arr(arr) {} + bool operator()(const size_t a, const size_t b) const + { + return arr[a] < arr[b]; + } + const T arr; + }; + + // For use with functions like std::unique + template struct IndexEquals + { + IndexEquals(const T arr) : arr(arr) {} + bool operator()(const size_t a, const size_t b) const + { + return arr[a] == arr[b]; + } + const T arr; + }; + + // For use with functions like std::sort + template struct IndexVectorLessThan + { + IndexVectorLessThan(const T & vec) : vec ( vec) {} + bool operator()(const size_t a, const size_t b) const + { + return vec(a) < vec(b); + } + const T & vec; + }; + + // For use with functions like std::sort + template struct IndexDimLessThan + { + IndexDimLessThan(const T & mat,const int & dim, const int & j) : + mat(mat), + dim(dim), + j(j) + {} + bool operator()(const size_t a, const size_t b) const + { + if(dim == 1) + { + return mat(a,j) < mat(b,j); + }else + { + return mat(j,a) < mat(j,b); + } + } + const T & mat; + const int & dim; + const int & j; + }; + + // For use with functions like std::sort + template struct IndexRowLessThan + { + IndexRowLessThan(const T & mat) : mat ( mat) {} + bool operator()(const size_t a, const size_t b) const + { + const int cols = mat.cols(); + // Lexicographical order + for(int j = 0;j mat(b,j)) + { + return false; + } else if(mat(a,j) < mat(b,j)) + { + return true; + } + } + // equality is false + return false; + } + const T & mat; + }; + + // For use with functions like std::sort + template struct IndexRowEquals + { + IndexRowEquals(const T & mat) : mat ( mat) {} + bool operator()(const size_t a, const size_t b) const + { + const int cols = mat.cols(); + // Lexicographical order + for(int j = 0;j +// This function is not intended to be a permanent function of libigl. Rather +// it is a "drop-in" workaround for documented bug in Eigen: +// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=1383 +// +// Replace: +// +// Eigen::VectorXi::LinSpaced(size,low,high); +// +// With: +// +// igl::LinSpaced(size,low,high); +// +// Specifcally, this version will _always_ return an empty vector if size==0, +// regardless of the values for low and high. If size != 0, then this simply +// returns the result of Eigen::Derived::LinSpaced. +// +// Until this bug is fixed, we should also avoid calls to the member function +// `.setLinSpaced`. This means replacing: +// +// a.setLinSpaced(size,low,high); +// +// with +// +// a = igl::LinSpaced(size,low,high); +// +namespace igl +{ + template + //inline typename Eigen::DenseBase< Derived >::RandomAccessLinSpacedReturnType + inline Derived LinSpaced( + typename Derived::Index size, + const typename Derived::Scalar & low, + const typename Derived::Scalar & high); +} + +// Implementation + +template +//inline typename Eigen::DenseBase< Derived >::RandomAccessLinSpacedReturnType +inline Derived +igl::LinSpaced( + typename Derived::Index size, + const typename Derived::Scalar & low, + const typename Derived::Scalar & high) +{ + if(size == 0) + { + // Force empty vector with correct "RandomAccessLinSpacedReturnType" type. + return Derived::LinSpaced(0,0,1); + }else if(high < low) + { + return low-Derived::LinSpaced(size,low-low,low-high).array(); + }else{ + return Derived::LinSpaced(size,low,high); + } +} + +#endif diff --git a/src/igl/MeshBooleanType.h b/src/igl/MeshBooleanType.h new file mode 100644 index 000000000..2eb293624 --- /dev/null +++ b/src/igl/MeshBooleanType.h @@ -0,0 +1,23 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MESH_BOOLEAN_TYPE_H +#define IGL_MESH_BOOLEAN_TYPE_H +namespace igl +{ + enum MeshBooleanType + { + MESH_BOOLEAN_TYPE_UNION = 0, + MESH_BOOLEAN_TYPE_INTERSECT = 1, + MESH_BOOLEAN_TYPE_MINUS = 2, + MESH_BOOLEAN_TYPE_XOR = 3, + MESH_BOOLEAN_TYPE_RESOLVE = 4, + NUM_MESH_BOOLEAN_TYPES = 5 + }; +}; + +#endif diff --git a/src/igl/NormalType.h b/src/igl/NormalType.h new file mode 100644 index 000000000..b74b57b6a --- /dev/null +++ b/src/igl/NormalType.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NORMALTYPE_H +#define IGL_NORMALTYPE_H + +namespace igl +{ + // PER_VERTEX_NORMALS Normals computed per vertex based on incident faces + // PER_FACE_NORMALS Normals computed per face + // PER_CORNER_NORMALS Normals computed per corner (aka wedge) based on + // incident faces without sharp edge + enum NormalType + { + PER_VERTEX_NORMALS, + PER_FACE_NORMALS, + PER_CORNER_NORMALS + }; +# define NUM_NORMAL_TYPE 3 +} + +#endif + diff --git a/src/igl/ONE.h b/src/igl/ONE.h new file mode 100644 index 000000000..93c509d4e --- /dev/null +++ b/src/igl/ONE.h @@ -0,0 +1,22 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ONE_H +#define IGL_ONE_H +namespace igl +{ + // Often one needs a reference to a dummy variable containing one as its + // value, for example when using AntTweakBar's + // TwSetParam( "3D View", "opened", TW_PARAM_INT32, 1, &INT_ONE); + const char CHAR_ONE = 1; + const int INT_ONE = 1; + const unsigned int UNSIGNED_INT_ONE = 1; + const double DOUBLE_ONE = 1; + const float FLOAT_ONE = 1; +} +#endif + diff --git a/src/igl/PI.h b/src/igl/PI.h new file mode 100644 index 000000000..f1ef4359d --- /dev/null +++ b/src/igl/PI.h @@ -0,0 +1,19 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PI_H +#define IGL_PI_H +namespace igl +{ + // Use standard mathematical constants' M_PI if available +#ifdef M_PI + const double PI = M_PI; +#else + const double PI = 3.1415926535897932384626433832795; +#endif +} +#endif diff --git a/src/igl/REDRUM.h b/src/igl/REDRUM.h new file mode 100644 index 000000000..79ab72ffd --- /dev/null +++ b/src/igl/REDRUM.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REDRUM_H +#define IGL_REDRUM_H + +// Q: These should probably be inside the igl namespace. What's the correct +// way to do that? +// A: I guess the right way is to not use a macro but a proper function with +// streams as input and output. + +// ANSI color codes for formatting iostream style output + +#ifdef IGL_REDRUM_NOOP + +// Bold Red, etc. +#define NORUM(X) X +#define REDRUM(X) X +#define GREENRUM(X) X +#define YELLOWRUM(X) X +#define BLUERUM(X) X +#define MAGENTARUM(X) X +#define CYANRUM(X) X +// Regular Red, etc. +#define REDGIN(X) X +#define GREENGIN(X) X +#define YELLOWGIN(X) X +#define BLUEGIN(X) X +#define MAGENTAGIN(X) X +#define CYANGIN(X) X + +#else + +// Bold Red, etc. +#define NORUM(X) ""< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_STR_H +#define IGL_STR_H +// http://stackoverflow.com/a/2433143/148668 +#include +#include +// Suppose you have a function: +// void func(std::string c); +// Then you can write: +// func(STR("foo"<<1<<"bar")); +#define STR(X) static_cast(std::ostringstream().flush() << X).str() +#endif diff --git a/src/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp b/src/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp new file mode 100644 index 000000000..2207285aa --- /dev/null +++ b/src/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp @@ -0,0 +1,128 @@ +//##################################################################### +// Copyright (c) 2010-2011, Eftychios Sifakis. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//##################################################################### + +//########################################################### +// Compute the Givens half-angle, construct the Givens quaternion and the rotation sine/cosine (for the full angle) +//########################################################### + +#ifdef _WIN32 + #undef max + #undef min +#endif + +ENABLE_SCALAR_IMPLEMENTATION(Ssh.f=SANPIVOT.f*SANPIVOT.f;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_mul_ps(VANPIVOT,VANPIVOT);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_mul_ps(VANPIVOT,VANPIVOT);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=(Ssh.f>=Ssmall_number.f)?0xffffffff:0;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_cmpge_ps(Vsh,Vsmall_number);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_cmp_ps(Vsh,Vsmall_number, _CMP_GE_OS);) //ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_cmpge_ps(Vsh,Vsmall_number);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=Ssh.ui&SANPIVOT.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_and_ps(Vsh,VANPIVOT);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_and_ps(Vsh,VANPIVOT);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp5.f=0.;) ENABLE_SSE_IMPLEMENTATION(Vtmp5=_mm_xor_ps(Vtmp5,Vtmp5);) ENABLE_AVX_IMPLEMENTATION(Vtmp5=_mm256_xor_ps(Vtmp5,Vtmp5);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=Stmp5.f-SAPIVOT.f;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_sub_ps(Vtmp5,VAPIVOT);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_sub_ps(Vtmp5,VAPIVOT);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=std::max(Sch.f,SAPIVOT.f);) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_max_ps(Vch,VAPIVOT);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_max_ps(Vch,VAPIVOT);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=std::max(Sch.f,Ssmall_number.f);) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_max_ps(Vch,Vsmall_number);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_max_ps(Vch,Vsmall_number);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp5.ui=(SAPIVOT.f>=Stmp5.f)?0xffffffff:0;) ENABLE_SSE_IMPLEMENTATION(Vtmp5=_mm_cmpge_ps(VAPIVOT,Vtmp5);) ENABLE_AVX_IMPLEMENTATION(Vtmp5=_mm256_cmp_ps(VAPIVOT,Vtmp5, _CMP_GE_OS);) //ENABLE_AVX_IMPLEMENTATION(Vtmp5=_mm256_cmpge_ps(VAPIVOT,Vtmp5);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sch.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vch,Vch);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vch,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Stmp1.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_add_ps(Vtmp1,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_add_ps(Vtmp1,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=rsqrt(Stmp2.f);) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_rsqrt_ps(Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_rsqrt_ps(Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Stmp1.f*Sone_half.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_mul_ps(Vtmp1,Vone_half);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_mul_ps(Vtmp1,Vone_half);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp1,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp1,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp1,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp1,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp2.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp2,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp2,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Stmp1.f+Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_add_ps(Vtmp1,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_add_ps(Vtmp1,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Stmp1.f-Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_sub_ps(Vtmp1,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_sub_ps(Vtmp1,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Stmp1.f*Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vtmp1,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vtmp1,Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=Sch.f+Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_add_ps(Vch,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_add_ps(Vch,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.ui=~Stmp5.ui&Ssh.ui;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_andnot_ps(Vtmp5,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=Vch;) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.ui=~Stmp5.ui&Sch.ui;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_andnot_ps(Vtmp5,Vch);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_blendv_ps(Vsh,Vch,Vtmp5);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=Stmp5.ui&Sch.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_and_ps(Vtmp5,Vch);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_blendv_ps(Vtmp1,Vsh,Vtmp5);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=Stmp5.ui&Ssh.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_and_ps(Vtmp5,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=Sch.ui|Stmp1.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_or_ps(Vch,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=Ssh.ui|Stmp2.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_or_ps(Vsh,Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sch.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vch,Vch);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vch,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Stmp1.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_add_ps(Vtmp1,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_add_ps(Vtmp1,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=rsqrt(Stmp2.f);) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_rsqrt_ps(Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_rsqrt_ps(Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Stmp1.f*Sone_half.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_mul_ps(Vtmp1,Vone_half);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_mul_ps(Vtmp1,Vone_half);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp1,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp1,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp1,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp1,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp2.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vtmp2,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vtmp2,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Stmp1.f+Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_add_ps(Vtmp1,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_add_ps(Vtmp1,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Stmp1.f-Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_sub_ps(Vtmp1,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_sub_ps(Vtmp1,Vtmp3);) + +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=Sch.f*Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_mul_ps(Vch,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_mul_ps(Vch,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.f=Ssh.f*Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_mul_ps(Vsh,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_mul_ps(Vsh,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Sch.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_mul_ps(Vch,Vch);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_mul_ps(Vch,Vch);)ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Sc.f-Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_sub_ps(Vc,Vs);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_sub_ps(Vc,Vs);) +ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Ssh.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_mul_ps(Vsh,Vch);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_mul_ps(Vsh,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Ss.f+Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_add_ps(Vs,Vs);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_add_ps(Vs,Vs);) + +//########################################################### +// Rotate matrix A +//########################################################### + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SA11.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VA11);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VA11);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SA21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VA21);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VA21);) +ENABLE_SCALAR_IMPLEMENTATION(SA11.f=Sc.f*SA11.f;) ENABLE_SSE_IMPLEMENTATION(VA11=_mm_mul_ps(Vc,VA11);) ENABLE_AVX_IMPLEMENTATION(VA11=_mm256_mul_ps(Vc,VA11);) +ENABLE_SCALAR_IMPLEMENTATION(SA21.f=Sc.f*SA21.f;) ENABLE_SSE_IMPLEMENTATION(VA21=_mm_mul_ps(Vc,VA21);) ENABLE_AVX_IMPLEMENTATION(VA21=_mm256_mul_ps(Vc,VA21);) +ENABLE_SCALAR_IMPLEMENTATION(SA11.f=SA11.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VA11=_mm_add_ps(VA11,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VA11=_mm256_add_ps(VA11,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SA21.f=SA21.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VA21=_mm_sub_ps(VA21,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VA21=_mm256_sub_ps(VA21,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SA12.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VA12);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VA12);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SA22.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VA22);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VA22);) +ENABLE_SCALAR_IMPLEMENTATION(SA12.f=Sc.f*SA12.f;) ENABLE_SSE_IMPLEMENTATION(VA12=_mm_mul_ps(Vc,VA12);) ENABLE_AVX_IMPLEMENTATION(VA12=_mm256_mul_ps(Vc,VA12);) +ENABLE_SCALAR_IMPLEMENTATION(SA22.f=Sc.f*SA22.f;) ENABLE_SSE_IMPLEMENTATION(VA22=_mm_mul_ps(Vc,VA22);) ENABLE_AVX_IMPLEMENTATION(VA22=_mm256_mul_ps(Vc,VA22);) +ENABLE_SCALAR_IMPLEMENTATION(SA12.f=SA12.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VA12=_mm_add_ps(VA12,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VA12=_mm256_add_ps(VA12,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SA22.f=SA22.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VA22=_mm_sub_ps(VA22,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VA22=_mm256_sub_ps(VA22,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SA13.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VA13);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VA13);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SA23.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VA23);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VA23);) +ENABLE_SCALAR_IMPLEMENTATION(SA13.f=Sc.f*SA13.f;) ENABLE_SSE_IMPLEMENTATION(VA13=_mm_mul_ps(Vc,VA13);) ENABLE_AVX_IMPLEMENTATION(VA13=_mm256_mul_ps(Vc,VA13);) +ENABLE_SCALAR_IMPLEMENTATION(SA23.f=Sc.f*SA23.f;) ENABLE_SSE_IMPLEMENTATION(VA23=_mm_mul_ps(Vc,VA23);) ENABLE_AVX_IMPLEMENTATION(VA23=_mm256_mul_ps(Vc,VA23);) +ENABLE_SCALAR_IMPLEMENTATION(SA13.f=SA13.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VA13=_mm_add_ps(VA13,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VA13=_mm256_add_ps(VA13,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SA23.f=SA23.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VA23=_mm_sub_ps(VA23,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VA23=_mm256_sub_ps(VA23,Vtmp1);) + +//########################################################### +// Update matrix U +//########################################################### + +#ifdef COMPUTE_U_AS_MATRIX +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SU11.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VU11);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VU11);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SU12.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VU12);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VU12);) +ENABLE_SCALAR_IMPLEMENTATION(SU11.f=Sc.f*SU11.f;) ENABLE_SSE_IMPLEMENTATION(VU11=_mm_mul_ps(Vc,VU11);) ENABLE_AVX_IMPLEMENTATION(VU11=_mm256_mul_ps(Vc,VU11);) +ENABLE_SCALAR_IMPLEMENTATION(SU12.f=Sc.f*SU12.f;) ENABLE_SSE_IMPLEMENTATION(VU12=_mm_mul_ps(Vc,VU12);) ENABLE_AVX_IMPLEMENTATION(VU12=_mm256_mul_ps(Vc,VU12);) +ENABLE_SCALAR_IMPLEMENTATION(SU11.f=SU11.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VU11=_mm_add_ps(VU11,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VU11=_mm256_add_ps(VU11,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SU12.f=SU12.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VU12=_mm_sub_ps(VU12,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VU12=_mm256_sub_ps(VU12,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SU21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VU21);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VU21);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SU22.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VU22);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VU22);) +ENABLE_SCALAR_IMPLEMENTATION(SU21.f=Sc.f*SU21.f;) ENABLE_SSE_IMPLEMENTATION(VU21=_mm_mul_ps(Vc,VU21);) ENABLE_AVX_IMPLEMENTATION(VU21=_mm256_mul_ps(Vc,VU21);) +ENABLE_SCALAR_IMPLEMENTATION(SU22.f=Sc.f*SU22.f;) ENABLE_SSE_IMPLEMENTATION(VU22=_mm_mul_ps(Vc,VU22);) ENABLE_AVX_IMPLEMENTATION(VU22=_mm256_mul_ps(Vc,VU22);) +ENABLE_SCALAR_IMPLEMENTATION(SU21.f=SU21.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VU21=_mm_add_ps(VU21,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VU21=_mm256_add_ps(VU21,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SU22.f=SU22.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VU22=_mm_sub_ps(VU22,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VU22=_mm256_sub_ps(VU22,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SU31.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VU31);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VU31);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SU32.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VU32);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VU32);) +ENABLE_SCALAR_IMPLEMENTATION(SU31.f=Sc.f*SU31.f;) ENABLE_SSE_IMPLEMENTATION(VU31=_mm_mul_ps(Vc,VU31);) ENABLE_AVX_IMPLEMENTATION(VU31=_mm256_mul_ps(Vc,VU31);) +ENABLE_SCALAR_IMPLEMENTATION(SU32.f=Sc.f*SU32.f;) ENABLE_SSE_IMPLEMENTATION(VU32=_mm_mul_ps(Vc,VU32);) ENABLE_AVX_IMPLEMENTATION(VU32=_mm256_mul_ps(Vc,VU32);) +ENABLE_SCALAR_IMPLEMENTATION(SU31.f=SU31.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VU31=_mm_add_ps(VU31,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VU31=_mm256_add_ps(VU31,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SU32.f=SU32.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VU32=_mm_sub_ps(VU32,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VU32=_mm256_sub_ps(VU32,Vtmp1);) +#endif diff --git a/src/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp b/src/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp new file mode 100644 index 000000000..70b437131 --- /dev/null +++ b/src/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp @@ -0,0 +1,118 @@ +//##################################################################### +// Copyright (c) 2010-2011, Eftychios Sifakis. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//##################################################################### + +//########################################################### +// Compute the Givens angle (and half-angle) +//########################################################### + +ENABLE_SCALAR_IMPLEMENTATION(Ssh.f=SS21.f*Sone_half.f;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_mul_ps(VS21,Vone_half);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_mul_ps(VS21,Vone_half);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp5.f=SS11.f-SS22.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp5=_mm_sub_ps(VS11,VS22);) ENABLE_AVX_IMPLEMENTATION(Vtmp5=_mm256_sub_ps(VS11,VS22);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.ui=(Stmp2.f>=Stiny_number.f)?0xffffffff:0;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_cmpge_ps(Vtmp2,Vtiny_number);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_cmp_ps(Vtmp2,Vtiny_number, _CMP_GE_OS);) //ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_cmpge_ps(Vtmp2,Vtiny_number);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=Stmp1.ui&Ssh.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_and_ps(Vtmp1,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_and_ps(Vtmp1,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=Stmp1.ui&Stmp5.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_and_ps(Vtmp1,Vtmp5);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_blendv_ps(Vone,Vtmp5,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.ui=~Stmp1.ui&Sone.ui;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_andnot_ps(Vtmp1,Vone);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=Sch.ui|Stmp2.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_or_ps(Vch,Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Sch.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vch,Vch);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vch,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_add_ps(Vtmp1,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_add_ps(Vtmp1,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=rsqrt(Stmp3.f);) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_rsqrt_ps(Vtmp3);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_rsqrt_ps(Vtmp3);) + +#ifdef USE_ACCURATE_RSQRT_IN_JACOBI_CONJUGATION +ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Stmp4.f*Sone_half.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_mul_ps(Vtmp4,Vone_half);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_mul_ps(Vtmp4,Vone_half);) +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Stmp4.f*Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_mul_ps(Vtmp4,Vs);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_mul_ps(Vtmp4,Vs);) +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Stmp4.f*Sc.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_mul_ps(Vtmp4,Vc);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_mul_ps(Vtmp4,Vc);) +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Stmp3.f*Sc.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_mul_ps(Vtmp3,Vc);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_mul_ps(Vtmp3,Vc);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Stmp4.f+Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_add_ps(Vtmp4,Vs);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_add_ps(Vtmp4,Vs);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Stmp4.f-Sc.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_sub_ps(Vtmp4,Vc);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_sub_ps(Vtmp4,Vc);) +#endif + +ENABLE_SCALAR_IMPLEMENTATION(Ssh.f=Stmp4.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_mul_ps(Vtmp4,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_mul_ps(Vtmp4,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.f=Stmp4.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_mul_ps(Vtmp4,Vch);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_mul_ps(Vtmp4,Vch);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sfour_gamma_squared.f*Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vfour_gamma_squared,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vfour_gamma_squared,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.ui=(Stmp2.f<=Stmp1.f)?0xffffffff:0;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_cmple_ps(Vtmp2,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_cmp_ps(Vtmp2,Vtmp1, _CMP_LE_OS);) //ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_cmple_ps(Vtmp2,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.ui=Ssine_pi_over_eight.ui&Stmp1.ui;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_and_ps(Vsine_pi_over_eight,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_blendv_ps(Vsh,Vsine_pi_over_eight,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=~Stmp1.ui&Ssh.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_andnot_ps(Vtmp1,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.ui=Ssh.ui|Stmp2.ui;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_or_ps(Vsh,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.ui=Scosine_pi_over_eight.ui&Stmp1.ui;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_and_ps(Vcosine_pi_over_eight,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vch=_mm256_blendv_ps(Vch,Vcosine_pi_over_eight,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=~Stmp1.ui&Sch.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_andnot_ps(Vtmp1,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Sch.ui=Sch.ui|Stmp2.ui;) ENABLE_SSE_IMPLEMENTATION(Vch=_mm_or_ps(Vch,Vtmp2);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ssh.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vsh,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vsh,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Sch.f*Sch.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vch,Vch);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vch,Vch);) +ENABLE_SCALAR_IMPLEMENTATION(Sc.f=Stmp2.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(Vc=_mm_sub_ps(Vtmp2,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(Vc=_mm256_sub_ps(Vtmp2,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Sch.f*Ssh.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_mul_ps(Vch,Vsh);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_mul_ps(Vch,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Ss.f=Ss.f+Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vs=_mm_add_ps(Vs,Vs);) ENABLE_AVX_IMPLEMENTATION(Vs=_mm256_add_ps(Vs,Vs);) + +//########################################################### +// Perform the actual Givens conjugation +//########################################################### + +#ifndef USE_ACCURATE_RSQRT_IN_JACOBI_CONJUGATION +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Stmp1.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_add_ps(Vtmp1,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_add_ps(Vtmp1,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SS33.f=SS33.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(VS33=_mm_mul_ps(VS33,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(VS33=_mm256_mul_ps(VS33,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(SS31.f=SS31.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(VS31=_mm_mul_ps(VS31,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(VS31=_mm256_mul_ps(VS31,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(SS32.f=SS32.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(VS32=_mm_mul_ps(VS32,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(VS32=_mm256_mul_ps(VS32,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(SS33.f=SS33.f*Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(VS33=_mm_mul_ps(VS33,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(VS33=_mm256_mul_ps(VS33,Vtmp3);) +#endif + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ss.f*SS31.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vs,VS31);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vs,VS31);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*SS32.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,VS32);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,VS32);) +ENABLE_SCALAR_IMPLEMENTATION(SS31.f=Sc.f*SS31.f;) ENABLE_SSE_IMPLEMENTATION(VS31=_mm_mul_ps(Vc,VS31);) ENABLE_AVX_IMPLEMENTATION(VS31=_mm256_mul_ps(Vc,VS31);) +ENABLE_SCALAR_IMPLEMENTATION(SS32.f=Sc.f*SS32.f;) ENABLE_SSE_IMPLEMENTATION(VS32=_mm_mul_ps(Vc,VS32);) ENABLE_AVX_IMPLEMENTATION(VS32=_mm256_mul_ps(Vc,VS32);) +ENABLE_SCALAR_IMPLEMENTATION(SS31.f=Stmp2.f+SS31.f;) ENABLE_SSE_IMPLEMENTATION(VS31=_mm_add_ps(Vtmp2,VS31);) ENABLE_AVX_IMPLEMENTATION(VS31=_mm256_add_ps(Vtmp2,VS31);) +ENABLE_SCALAR_IMPLEMENTATION(SS32.f=SS32.f-Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VS32=_mm_sub_ps(VS32,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VS32=_mm256_sub_ps(VS32,Vtmp1);) + +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ss.f*Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vs,Vs);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vs,Vs);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=SS22.f*Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(VS22,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(VS22,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=SS11.f*Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(VS11,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(VS11,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Sc.f*Sc.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_mul_ps(Vc,Vc);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_mul_ps(Vc,Vc);) +ENABLE_SCALAR_IMPLEMENTATION(SS11.f=SS11.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(VS11=_mm_mul_ps(VS11,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(VS11=_mm256_mul_ps(VS11,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(SS22.f=SS22.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(VS22=_mm_mul_ps(VS22,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(VS22=_mm256_mul_ps(VS22,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(SS11.f=SS11.f+Stmp1.f;) ENABLE_SSE_IMPLEMENTATION(VS11=_mm_add_ps(VS11,Vtmp1);) ENABLE_AVX_IMPLEMENTATION(VS11=_mm256_add_ps(VS11,Vtmp1);) +ENABLE_SCALAR_IMPLEMENTATION(SS22.f=SS22.f+Stmp3.f;) ENABLE_SSE_IMPLEMENTATION(VS22=_mm_add_ps(VS22,Vtmp3);) ENABLE_AVX_IMPLEMENTATION(VS22=_mm256_add_ps(VS22,Vtmp3);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Stmp4.f-Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_sub_ps(Vtmp4,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_sub_ps(Vtmp4,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=SS21.f+SS21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_add_ps(VS21,VS21);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_add_ps(VS21,VS21);) +ENABLE_SCALAR_IMPLEMENTATION(SS21.f=SS21.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(VS21=_mm_mul_ps(VS21,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(VS21=_mm256_mul_ps(VS21,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp4.f=Sc.f*Ss.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp4=_mm_mul_ps(Vc,Vs);) ENABLE_AVX_IMPLEMENTATION(Vtmp4=_mm256_mul_ps(Vc,Vs);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Stmp2.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vtmp2,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vtmp2,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp5.f=Stmp5.f*Stmp4.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp5=_mm_mul_ps(Vtmp5,Vtmp4);) ENABLE_AVX_IMPLEMENTATION(Vtmp5=_mm256_mul_ps(Vtmp5,Vtmp4);) +ENABLE_SCALAR_IMPLEMENTATION(SS11.f=SS11.f+Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VS11=_mm_add_ps(VS11,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VS11=_mm256_add_ps(VS11,Vtmp2);) +ENABLE_SCALAR_IMPLEMENTATION(SS21.f=SS21.f-Stmp5.f;) ENABLE_SSE_IMPLEMENTATION(VS21=_mm_sub_ps(VS21,Vtmp5);) ENABLE_AVX_IMPLEMENTATION(VS21=_mm256_sub_ps(VS21,Vtmp5);) +ENABLE_SCALAR_IMPLEMENTATION(SS22.f=SS22.f-Stmp2.f;) ENABLE_SSE_IMPLEMENTATION(VS22=_mm_sub_ps(VS22,Vtmp2);) ENABLE_AVX_IMPLEMENTATION(VS22=_mm256_sub_ps(VS22,Vtmp2);) + +//########################################################### +// Compute the cumulative rotation, in quaternion form +//########################################################### + +ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Ssh.f*Sqvvx.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Vsh,Vqvvx);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Vsh,Vqvvx);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp2.f=Ssh.f*Sqvvy.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp2=_mm_mul_ps(Vsh,Vqvvy);) ENABLE_AVX_IMPLEMENTATION(Vtmp2=_mm256_mul_ps(Vsh,Vqvvy);) +ENABLE_SCALAR_IMPLEMENTATION(Stmp3.f=Ssh.f*Sqvvz.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp3=_mm_mul_ps(Vsh,Vqvvz);) ENABLE_AVX_IMPLEMENTATION(Vtmp3=_mm256_mul_ps(Vsh,Vqvvz);) +ENABLE_SCALAR_IMPLEMENTATION(Ssh.f=Ssh.f*Sqvs.f;) ENABLE_SSE_IMPLEMENTATION(Vsh=_mm_mul_ps(Vsh,Vqvs);) ENABLE_AVX_IMPLEMENTATION(Vsh=_mm256_mul_ps(Vsh,Vqvs);) + +ENABLE_SCALAR_IMPLEMENTATION(Sqvs.f=Sch.f*Sqvs.f;) ENABLE_SSE_IMPLEMENTATION(Vqvs=_mm_mul_ps(Vch,Vqvs);) ENABLE_AVX_IMPLEMENTATION(Vqvs=_mm256_mul_ps(Vch,Vqvs);) +ENABLE_SCALAR_IMPLEMENTATION(Sqvvx.f=Sch.f*Sqvvx.f;) ENABLE_SSE_IMPLEMENTATION(Vqvvx=_mm_mul_ps(Vch,Vqvvx);) ENABLE_AVX_IMPLEMENTATION(Vqvvx=_mm256_mul_ps(Vch,Vqvvx);) +ENABLE_SCALAR_IMPLEMENTATION(Sqvvy.f=Sch.f*Sqvvy.f;) ENABLE_SSE_IMPLEMENTATION(Vqvvy=_mm_mul_ps(Vch,Vqvvy);) ENABLE_AVX_IMPLEMENTATION(Vqvvy=_mm256_mul_ps(Vch,Vqvvy);) +ENABLE_SCALAR_IMPLEMENTATION(Sqvvz.f=Sch.f*Sqvvz.f;) ENABLE_SSE_IMPLEMENTATION(Vqvvz=_mm_mul_ps(Vch,Vqvvz);) ENABLE_AVX_IMPLEMENTATION(Vqvvz=_mm256_mul_ps(Vch,Vqvvz);) + +ENABLE_SCALAR_IMPLEMENTATION(SQVVZ.f=SQVVZ.f+Ssh.f;) ENABLE_SSE_IMPLEMENTATION(VQVVZ=_mm_add_ps(VQVVZ,Vsh);) ENABLE_AVX_IMPLEMENTATION(VQVVZ=_mm256_add_ps(VQVVZ,Vsh);) +ENABLE_SCALAR_IMPLEMENTATION(Sqvs.f=Sqvs.f-STMP3.f;) ENABLE_SSE_IMPLEMENTATION(Vqvs=_mm_sub_ps(Vqvs,VTMP3);) ENABLE_AVX_IMPLEMENTATION(Vqvs=_mm256_sub_ps(Vqvs,VTMP3);) +ENABLE_SCALAR_IMPLEMENTATION(SQVVX.f=SQVVX.f+STMP2.f;) ENABLE_SSE_IMPLEMENTATION(VQVVX=_mm_add_ps(VQVVX,VTMP2);) ENABLE_AVX_IMPLEMENTATION(VQVVX=_mm256_add_ps(VQVVX,VTMP2);) +ENABLE_SCALAR_IMPLEMENTATION(SQVVY.f=SQVVY.f-STMP1.f;) ENABLE_SSE_IMPLEMENTATION(VQVVY=_mm_sub_ps(VQVVY,VTMP1);) ENABLE_AVX_IMPLEMENTATION(VQVVY=_mm256_sub_ps(VQVVY,VTMP1);) diff --git a/src/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp b/src/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp new file mode 100644 index 000000000..0c3b3ff87 --- /dev/null +++ b/src/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp @@ -0,0 +1,137 @@ +//##################################################################### +// Copyright (c) 2010-2011, Eftychios Sifakis. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//##################################################################### + +//########################################################### +// Local variable declarations +//########################################################### + +#ifdef PRINT_DEBUGGING_OUTPUT + +#ifdef USE_SSE_IMPLEMENTATION + float buf[4]; + float A11,A21,A31,A12,A22,A32,A13,A23,A33; + float S11,S21,S31,S22,S32,S33; +#ifdef COMPUTE_V_AS_QUATERNION + float QVS,QVVX,QVVY,QVVZ; +#endif +#ifdef COMPUTE_V_AS_MATRIX + float V11,V21,V31,V12,V22,V32,V13,V23,V33; +#endif +#ifdef COMPUTE_U_AS_QUATERNION + float QUS,QUVX,QUVY,QUVZ; +#endif +#ifdef COMPUTE_U_AS_MATRIX + float U11,U21,U31,U12,U22,U32,U13,U23,U33; +#endif +#endif + +#ifdef USE_AVX_IMPLEMENTATION + float buf[8]; + float A11,A21,A31,A12,A22,A32,A13,A23,A33; + float S11,S21,S31,S22,S32,S33; +#ifdef COMPUTE_V_AS_QUATERNION + float QVS,QVVX,QVVY,QVVZ; +#endif +#ifdef COMPUTE_V_AS_MATRIX + float V11,V21,V31,V12,V22,V32,V13,V23,V33; +#endif +#ifdef COMPUTE_U_AS_QUATERNION + float QUS,QUVX,QUVY,QUVZ; +#endif +#ifdef COMPUTE_U_AS_MATRIX + float U11,U21,U31,U12,U22,U32,U13,U23,U33; +#endif +#endif + +#endif + +const float Four_Gamma_Squared=sqrt(8.)+3.; +const float Sine_Pi_Over_Eight=.5*sqrt(2.-sqrt(2.)); +const float Cosine_Pi_Over_Eight=.5*sqrt(2.+sqrt(2.)); + +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sfour_gamma_squared;) ENABLE_SSE_IMPLEMENTATION(__m128 Vfour_gamma_squared;) ENABLE_AVX_IMPLEMENTATION(__m256 Vfour_gamma_squared;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ssine_pi_over_eight;) ENABLE_SSE_IMPLEMENTATION(__m128 Vsine_pi_over_eight;) ENABLE_AVX_IMPLEMENTATION(__m256 Vsine_pi_over_eight;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Scosine_pi_over_eight;) ENABLE_SSE_IMPLEMENTATION(__m128 Vcosine_pi_over_eight;) ENABLE_AVX_IMPLEMENTATION(__m256 Vcosine_pi_over_eight;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sone_half;) ENABLE_SSE_IMPLEMENTATION(__m128 Vone_half;) ENABLE_AVX_IMPLEMENTATION(__m256 Vone_half;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sone;) ENABLE_SSE_IMPLEMENTATION(__m128 Vone;) ENABLE_AVX_IMPLEMENTATION(__m256 Vone;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stiny_number;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtiny_number;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtiny_number;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ssmall_number;) ENABLE_SSE_IMPLEMENTATION(__m128 Vsmall_number;) ENABLE_AVX_IMPLEMENTATION(__m256 Vsmall_number;) + +ENABLE_SCALAR_IMPLEMENTATION(Sfour_gamma_squared.f=Four_Gamma_Squared;) ENABLE_SSE_IMPLEMENTATION(Vfour_gamma_squared=_mm_set1_ps(Four_Gamma_Squared);) ENABLE_AVX_IMPLEMENTATION(Vfour_gamma_squared=_mm256_set1_ps(Four_Gamma_Squared);) +ENABLE_SCALAR_IMPLEMENTATION(Ssine_pi_over_eight.f=Sine_Pi_Over_Eight;) ENABLE_SSE_IMPLEMENTATION(Vsine_pi_over_eight=_mm_set1_ps(Sine_Pi_Over_Eight);) ENABLE_AVX_IMPLEMENTATION(Vsine_pi_over_eight=_mm256_set1_ps(Sine_Pi_Over_Eight);) +ENABLE_SCALAR_IMPLEMENTATION(Scosine_pi_over_eight.f=Cosine_Pi_Over_Eight;) ENABLE_SSE_IMPLEMENTATION(Vcosine_pi_over_eight=_mm_set1_ps(Cosine_Pi_Over_Eight);) ENABLE_AVX_IMPLEMENTATION(Vcosine_pi_over_eight=_mm256_set1_ps(Cosine_Pi_Over_Eight);) +ENABLE_SCALAR_IMPLEMENTATION(Sone_half.f=.5;) ENABLE_SSE_IMPLEMENTATION(Vone_half=_mm_set1_ps(.5);) ENABLE_AVX_IMPLEMENTATION(Vone_half=_mm256_set1_ps(.5);) +ENABLE_SCALAR_IMPLEMENTATION(Sone.f=1.;) ENABLE_SSE_IMPLEMENTATION(Vone=_mm_set1_ps(1.);) ENABLE_AVX_IMPLEMENTATION(Vone=_mm256_set1_ps(1.);) +ENABLE_SCALAR_IMPLEMENTATION(Stiny_number.f=1.e-20;) ENABLE_SSE_IMPLEMENTATION(Vtiny_number=_mm_set1_ps(1.e-20);) ENABLE_AVX_IMPLEMENTATION(Vtiny_number=_mm256_set1_ps(1.e-20);) +ENABLE_SCALAR_IMPLEMENTATION(Ssmall_number.f=1.e-12;) ENABLE_SSE_IMPLEMENTATION(Vsmall_number=_mm_set1_ps(1.e-12);) ENABLE_AVX_IMPLEMENTATION(Vsmall_number=_mm256_set1_ps(1.e-12);) + +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa11;) ENABLE_SSE_IMPLEMENTATION(__m128 Va11;) ENABLE_AVX_IMPLEMENTATION(__m256 Va11;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa21;) ENABLE_SSE_IMPLEMENTATION(__m128 Va21;) ENABLE_AVX_IMPLEMENTATION(__m256 Va21;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa31;) ENABLE_SSE_IMPLEMENTATION(__m128 Va31;) ENABLE_AVX_IMPLEMENTATION(__m256 Va31;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa12;) ENABLE_SSE_IMPLEMENTATION(__m128 Va12;) ENABLE_AVX_IMPLEMENTATION(__m256 Va12;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa22;) ENABLE_SSE_IMPLEMENTATION(__m128 Va22;) ENABLE_AVX_IMPLEMENTATION(__m256 Va22;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa32;) ENABLE_SSE_IMPLEMENTATION(__m128 Va32;) ENABLE_AVX_IMPLEMENTATION(__m256 Va32;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa13;) ENABLE_SSE_IMPLEMENTATION(__m128 Va13;) ENABLE_AVX_IMPLEMENTATION(__m256 Va13;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa23;) ENABLE_SSE_IMPLEMENTATION(__m128 Va23;) ENABLE_AVX_IMPLEMENTATION(__m256 Va23;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sa33;) ENABLE_SSE_IMPLEMENTATION(__m128 Va33;) ENABLE_AVX_IMPLEMENTATION(__m256 Va33;) + +#ifdef COMPUTE_V_AS_MATRIX +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv11;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv11;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv11;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv21;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv21;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv21;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv31;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv31;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv31;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv12;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv12;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv12;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv22;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv22;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv22;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv32;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv32;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv32;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv13;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv13;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv13;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv23;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv23;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv23;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sv33;) ENABLE_SSE_IMPLEMENTATION(__m128 Vv33;) ENABLE_AVX_IMPLEMENTATION(__m256 Vv33;) +#endif + +#ifdef COMPUTE_V_AS_QUATERNION +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvs;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvs;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvs;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvx;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvx;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvx;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvy;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvy;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvy;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvz;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvz;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvz;) +#endif + +#ifdef COMPUTE_U_AS_MATRIX +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su11;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu11;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu11;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su21;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu21;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu21;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su31;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu31;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu31;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su12;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu12;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu12;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su22;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu22;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu22;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su32;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu32;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu32;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su13;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu13;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu13;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su23;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu23;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu23;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Su33;) ENABLE_SSE_IMPLEMENTATION(__m128 Vu33;) ENABLE_AVX_IMPLEMENTATION(__m256 Vu33;) +#endif + +#ifdef COMPUTE_U_AS_QUATERNION +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Squs;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqus;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqus;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Squvx;) ENABLE_SSE_IMPLEMENTATION(__m128 Vquvx;) ENABLE_AVX_IMPLEMENTATION(__m256 Vquvx;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Squvy;) ENABLE_SSE_IMPLEMENTATION(__m128 Vquvy;) ENABLE_AVX_IMPLEMENTATION(__m256 Vquvy;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Squvz;) ENABLE_SSE_IMPLEMENTATION(__m128 Vquvz;) ENABLE_AVX_IMPLEMENTATION(__m256 Vquvz;) +#endif + +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sc;) ENABLE_SSE_IMPLEMENTATION(__m128 Vc;) ENABLE_AVX_IMPLEMENTATION(__m256 Vc;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sch;) ENABLE_SSE_IMPLEMENTATION(__m128 Vch;) ENABLE_AVX_IMPLEMENTATION(__m256 Vch;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ssh;) ENABLE_SSE_IMPLEMENTATION(__m128 Vsh;) ENABLE_AVX_IMPLEMENTATION(__m256 Vsh;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stmp1;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtmp1;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtmp1;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stmp2;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtmp2;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtmp2;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stmp3;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtmp3;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtmp3;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stmp4;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtmp4;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtmp4;) +ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Stmp5;) ENABLE_SSE_IMPLEMENTATION(__m128 Vtmp5;) ENABLE_AVX_IMPLEMENTATION(__m256 Vtmp5;) diff --git a/src/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp b/src/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp new file mode 100644 index 000000000..e8898a8aa --- /dev/null +++ b/src/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp @@ -0,0 +1,1277 @@ +//##################################################################### +// Copyright (c) 2010-2011, Eftychios Sifakis. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//##################################################################### + +#ifdef __INTEL_COMPILER +#pragma warning( disable : 592 ) +#endif + +// #define USE_ACCURATE_RSQRT_IN_JACOBI_CONJUGATION +// #define PERFORM_STRICT_QUATERNION_RENORMALIZATION + +{ // Begin block : Scope of qV (if not maintained) + +#ifndef COMPUTE_V_AS_QUATERNION + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvs;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvs;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvs;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvx;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvx;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvx;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvy;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvy;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvy;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Sqvvz;) ENABLE_SSE_IMPLEMENTATION(__m128 Vqvvz;) ENABLE_AVX_IMPLEMENTATION(__m256 Vqvvz;) +#endif + +{ // Begin block : Symmetric eigenanalysis + + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss11;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs11;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs11;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss21;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs21;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs21;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss31;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs31;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs31;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss22;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs22;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs22;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss32;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs32;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs32;) + ENABLE_SCALAR_IMPLEMENTATION(union {float f;unsigned int ui;} Ss33;) ENABLE_SSE_IMPLEMENTATION(__m128 Vs33;) ENABLE_AVX_IMPLEMENTATION(__m256 Vs33;) + + ENABLE_SCALAR_IMPLEMENTATION(Sqvs.f=1.;) ENABLE_SSE_IMPLEMENTATION(Vqvs=Vone;) ENABLE_AVX_IMPLEMENTATION(Vqvs=Vone;) + ENABLE_SCALAR_IMPLEMENTATION(Sqvvx.f=0.;) ENABLE_SSE_IMPLEMENTATION(Vqvvx=_mm_xor_ps(Vqvvx,Vqvvx);) ENABLE_AVX_IMPLEMENTATION(Vqvvx=_mm256_xor_ps(Vqvvx,Vqvvx);) + ENABLE_SCALAR_IMPLEMENTATION(Sqvvy.f=0.;) ENABLE_SSE_IMPLEMENTATION(Vqvvy=_mm_xor_ps(Vqvvy,Vqvvy);) ENABLE_AVX_IMPLEMENTATION(Vqvvy=_mm256_xor_ps(Vqvvy,Vqvvy);) + ENABLE_SCALAR_IMPLEMENTATION(Sqvvz.f=0.;) ENABLE_SSE_IMPLEMENTATION(Vqvvz=_mm_xor_ps(Vqvvz,Vqvvz);) ENABLE_AVX_IMPLEMENTATION(Vqvvz=_mm256_xor_ps(Vqvvz,Vqvvz);) + + //########################################################### + // Compute normal equations matrix + //########################################################### + + ENABLE_SCALAR_IMPLEMENTATION(Ss11.f=Sa11.f*Sa11.f;) ENABLE_SSE_IMPLEMENTATION(Vs11=_mm_mul_ps(Va11,Va11);) ENABLE_AVX_IMPLEMENTATION(Vs11=_mm256_mul_ps(Va11,Va11);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa21.f*Sa21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va21,Va21);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va21,Va21);) + ENABLE_SCALAR_IMPLEMENTATION(Ss11.f=Stmp1.f+Ss11.f;) ENABLE_SSE_IMPLEMENTATION(Vs11=_mm_add_ps(Vtmp1,Vs11);) ENABLE_AVX_IMPLEMENTATION(Vs11=_mm256_add_ps(Vtmp1,Vs11);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa31.f*Sa31.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va31,Va31);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va31,Va31);) + ENABLE_SCALAR_IMPLEMENTATION(Ss11.f=Stmp1.f+Ss11.f;) ENABLE_SSE_IMPLEMENTATION(Vs11=_mm_add_ps(Vtmp1,Vs11);) ENABLE_AVX_IMPLEMENTATION(Vs11=_mm256_add_ps(Vtmp1,Vs11);) + + ENABLE_SCALAR_IMPLEMENTATION(Ss21.f=Sa12.f*Sa11.f;) ENABLE_SSE_IMPLEMENTATION(Vs21=_mm_mul_ps(Va12,Va11);) ENABLE_AVX_IMPLEMENTATION(Vs21=_mm256_mul_ps(Va12,Va11);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa22.f*Sa21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va22,Va21);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va22,Va21);) + ENABLE_SCALAR_IMPLEMENTATION(Ss21.f=Stmp1.f+Ss21.f;) ENABLE_SSE_IMPLEMENTATION(Vs21=_mm_add_ps(Vtmp1,Vs21);) ENABLE_AVX_IMPLEMENTATION(Vs21=_mm256_add_ps(Vtmp1,Vs21);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa32.f*Sa31.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va32,Va31);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va32,Va31);) + ENABLE_SCALAR_IMPLEMENTATION(Ss21.f=Stmp1.f+Ss21.f;) ENABLE_SSE_IMPLEMENTATION(Vs21=_mm_add_ps(Vtmp1,Vs21);) ENABLE_AVX_IMPLEMENTATION(Vs21=_mm256_add_ps(Vtmp1,Vs21);) + + ENABLE_SCALAR_IMPLEMENTATION(Ss31.f=Sa13.f*Sa11.f;) ENABLE_SSE_IMPLEMENTATION(Vs31=_mm_mul_ps(Va13,Va11);) ENABLE_AVX_IMPLEMENTATION(Vs31=_mm256_mul_ps(Va13,Va11);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa23.f*Sa21.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va23,Va21);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va23,Va21);) + ENABLE_SCALAR_IMPLEMENTATION(Ss31.f=Stmp1.f+Ss31.f;) ENABLE_SSE_IMPLEMENTATION(Vs31=_mm_add_ps(Vtmp1,Vs31);) ENABLE_AVX_IMPLEMENTATION(Vs31=_mm256_add_ps(Vtmp1,Vs31);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa33.f*Sa31.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va33,Va31);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va33,Va31);) + ENABLE_SCALAR_IMPLEMENTATION(Ss31.f=Stmp1.f+Ss31.f;) ENABLE_SSE_IMPLEMENTATION(Vs31=_mm_add_ps(Vtmp1,Vs31);) ENABLE_AVX_IMPLEMENTATION(Vs31=_mm256_add_ps(Vtmp1,Vs31);) + + ENABLE_SCALAR_IMPLEMENTATION(Ss22.f=Sa12.f*Sa12.f;) ENABLE_SSE_IMPLEMENTATION(Vs22=_mm_mul_ps(Va12,Va12);) ENABLE_AVX_IMPLEMENTATION(Vs22=_mm256_mul_ps(Va12,Va12);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa22.f*Sa22.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va22,Va22);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va22,Va22);) + ENABLE_SCALAR_IMPLEMENTATION(Ss22.f=Stmp1.f+Ss22.f;) ENABLE_SSE_IMPLEMENTATION(Vs22=_mm_add_ps(Vtmp1,Vs22);) ENABLE_AVX_IMPLEMENTATION(Vs22=_mm256_add_ps(Vtmp1,Vs22);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa32.f*Sa32.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va32,Va32);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va32,Va32);) + ENABLE_SCALAR_IMPLEMENTATION(Ss22.f=Stmp1.f+Ss22.f;) ENABLE_SSE_IMPLEMENTATION(Vs22=_mm_add_ps(Vtmp1,Vs22);) ENABLE_AVX_IMPLEMENTATION(Vs22=_mm256_add_ps(Vtmp1,Vs22);) + + ENABLE_SCALAR_IMPLEMENTATION(Ss32.f=Sa13.f*Sa12.f;) ENABLE_SSE_IMPLEMENTATION(Vs32=_mm_mul_ps(Va13,Va12);) ENABLE_AVX_IMPLEMENTATION(Vs32=_mm256_mul_ps(Va13,Va12);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa23.f*Sa22.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va23,Va22);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va23,Va22);) + ENABLE_SCALAR_IMPLEMENTATION(Ss32.f=Stmp1.f+Ss32.f;) ENABLE_SSE_IMPLEMENTATION(Vs32=_mm_add_ps(Vtmp1,Vs32);) ENABLE_AVX_IMPLEMENTATION(Vs32=_mm256_add_ps(Vtmp1,Vs32);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa33.f*Sa32.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va33,Va32);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va33,Va32);) + ENABLE_SCALAR_IMPLEMENTATION(Ss32.f=Stmp1.f+Ss32.f;) ENABLE_SSE_IMPLEMENTATION(Vs32=_mm_add_ps(Vtmp1,Vs32);) ENABLE_AVX_IMPLEMENTATION(Vs32=_mm256_add_ps(Vtmp1,Vs32);) + + ENABLE_SCALAR_IMPLEMENTATION(Ss33.f=Sa13.f*Sa13.f;) ENABLE_SSE_IMPLEMENTATION(Vs33=_mm_mul_ps(Va13,Va13);) ENABLE_AVX_IMPLEMENTATION(Vs33=_mm256_mul_ps(Va13,Va13);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa23.f*Sa23.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va23,Va23);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va23,Va23);) + ENABLE_SCALAR_IMPLEMENTATION(Ss33.f=Stmp1.f+Ss33.f;) ENABLE_SSE_IMPLEMENTATION(Vs33=_mm_add_ps(Vtmp1,Vs33);) ENABLE_AVX_IMPLEMENTATION(Vs33=_mm256_add_ps(Vtmp1,Vs33);) + ENABLE_SCALAR_IMPLEMENTATION(Stmp1.f=Sa33.f*Sa33.f;) ENABLE_SSE_IMPLEMENTATION(Vtmp1=_mm_mul_ps(Va33,Va33);) ENABLE_AVX_IMPLEMENTATION(Vtmp1=_mm256_mul_ps(Va33,Va33);) + ENABLE_SCALAR_IMPLEMENTATION(Ss33.f=Stmp1.f+Ss33.f;) ENABLE_SSE_IMPLEMENTATION(Vs33=_mm_add_ps(Vtmp1,Vs33);) ENABLE_AVX_IMPLEMENTATION(Vs33=_mm256_add_ps(Vtmp1,Vs33);) + + //########################################################### + // Solve symmetric eigenproblem using Jacobi iteration + //########################################################### + + for(int sweep=1;sweep<=4;sweep++){ + + // First Jacobi conjugation + +#define SS11 Ss11 +#define SS21 Ss21 +#define SS31 Ss31 +#define SS22 Ss22 +#define SS32 Ss32 +#define SS33 Ss33 +#define SQVVX Sqvvx +#define SQVVY Sqvvy +#define SQVVZ Sqvvz +#define STMP1 Stmp1 +#define STMP2 Stmp2 +#define STMP3 Stmp3 + +#define VS11 Vs11 +#define VS21 Vs21 +#define VS31 Vs31 +#define VS22 Vs22 +#define VS32 Vs32 +#define VS33 Vs33 +#define VQVVX Vqvvx +#define VQVVY Vqvvy +#define VQVVZ Vqvvz +#define VTMP1 Vtmp1 +#define VTMP2 Vtmp2 +#define VTMP3 Vtmp3 + +#include "Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp" + +#undef SS11 +#undef SS21 +#undef SS31 +#undef SS22 +#undef SS32 +#undef SS33 +#undef SQVVX +#undef SQVVY +#undef SQVVZ +#undef STMP1 +#undef STMP2 +#undef STMP3 + +#undef VS11 +#undef VS21 +#undef VS31 +#undef VS22 +#undef VS32 +#undef VS33 +#undef VQVVX +#undef VQVVY +#undef VQVVZ +#undef VTMP1 +#undef VTMP2 +#undef VTMP3 + + // Second Jacobi conjugation + +#define SS11 Ss22 +#define SS21 Ss32 +#define SS31 Ss21 +#define SS22 Ss33 +#define SS32 Ss31 +#define SS33 Ss11 +#define SQVVX Sqvvy +#define SQVVY Sqvvz +#define SQVVZ Sqvvx +#define STMP1 Stmp2 +#define STMP2 Stmp3 +#define STMP3 Stmp1 + +#define VS11 Vs22 +#define VS21 Vs32 +#define VS31 Vs21 +#define VS22 Vs33 +#define VS32 Vs31 +#define VS33 Vs11 +#define VQVVX Vqvvy +#define VQVVY Vqvvz +#define VQVVZ Vqvvx +#define VTMP1 Vtmp2 +#define VTMP2 Vtmp3 +#define VTMP3 Vtmp1 + +#include "Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp" + +#undef SS11 +#undef SS21 +#undef SS31 +#undef SS22 +#undef SS32 +#undef SS33 +#undef SQVVX +#undef SQVVY +#undef SQVVZ +#undef STMP1 +#undef STMP2 +#undef STMP3 + +#undef VS11 +#undef VS21 +#undef VS31 +#undef VS22 +#undef VS32 +#undef VS33 +#undef VQVVX +#undef VQVVY +#undef VQVVZ +#undef VTMP1 +#undef VTMP2 +#undef VTMP3 + + // Third Jacobi conjugation + +#define SS11 Ss33 +#define SS21 Ss31 +#define SS31 Ss32 +#define SS22 Ss11 +#define SS32 Ss21 +#define SS33 Ss22 +#define SQVVX Sqvvz +#define SQVVY Sqvvx +#define SQVVZ Sqvvy +#define STMP1 Stmp3 +#define STMP2 Stmp1 +#define STMP3 Stmp2 + +#define VS11 Vs33 +#define VS21 Vs31 +#define VS31 Vs32 +#define VS22 Vs11 +#define VS32 Vs21 +#define VS33 Vs22 +#define VQVVX Vqvvz +#define VQVVY Vqvvx +#define VQVVZ Vqvvy +#define VTMP1 Vtmp3 +#define VTMP2 Vtmp1 +#define VTMP3 Vtmp2 + +#include "Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp" + +#undef SS11 +#undef SS21 +#undef SS31 +#undef SS22 +#undef SS32 +#undef SS33 +#undef SQVVX +#undef SQVVY +#undef SQVVZ +#undef STMP1 +#undef STMP2 +#undef STMP3 + +#undef VS11 +#undef VS21 +#undef VS31 +#undef VS22 +#undef VS32 +#undef VS33 +#undef VQVVX +#undef VQVVY +#undef VQVVZ +#undef VTMP1 +#undef VTMP2 +#undef VTMP3 + } + +#ifdef PRINT_DEBUGGING_OUTPUT +#ifdef USE_SCALAR_IMPLEMENTATION + std::cout<<"Scalar S ="< +#include +#endif + +// Prevent warnings +#ifdef ENABLE_SCALAR_IMPLEMENTATION +# undef ENABLE_SCALAR_IMPLEMENTATION +#endif +#ifdef ENABLE_SSE_IMPLEMENTATION +# undef ENABLE_SSE_IMPLEMENTATION +#endif +#ifdef ENABLE_AVX_IMPLEMENTATION +# undef ENABLE_AVX_IMPLEMENTATION +#endif + +#ifdef USE_SCALAR_IMPLEMENTATION +#define ENABLE_SCALAR_IMPLEMENTATION(X) X +#else +#define ENABLE_SCALAR_IMPLEMENTATION(X) +#endif + +#ifdef USE_SSE_IMPLEMENTATION +#define ENABLE_SSE_IMPLEMENTATION(X) X +#else +#define ENABLE_SSE_IMPLEMENTATION(X) +#endif + +#ifdef USE_AVX_IMPLEMENTATION +#include +#define ENABLE_AVX_IMPLEMENTATION(X) X +#else +// Stefan: removed include. Why does it import MMX instructions, shouldn't this be under the #ifdef USE_SSE_IMPLEMENTATION above? +//#include +#define ENABLE_AVX_IMPLEMENTATION(X) +#endif + +#ifdef USE_SCALAR_IMPLEMENTATION +// Alec: Why is this using sse intrinsics if it's supposed to be the scalar +// implementation? +#ifdef __SSE__ +#include +// Changed to inline +inline float rsqrt(const float f) +{ + float buf[4]; + buf[0]=f; + __m128 v=_mm_loadu_ps(buf); + v=_mm_rsqrt_ss(v); + _mm_storeu_ps(buf,v); + return buf[0]; +} +#else +#include +inline float rsqrt(const float f) +{ + return 1./sqrtf(f); +} +#endif +#endif + + diff --git a/src/igl/SolverStatus.h b/src/igl/SolverStatus.h new file mode 100644 index 000000000..d9151f5c0 --- /dev/null +++ b/src/igl/SolverStatus.h @@ -0,0 +1,23 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SOLVER_STATUS_H +#define IGL_SOLVER_STATUS_H +namespace igl +{ + enum SolverStatus + { + // Good + SOLVER_STATUS_CONVERGED = 0, + // OK + SOLVER_STATUS_MAX_ITER = 1, + // Bad + SOLVER_STATUS_ERROR = 2, + NUM_SOLVER_STATUSES = 3, + }; +}; +#endif diff --git a/src/igl/SortableRow.h b/src/igl/SortableRow.h new file mode 100644 index 000000000..5f172987b --- /dev/null +++ b/src/igl/SortableRow.h @@ -0,0 +1,70 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SORTABLE_ROW_H +#define IGL_SORTABLE_ROW_H + +// Simple class to contain a rowvector which allows rowwise sorting and +// reordering +#include + +namespace igl +{ + // Templates: + // T should be a matrix that implements .size(), and operator(int i) + template + class SortableRow + { + public: + T data; + public: + SortableRow():data(){}; + SortableRow(const T & data):data(data){}; + bool operator<(const SortableRow & that) const + { + // Get reference so that I can use parenthesis + const SortableRow & THIS = *this; + // Lexicographical + int minc = (THIS.data.size() < that.data.size()? + THIS.data.size() : that.data.size()); + // loop over columns + for(int i = 0;i & THIS = *this; + if(THIS.data.size() != that.data.size()) + { + return false; + } + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// High Resolution Timer. +// +// Resolution on Mac (clock tick) +// Resolution on Linux (1 us not tested) +// Resolution on Windows (clock tick not tested) + +#ifndef IGL_TIMER_H +#define IGL_TIMER_H + +#ifdef WIN32 // Windows system specific +#include +#elif __APPLE__ // Unix based system specific +#include // for mach_absolute_time +#else +#include +#endif +#include + +namespace igl +{ + class Timer + { + public: + // default constructor + Timer(): + stopped(0), +#ifdef WIN32 + frequency(), + startCount(), + endCount() +#elif __APPLE__ + startCount(0), + endCount(0) +#else + startCount(), + endCount() +#endif + { +#ifdef WIN32 + QueryPerformanceFrequency(&frequency); + startCount.QuadPart = 0; + endCount.QuadPart = 0; +#elif __APPLE__ + startCount = 0; + endCount = 0; +#else + startCount.tv_sec = startCount.tv_usec = 0; + endCount.tv_sec = endCount.tv_usec = 0; +#endif + + stopped = 0; + } + // default destructor + ~Timer() + { + + } + +#ifdef __APPLE__ + //Raw mach_absolute_times going in, difference in seconds out + double subtractTimes( uint64_t endTime, uint64_t startTime ) + { + uint64_t difference = endTime - startTime; + static double conversion = 0.0; + + if( conversion == 0.0 ) + { + mach_timebase_info_data_t info; + kern_return_t err = mach_timebase_info( &info ); + + //Convert the timebase into seconds + if( err == 0 ) + conversion = 1e-9 * (double) info.numer / (double) info.denom; + } + + return conversion * (double) difference; + } +#endif + + // start timer + void start() + { + stopped = 0; // reset stop flag +#ifdef WIN32 + QueryPerformanceCounter(&startCount); +#elif __APPLE__ + startCount = mach_absolute_time(); +#else + gettimeofday(&startCount, NULL); +#endif + + } + + // stop the timer + void stop() + { + stopped = 1; // set timer stopped flag + +#ifdef WIN32 + QueryPerformanceCounter(&endCount); +#elif __APPLE__ + endCount = mach_absolute_time(); +#else + gettimeofday(&endCount, NULL); +#endif + + } + // get elapsed time in second + double getElapsedTime() + { + return this->getElapsedTimeInSec(); + } + // get elapsed time in second (same as getElapsedTime) + double getElapsedTimeInSec() + { + return this->getElapsedTimeInMicroSec() * 0.000001; + } + + // get elapsed time in milli-second + double getElapsedTimeInMilliSec() + { + return this->getElapsedTimeInMicroSec() * 0.001; + } + // get elapsed time in micro-second + double getElapsedTimeInMicroSec() + { + double startTimeInMicroSec = 0; + double endTimeInMicroSec = 0; + +#ifdef WIN32 + if(!stopped) + QueryPerformanceCounter(&endCount); + + startTimeInMicroSec = + startCount.QuadPart * (1000000.0 / frequency.QuadPart); + endTimeInMicroSec = endCount.QuadPart * (1000000.0 / frequency.QuadPart); +#elif __APPLE__ + if (!stopped) + endCount = mach_absolute_time(); + + return subtractTimes(endCount,startCount)/1e-6; +#else + if(!stopped) + gettimeofday(&endCount, NULL); + + startTimeInMicroSec = + (startCount.tv_sec * 1000000.0) + startCount.tv_usec; + endTimeInMicroSec = (endCount.tv_sec * 1000000.0) + endCount.tv_usec; +#endif + + return endTimeInMicroSec - startTimeInMicroSec; + } + + private: + // stop flag + int stopped; +#ifdef WIN32 + // ticks per second + LARGE_INTEGER frequency; + LARGE_INTEGER startCount; + LARGE_INTEGER endCount; +#elif __APPLE__ + uint64_t startCount; + uint64_t endCount; +#else + timeval startCount; + timeval endCount; +#endif + }; +} +#endif // TIMER_H_DEF + diff --git a/src/igl/Viewport.h b/src/igl/Viewport.h new file mode 100644 index 000000000..717d3f97b --- /dev/null +++ b/src/igl/Viewport.h @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VIEWPORT_H +#define IGL_VIEWPORT_H + +namespace igl +{ + // Simple Viewport class for an opengl context. Handles reshaping and mouse. + struct Viewport + { + int x,y,width,height; + // Constructors + Viewport( + const int x=0, + const int y=0, + const int width=0, + const int height=0): + x(x), + y(y), + width(width), + height(height) + { + }; + virtual ~Viewport(){} + void reshape( + const int x, + const int y, + const int width, + const int height) + { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + }; + // Given mouse_x,mouse_y on the entire window return mouse_x, mouse_y in + // this viewport. + // + // Inputs: + // my mouse y-coordinate + // wh window height + // Returns y-coordinate in viewport + int mouse_y(const int my,const int wh) + { + return my - (wh - height - y); + } + // Inputs: + // mx mouse x-coordinate + // Returns x-coordinate in viewport + int mouse_x(const int mx) + { + return mx - x; + } + // Returns whether point (mx,my) is in extend of Viewport + bool inside(const int mx, const int my) const + { + return + mx >= x && my >= y && + mx < x+width && my < y+height; + } + }; +} + +#endif diff --git a/src/igl/WindingNumberAABB.h b/src/igl/WindingNumberAABB.h new file mode 100644 index 000000000..478026989 --- /dev/null +++ b/src/igl/WindingNumberAABB.h @@ -0,0 +1,377 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +// # MUTUAL DEPENDENCY ISSUE FOR HEADER ONLY VERSION +// MUST INCLUDE winding_number.h first before guard: +#include "winding_number.h" + +#ifndef IGL_WINDINGNUMBERAABB_H +#define IGL_WINDINGNUMBERAABB_H +#include "WindingNumberTree.h" + +namespace igl +{ + template < + typename Point, + typename DerivedV, + typename DerivedF > + class WindingNumberAABB : public WindingNumberTree + { + protected: + Point min_corner; + Point max_corner; + typename DerivedV::Scalar total_positive_area; + public: + enum SplitMethod + { + CENTER_ON_LONGEST_AXIS = 0, + MEDIAN_ON_LONGEST_AXIS = 1, + NUM_SPLIT_METHODS = 2 + } split_method; + public: + inline WindingNumberAABB(): + total_positive_area(std::numeric_limits::infinity()), + split_method(MEDIAN_ON_LONGEST_AXIS) + {} + inline WindingNumberAABB( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F); + inline WindingNumberAABB( + const WindingNumberTree & parent, + const Eigen::MatrixBase & F); + // Initialize some things + inline void set_mesh( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F); + inline void init(); + inline bool inside(const Point & p) const; + inline virtual void grow(); + // Compute min and max corners + inline void compute_min_max_corners(); + inline typename DerivedV::Scalar max_abs_winding_number(const Point & p) const; + inline typename DerivedV::Scalar max_simple_abs_winding_number(const Point & p) const; + }; +} + +// Implementation + +#include "winding_number.h" + +#include "barycenter.h" +#include "median.h" +#include "doublearea.h" +#include "per_face_normals.h" + +#include +#include +#include + +// Minimum number of faces in a hierarchy element (this is probably dependent +// on speed of machine and compiler optimization) +#ifndef WindingNumberAABB_MIN_F +# define WindingNumberAABB_MIN_F 100 +#endif + +template +inline void igl::WindingNumberAABB::set_mesh( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F) +{ + igl::WindingNumberTree::set_mesh(V,F); + init(); +} + +template +inline void igl::WindingNumberAABB::init() +{ + using namespace Eigen; + assert(max_corner.size() == 3); + assert(min_corner.size() == 3); + compute_min_max_corners(); + Eigen::Matrix dblA; + doublearea(this->getV(),this->getF(),dblA); + total_positive_area = dblA.sum()/2.0; +} + +template +inline igl::WindingNumberAABB::WindingNumberAABB( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F): + WindingNumberTree(V,F), + min_corner(), + max_corner(), + total_positive_area( + std::numeric_limits::infinity()), + split_method(MEDIAN_ON_LONGEST_AXIS) +{ + init(); +} + +template +inline igl::WindingNumberAABB::WindingNumberAABB( + const WindingNumberTree & parent, + const Eigen::MatrixBase & F): + WindingNumberTree(parent,F), + min_corner(), + max_corner(), + total_positive_area( + std::numeric_limits::infinity()), + split_method(MEDIAN_ON_LONGEST_AXIS) +{ + init(); +} + +template +inline void igl::WindingNumberAABB::grow() +{ + using namespace std; + using namespace Eigen; + // Clear anything that already exists + this->delete_children(); + + //cout<<"cap.rows(): "<getcap().rows()<getF().rows()<getF().rows() <= (WindingNumberAABB_MIN_F>0?WindingNumberAABB_MIN_F:0) || + (this->getcap().rows() - 2) >= this->getF().rows()) + { + // Don't grow + return; + } + + // Compute longest direction + int max_d = -1; + typename DerivedV::Scalar max_len = + -numeric_limits::infinity(); + for(int d = 0;d max_len ) + { + max_len = (max_corner[d] - min_corner[d]); + max_d = d; + } + } + // Compute facet barycenters + Eigen::Matrix BC; + barycenter(this->getV(),this->getF(),BC); + + + // Blerg, why is selecting rows so difficult + + typename DerivedV::Scalar split_value; + // Split in longest direction + switch(split_method) + { + case MEDIAN_ON_LONGEST_AXIS: + // Determine median + median(BC.col(max_d),split_value); + break; + default: + assert(false); + case CENTER_ON_LONGEST_AXIS: + split_value = 0.5*(max_corner[max_d] + min_corner[max_d]); + break; + } + //cout<<"c: "<<0.5*(max_corner[max_d] + min_corner[max_d])<<" "<< + // "m: "< id( this->getF().rows()); + for(int i = 0;igetF().rows();i++) + { + if(BC(i,max_d) <= split_value) + { + id[i] = 0; //left + }else + { + id[i] = 1; //right + } + } + + const int lefts = (int) count(id.begin(),id.end(),0); + const int rights = (int) count(id.begin(),id.end(),1); + if(lefts == 0 || rights == 0) + { + // badly balanced base case (could try to recut) + return; + } + assert(lefts+rights == this->getF().rows()); + DerivedF leftF(lefts, this->getF().cols()); + DerivedF rightF(rights,this->getF().cols()); + int left_i = 0; + int right_i = 0; + for(int i = 0;igetF().rows();i++) + { + if(id[i] == 0) + { + leftF.row(left_i++) = this->getF().row(i); + }else if(id[i] == 1) + { + rightF.row(right_i++) = this->getF().row(i); + }else + { + assert(false); + } + } + assert(right_i == rightF.rows()); + assert(left_i == leftF.rows()); + // Finally actually grow children and Recursively grow + WindingNumberAABB * leftWindingNumberAABB = + new WindingNumberAABB(*this,leftF); + leftWindingNumberAABB->grow(); + this->children.push_back(leftWindingNumberAABB); + WindingNumberAABB * rightWindingNumberAABB = + new WindingNumberAABB(*this,rightF); + rightWindingNumberAABB->grow(); + this->children.push_back(rightWindingNumberAABB); +} + +template +inline bool igl::WindingNumberAABB::inside(const Point & p) const +{ + assert(p.size() == max_corner.size()); + assert(p.size() == min_corner.size()); + for(int i = 0;i= max_corner(i)) + // **MUST** be conservative + if( p(i) < min_corner(i) || p(i) > max_corner(i)) + { + return false; + } + } + return true; +} + +template +inline void igl::WindingNumberAABB::compute_min_max_corners() +{ + using namespace std; + // initialize corners + for(int d = 0;d::infinity(); + max_corner[d] = -numeric_limits::infinity(); + } + + this->center = Point(0,0,0); + // Loop over facets + for(int i = 0;igetF().rows();i++) + { + for(int j = 0;jgetF().cols();j++) + { + for(int d = 0;dgetV()(this->getF()(i,j),d) < min_corner[d] ? + this->getV()(this->getF()(i,j),d) : min_corner[d]; + max_corner[d] = + this->getV()(this->getF()(i,j),d) > max_corner[d] ? + this->getV()(this->getF()(i,j),d) : max_corner[d]; + } + // This is biased toward vertices incident on more than one face, but + // perhaps that's good + this->center += this->getV().row(this->getF()(i,j)); + } + } + // Average + this->center.array() /= this->getF().size(); + + //cout<<"min_corner: "<min_corner.transpose()<center.transpose()<max_corner.transpose()<max_corner + this->min_corner)*0.5).transpose()<radius = (max_corner-min_corner).norm()/2.0; +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberAABB::max_abs_winding_number(const Point & p) const +{ + using namespace std; + // Only valid if not inside + if(inside(p)) + { + return numeric_limits::infinity(); + } + // Q: we know the total positive area so what's the most this could project + // to? Remember it could be layered in the same direction. + return numeric_limits::infinity(); +} + +template +inline typename DerivedV::Scalar + igl::WindingNumberAABB::max_simple_abs_winding_number( + const Point & p) const +{ + using namespace std; + using namespace Eigen; + // Only valid if not inside + if(inside(p)) + { + return numeric_limits::infinity(); + } + // Max simple is the same as sum of positive winding number contributions of + // bounding box + + // begin precomputation + //MatrixXd BV((int)pow(2,3),3); + typedef + Eigen::Matrix + MatrixXS; + typedef + Eigen::Matrix + MatrixXF; + MatrixXS BV((int)(1<<3),3); + BV << + min_corner[0],min_corner[1],min_corner[2], + min_corner[0],min_corner[1],max_corner[2], + min_corner[0],max_corner[1],min_corner[2], + min_corner[0],max_corner[1],max_corner[2], + max_corner[0],min_corner[1],min_corner[2], + max_corner[0],min_corner[1],max_corner[2], + max_corner[0],max_corner[1],min_corner[2], + max_corner[0],max_corner[1],max_corner[2]; + MatrixXF BF(2*2*3,3); + BF << + 0,6,4, + 0,2,6, + 0,3,2, + 0,1,3, + 2,7,6, + 2,3,7, + 4,6,7, + 4,7,5, + 0,4,5, + 0,5,1, + 1,5,7, + 1,7,3; + MatrixXS BFN; + per_face_normals(BV,BF,BFN); + // end of precomputation + + // Only keep those with positive dot products + MatrixXF PBF(BF.rows(),BF.cols()); + int pbfi = 0; + Point p2c = 0.5*(min_corner+max_corner)-p; + for(int i = 0;i 0) + { + PBF.row(pbfi++) = BF.row(i); + } + } + PBF.conservativeResize(pbfi,PBF.cols()); + return igl::winding_number(BV,PBF,p); +} + +#endif diff --git a/src/igl/WindingNumberMethod.h b/src/igl/WindingNumberMethod.h new file mode 100644 index 000000000..3bb9062b1 --- /dev/null +++ b/src/igl/WindingNumberMethod.h @@ -0,0 +1,23 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WINDINGNUMBERMETHOD_H +#define IGL_WINDINGNUMBERMETHOD_H +namespace igl +{ + // EXACT_WINDING_NUMBER_METHOD exact hierarchical evaluation + // APPROX_SIMPLE_WINDING_NUMBER_METHOD poor approximation + // APPROX_CACHE_WINDING_NUMBER_METHOD another poor approximation + enum WindingNumberMethod + { + EXACT_WINDING_NUMBER_METHOD = 0, + APPROX_SIMPLE_WINDING_NUMBER_METHOD = 1, + APPROX_CACHE_WINDING_NUMBER_METHOD = 2, + NUM_WINDING_NUMBER_METHODS = 3 + }; +} +#endif diff --git a/src/igl/WindingNumberTree.h b/src/igl/WindingNumberTree.h new file mode 100644 index 000000000..317ad9442 --- /dev/null +++ b/src/igl/WindingNumberTree.h @@ -0,0 +1,503 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WINDINGNUMBERTREE_H +#define IGL_WINDINGNUMBERTREE_H +#include +#include +#include +#include "WindingNumberMethod.h" + +namespace igl +{ + // Space partitioning tree for computing winding number hierarchically. + // + // Templates: + // Point type for points in space, e.g. Eigen::Vector3d + template < + typename Point, + typename DerivedV, + typename DerivedF > + class WindingNumberTree + { + public: + // Method to use (see enum above) + //static double min_max_w; + static std::map< + std::pair, + typename DerivedV::Scalar> + cached; + // This is only need to fill in references, it should never actually be touched + // and shouldn't cause race conditions. (This is a hack, but I think it's "safe") + static DerivedV dummyV; + protected: + WindingNumberMethod method; + const WindingNumberTree * parent; + std::list children; + typedef + Eigen::Matrix + MatrixXS; + typedef + Eigen::Matrix + MatrixXF; + //// List of boundary edges (recall edges are vertices in 2d) + //const Eigen::MatrixXi boundary; + // Base mesh vertices + DerivedV & V; + // Base mesh vertices with duplicates removed + MatrixXS SV; + // Facets in this bounding volume + MatrixXF F; + // Tessellated boundary curve + MatrixXF cap; + // Upper Bound on radius of enclosing ball + typename DerivedV::Scalar radius; + // (Approximate) center (of mass) + Point center; + public: + inline WindingNumberTree(); + // For root + inline WindingNumberTree( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F); + // For chilluns + inline WindingNumberTree( + const WindingNumberTree & parent, + const Eigen::MatrixBase & F); + inline virtual ~WindingNumberTree(); + inline void delete_children(); + inline virtual void set_mesh( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F); + // Set method + inline void set_method( const WindingNumberMethod & m); + public: + inline const DerivedV & getV() const; + inline const MatrixXF & getF() const; + inline const MatrixXF & getcap() const; + // Grow the Tree recursively + inline virtual void grow(); + // Determine whether a given point is inside the bounding + // + // Inputs: + // p query point + // Returns true if the point p is inside this bounding volume + inline virtual bool inside(const Point & p) const; + // Compute the (partial) winding number of a given point p + // According to method + // + // Inputs: + // p query point + // Returns winding number + inline typename DerivedV::Scalar winding_number(const Point & p) const; + // Same as above, but always computes winding number using exact method + // (sum over every facet) + inline typename DerivedV::Scalar winding_number_all(const Point & p) const; + // Same as above, but always computes using sum over tessllated boundary + inline typename DerivedV::Scalar winding_number_boundary(const Point & p) const; + //// Same as winding_number above, but if max_simple_abs_winding_number is + //// less than some threshold min_max_w just return 0 (colloquially the "fast + //// multipole method) + //// + //// + //// Inputs: + //// p query point + //// min_max_w minimum max simple w to be processed + //// Returns approximate winding number + //double winding_number_approx_simple( + // const Point & p, + // const double min_max_w); + // Print contents of Tree + // + // Optional input: + // tab tab to show depth + inline void print(const char * tab=""); + // Determine max absolute winding number + // + // Inputs: + // p query point + // Returns max winding number of + inline virtual typename DerivedV::Scalar max_abs_winding_number(const Point & p) const; + // Same as above, but stronger assumptions on (V,F). Assumes (V,F) is a + // simple polyhedron + inline virtual typename DerivedV::Scalar max_simple_abs_winding_number(const Point & p) const; + // Compute or read cached winding number for point p with respect to mesh + // in bounding box, recursing according to approximation criteria + // + // Inputs: + // p query point + // that WindingNumberTree containing mesh w.r.t. which we're computing w.n. + // Returns cached winding number + inline virtual typename DerivedV::Scalar cached_winding_number(const WindingNumberTree & that, const Point & p) const; + }; +} + +// Implementation + +#include "WindingNumberTree.h" +#include "winding_number.h" +#include "triangle_fan.h" +#include "exterior_edges.h" + +#include +#include + +#include +#include + +//template +//WindingNumberMethod WindingNumberTree::method = EXACT_WINDING_NUMBER_METHOD; +//template +//double WindingNumberTree::min_max_w = 0; +template +std::map< std::pair*,const igl::WindingNumberTree*>, typename DerivedV::Scalar> + igl::WindingNumberTree::cached; + +template +inline igl::WindingNumberTree::WindingNumberTree(): + method(EXACT_WINDING_NUMBER_METHOD), + parent(NULL), + V(dummyV), + SV(), + F(), + //boundary(igl::boundary_facets(F)) + cap(), + radius(std::numeric_limits::infinity()), + center(0,0,0) +{ +} + +template +inline igl::WindingNumberTree::WindingNumberTree( + const Eigen::MatrixBase & _V, + const Eigen::MatrixBase & _F): + method(EXACT_WINDING_NUMBER_METHOD), + parent(NULL), + V(dummyV), + SV(), + F(), + //boundary(igl::boundary_facets(F)) + cap(), + radius(std::numeric_limits::infinity()), + center(0,0,0) +{ + set_mesh(_V,_F); +} + +template +inline void igl::WindingNumberTree::set_mesh( + const Eigen::MatrixBase & _V, + const Eigen::MatrixBase & _F) +{ + using namespace std; + // Remove any exactly duplicate vertices + // Q: Can this ever increase the complexity of the boundary? + // Q: Would we gain even more by remove almost exactly duplicate vertices? + MatrixXF SF,SVI,SVJ; + igl::remove_duplicate_vertices(_V,_F,0.0,SV,SVI,SVJ,F); + triangle_fan(igl::exterior_edges(F),cap); + V = SV; +} + +template +inline igl::WindingNumberTree::WindingNumberTree( + const igl::WindingNumberTree & parent, + const Eigen::MatrixBase & _F): + method(parent.method), + parent(&parent), + V(parent.V), + SV(), + F(_F), + cap(triangle_fan(igl::exterior_edges(_F))) +{ +} + +template +inline igl::WindingNumberTree::~WindingNumberTree() +{ + delete_children(); +} + +template +inline void igl::WindingNumberTree::delete_children() +{ + using namespace std; + // Delete children + typename list* >::iterator cit = children.begin(); + while(cit != children.end()) + { + // clear the memory of this item + delete (* cit); + // erase from list, returns next element in iterator + cit = children.erase(cit); + } +} + +template +inline void igl::WindingNumberTree::set_method(const WindingNumberMethod & m) +{ + this->method = m; + for(auto child : children) + { + child->set_method(m); + } +} + +template +inline const DerivedV & igl::WindingNumberTree::getV() const +{ + return V; +} + +template +inline const typename igl::WindingNumberTree::MatrixXF& + igl::WindingNumberTree::getF() const +{ + return F; +} + +template +inline const typename igl::WindingNumberTree::MatrixXF& + igl::WindingNumberTree::getcap() const +{ + return cap; +} + +template +inline void igl::WindingNumberTree::grow() +{ + // Don't grow + return; +} + +template +inline bool igl::WindingNumberTree::inside(const Point & /*p*/) const +{ + return true; +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberTree::winding_number(const Point & p) const +{ + using namespace std; + //cout<<"+"<0) + { + // Recurse on each child and accumulate + typename DerivedV::Scalar sum = 0; + for( + typename list* >::const_iterator cit = children.begin(); + cit != children.end(); + cit++) + { + switch(method) + { + case EXACT_WINDING_NUMBER_METHOD: + sum += (*cit)->winding_number(p); + break; + case APPROX_SIMPLE_WINDING_NUMBER_METHOD: + case APPROX_CACHE_WINDING_NUMBER_METHOD: + //if((*cit)->max_simple_abs_winding_number(p) > min_max_w) + //{ + sum += (*cit)->winding_number(p); + //} + break; + default: + assert(false); + break; + } + } + return sum; + }else + { + return winding_number_all(p); + } + }else{ + // Otherwise we can just consider boundary + // Q: If we using the "multipole" method should we also subdivide the + // boundary case? + if((cap.rows() - 2) < F.rows()) + { + switch(method) + { + case EXACT_WINDING_NUMBER_METHOD: + return winding_number_boundary(p); + case APPROX_SIMPLE_WINDING_NUMBER_METHOD: + { + typename DerivedV::Scalar dist = (p-center).norm(); + // Radius is already an overestimate of inside + if(dist>1.0*radius) + { + return 0; + }else + { + return winding_number_boundary(p); + } + } + case APPROX_CACHE_WINDING_NUMBER_METHOD: + { + return parent->cached_winding_number(*this,p); + } + default: assert(false);break; + } + }else + { + // doesn't pay off to use boundary + return winding_number_all(p); + } + } + return 0; +} + +template +inline typename DerivedV::Scalar + igl::WindingNumberTree::winding_number_all(const Point & p) const +{ + return igl::winding_number(V,F,p); +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberTree::winding_number_boundary(const Point & p) const +{ + using namespace Eigen; + using namespace std; + return igl::winding_number(V,cap,p); +} + +//template +//inline double igl::WindingNumberTree::winding_number_approx_simple( +// const Point & p, +// const double min_max_w) +//{ +// using namespace std; +// if(max_simple_abs_winding_number(p) > min_max_w) +// { +// return winding_number(p); +// }else +// { +// cout<<"Skipped! "< +inline void igl::WindingNumberTree::print(const char * tab) +{ + using namespace std; + // Print all facets + cout<* >::iterator cit = children.begin(); + cit != children.end(); + cit++) + { + cout<<","<print((string(tab)+"").c_str()); + } +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberTree::max_abs_winding_number(const Point & /*p*/) const +{ + return std::numeric_limits::infinity(); +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberTree::max_simple_abs_winding_number( + const Point & /*p*/) const +{ + using namespace std; + return numeric_limits::infinity(); +} + +template +inline typename DerivedV::Scalar +igl::WindingNumberTree::cached_winding_number( + const igl::WindingNumberTree & that, + const Point & p) const +{ + using namespace std; + // Simple metric for `is_far` + // + // this that + // -------- + // ----- / | \ . + // / r \ / R \ . + // | p ! | | ! | + // \_____/ \ / + // \________/ + // + // + // a = angle formed by trapazoid formed by raising sides with lengths r and R + // at respective centers. + // + // a = atan2(R-r,d), where d is the distance between centers + + // That should be bigger (what about parent? what about sister?) + bool is_far = this->radiusradius, + (that.center - this->center).norm()); + assert(a>0); + is_far = (a this_that(this,&that); + // Need to compute it for first time? + if(cached.count(this_that)==0) + { + cached[this_that] = + that.winding_number_boundary(this->center); + } + return cached[this_that]; + }else if(children.size() == 0) + { + // not far and hierarchy ended too soon: can't use cache + return that.winding_number_boundary(p); + }else + { + for( + typename list* >::const_iterator cit = children.begin(); + cit != children.end(); + cit++) + { + if((*cit)->inside(p)) + { + return (*cit)->cached_winding_number(that,p); + } + } + // Not inside any children? This can totally happen because bounding boxes + // are set to bound contained facets. So sibilings may overlap and their + // union may not contain their parent (though, their union is certainly a + // subset of their parent). + assert(false); + } + return 0; +} + +// Explicit instantiation of static variable +template < + typename Point, + typename DerivedV, + typename DerivedF > +DerivedV igl::WindingNumberTree::dummyV; + +#endif diff --git a/src/igl/ZERO.h b/src/igl/ZERO.h new file mode 100644 index 000000000..bd2829905 --- /dev/null +++ b/src/igl/ZERO.h @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ZERO_H +#define IGL_ZERO_H +namespace igl +{ + // Often one needs a reference to a dummy variable containing zero as its + // value, for example when using AntTweakBar's + // TwSetParam( "3D View", "opened", TW_PARAM_INT32, 1, &INT_ZERO); + const char CHAR_ZERO = 0; + const int INT_ZERO = 0; + const unsigned int UNSIGNED_INT_ZERO = 0; + const double DOUBLE_ZERO = 0; + const float FLOAT_ZERO = 0; +} +#endif diff --git a/src/igl/active_set.cpp b/src/igl/active_set.cpp new file mode 100755 index 000000000..fd5dfba88 --- /dev/null +++ b/src/igl/active_set.cpp @@ -0,0 +1,370 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "active_set.h" +#include "min_quad_with_fixed.h" +#include "slice.h" +#include "slice_into.h" +#include "cat.h" +//#include "matlab_format.h" + +#include +#include +#include + +template < + typename AT, + typename DerivedB, + typename Derivedknown, + typename DerivedY, + typename AeqT, + typename DerivedBeq, + typename AieqT, + typename DerivedBieq, + typename Derivedlx, + typename Derivedux, + typename DerivedZ + > +IGL_INLINE igl::SolverStatus igl::active_set( + const Eigen::SparseMatrix& A, + const Eigen::PlainObjectBase & B, + const Eigen::PlainObjectBase & known, + const Eigen::PlainObjectBase & Y, + const Eigen::SparseMatrix& Aeq, + const Eigen::PlainObjectBase & Beq, + const Eigen::SparseMatrix& Aieq, + const Eigen::PlainObjectBase & Bieq, + const Eigen::PlainObjectBase & p_lx, + const Eigen::PlainObjectBase & p_ux, + const igl::active_set_params & params, + Eigen::PlainObjectBase & Z + ) +{ +//#define ACTIVE_SET_CPP_DEBUG +#if defined(ACTIVE_SET_CPP_DEBUG) && !defined(_MSC_VER) +# warning "ACTIVE_SET_CPP_DEBUG" +#endif + using namespace Eigen; + using namespace std; + SolverStatus ret = SOLVER_STATUS_ERROR; + const int n = A.rows(); + assert(n == A.cols() && "A must be square"); + // Discard const qualifiers + //if(B.size() == 0) + //{ + // B = DerivedB::Zero(n,1); + //} + assert(n == B.rows() && "B.rows() must match A.rows()"); + assert(B.cols() == 1 && "B must be a column vector"); + assert(Y.cols() == 1 && "Y must be a column vector"); + assert((Aeq.size() == 0 && Beq.size() == 0) || Aeq.cols() == n); + assert((Aeq.size() == 0 && Beq.size() == 0) || Aeq.rows() == Beq.rows()); + assert((Aeq.size() == 0 && Beq.size() == 0) || Beq.cols() == 1); + assert((Aieq.size() == 0 && Bieq.size() == 0) || Aieq.cols() == n); + assert((Aieq.size() == 0 && Bieq.size() == 0) || Aieq.rows() == Bieq.rows()); + assert((Aieq.size() == 0 && Bieq.size() == 0) || Bieq.cols() == 1); + Eigen::Matrix lx; + Eigen::Matrix ux; + if(p_lx.size() == 0) + { + lx = Derivedlx::Constant( + n,1,-numeric_limits::max()); + }else + { + lx = p_lx; + } + if(p_ux.size() == 0) + { + ux = Derivedux::Constant( + n,1,numeric_limits::max()); + }else + { + ux = p_ux; + } + assert(lx.rows() == n && "lx must have n rows"); + assert(ux.rows() == n && "ux must have n rows"); + assert(ux.cols() == 1 && "lx must be a column vector"); + assert(lx.cols() == 1 && "ux must be a column vector"); + assert((ux.array()-lx.array()).minCoeff() > 0 && "ux(i) must be > lx(i)"); + if(Z.size() != 0) + { + // Initial guess should have correct size + assert(Z.rows() == n && "Z must have n rows"); + assert(Z.cols() == 1 && "Z must be a column vector"); + } + assert(known.cols() == 1 && "known must be a column vector"); + // Number of knowns + const int nk = known.size(); + + // Initialize active sets + typedef int BOOL; +#define TRUE 1 +#define FALSE 0 + Matrix as_lx = Matrix::Constant(n,1,FALSE); + Matrix as_ux = Matrix::Constant(n,1,FALSE); + Matrix as_ieq = Matrix::Constant(Aieq.rows(),1,FALSE); + + // Keep track of previous Z for comparison + DerivedZ old_Z; + old_Z = DerivedZ::Constant( + n,1,numeric_limits::max()); + + int iter = 0; + while(true) + { +#ifdef ACTIVE_SET_CPP_DEBUG + cout<<"Iteration: "< 0) + { + for(int z = 0;z < n;z++) + { + if(Z(z) < lx(z)) + { + new_as_lx += (as_lx(z)?0:1); + //new_as_lx++; + as_lx(z) = TRUE; + } + if(Z(z) > ux(z)) + { + new_as_ux += (as_ux(z)?0:1); + //new_as_ux++; + as_ux(z) = TRUE; + } + } + if(Aieq.rows() > 0) + { + DerivedZ AieqZ; + AieqZ = Aieq*Z; + for(int a = 0;a Bieq(a)) + { + new_as_ieq += (as_ieq(a)?0:1); + as_ieq(a) = TRUE; + } + } + } +#ifdef ACTIVE_SET_CPP_DEBUG + cout<<" new_as_lx: "< Aeq_i,Aieq_i; + slice(Aieq,as_ieq_list,1,Aieq_i); + // Append to equality constraints + cat(1,Aeq,Aieq_i,Aeq_i); + + + min_quad_with_fixed_data data; +#ifndef NDEBUG + { + // NO DUPES! + Matrix fixed = Matrix::Constant(n,1,FALSE); + for(int k = 0;k 0 && Aeq_i.rows() > Aeq.rows()) + { + cerr<<" *Are you sure rows of [Aeq;Aieq] are linearly independent?*"<< + endl; + } + ret = SOLVER_STATUS_ERROR; + break; + } +#ifdef ACTIVE_SET_CPP_DEBUG + cout<<" min_quad_with_fixed_solve"< Ak; + // Slow + slice(A,known_i,1,Ak); + DerivedB Bk; + slice(B,known_i,Bk); + MatrixXd Lambda_known_i = -(0.5*Ak*Z + 0.5*Bk); + // reverse the lambda values for lx + Lambda_known_i.block(nk,0,as_lx_count,1) = + (-1*Lambda_known_i.block(nk,0,as_lx_count,1)).eval(); + + // Extract Lagrange multipliers for Aieq_i (always at back of sol) + VectorXd Lambda_Aieq_i(Aieq_i.rows(),1); + for(int l = 0;l0 && iter>=params.max_iter) + { + ret = SOLVER_STATUS_MAX_ITER; + break; + } + + } + + return ret; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template igl::SolverStatus igl::active_set, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::active_set_params const&, Eigen::PlainObjectBase >&); +template igl::SolverStatus igl::active_set, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::active_set_params const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/active_set.h b/src/igl/active_set.h new file mode 100644 index 000000000..b82d0e52b --- /dev/null +++ b/src/igl/active_set.h @@ -0,0 +1,111 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ACTIVE_SET_H +#define IGL_ACTIVE_SET_H + +#include "igl_inline.h" +#include "SolverStatus.h" +#include +#include + +namespace igl +{ + struct active_set_params; + // Known Bugs: rows of [Aeq;Aieq] **must** be linearly independent. Should be + // using QR decomposition otherwise: + // http://www.okstate.edu/sas/v8/sashtml/ormp/chap5/sect32.htm + // + // ACTIVE_SET Minimize quadratic energy + // + // 0.5*Z'*A*Z + Z'*B + C with constraints + // + // that Z(known) = Y, optionally also subject to the constraints Aeq*Z = Beq, + // and further optionally subject to the linear inequality constraints that + // Aieq*Z <= Bieq and constant inequality constraints lx <= x <= ux + // + // Inputs: + // A n by n matrix of quadratic coefficients + // B n by 1 column of linear coefficients + // known list of indices to known rows in Z + // Y list of fixed values corresponding to known rows in Z + // Aeq meq by n list of linear equality constraint coefficients + // Beq meq by 1 list of linear equality constraint constant values + // Aieq mieq by n list of linear inequality constraint coefficients + // Bieq mieq by 1 list of linear inequality constraint constant values + // lx n by 1 list of lower bounds [] implies -Inf + // ux n by 1 list of upper bounds [] implies Inf + // params struct of additional parameters (see below) + // Z if not empty, is taken to be an n by 1 list of initial guess values + // (see output) + // Outputs: + // Z n by 1 list of solution values + // Returns true on success, false on error + // + // Benchmark: For a harmonic solve on a mesh with 325K facets, matlab 2.2 + // secs, igl/min_quad_with_fixed.h 7.1 secs + // + template < + typename AT, + typename DerivedB, + typename Derivedknown, + typename DerivedY, + typename AeqT, + typename DerivedBeq, + typename AieqT, + typename DerivedBieq, + typename Derivedlx, + typename Derivedux, + typename DerivedZ + > + IGL_INLINE igl::SolverStatus active_set( + const Eigen::SparseMatrix& A, + const Eigen::PlainObjectBase & B, + const Eigen::PlainObjectBase & known, + const Eigen::PlainObjectBase & Y, + const Eigen::SparseMatrix& Aeq, + const Eigen::PlainObjectBase & Beq, + const Eigen::SparseMatrix& Aieq, + const Eigen::PlainObjectBase & Bieq, + const Eigen::PlainObjectBase & lx, + const Eigen::PlainObjectBase & ux, + const igl::active_set_params & params, + Eigen::PlainObjectBase & Z + ); +}; + +#include "EPS.h" +struct igl::active_set_params +{ + // Input parameters for active_set: + // Auu_pd whether Auu is positive definite {false} + // max_iter Maximum number of iterations (0 = Infinity, {100}) + // inactive_threshold Threshold on Lagrange multiplier values to determine + // whether to keep constraints active {EPS} + // constraint_threshold Threshold on whether constraints are violated (0 + // is perfect) {EPS} + // solution_diff_threshold Threshold on the squared norm of the difference + // between two consecutive solutions {EPS} + bool Auu_pd; + int max_iter; + double inactive_threshold; + double constraint_threshold; + double solution_diff_threshold; + active_set_params(): + Auu_pd(false), + max_iter(100), + inactive_threshold(igl::DOUBLE_EPS), + constraint_threshold(igl::DOUBLE_EPS), + solution_diff_threshold(igl::DOUBLE_EPS) + {}; +}; + +#ifndef IGL_STATIC_LIBRARY +# include "active_set.cpp" +#endif + +#endif diff --git a/src/igl/adjacency_list.cpp b/src/igl/adjacency_list.cpp new file mode 100644 index 000000000..1fb61ebf1 --- /dev/null +++ b/src/igl/adjacency_list.cpp @@ -0,0 +1,168 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "adjacency_list.h" + +#include "verbose.h" +#include + +template +IGL_INLINE void igl::adjacency_list( + const Eigen::PlainObjectBase & F, + std::vector >& A, + bool sorted) +{ + A.clear(); + A.resize(F.maxCoeff()+1); + + // Loop over faces + for(int i = 0;i d + int s = F(i,j); + int d = F(i,(j+1)%F.cols()); + A.at(s).push_back(d); + A.at(d).push_back(s); + } + } + + // Remove duplicates + for(int i=0; i<(int)A.size();++i) + { + std::sort(A[i].begin(), A[i].end()); + A[i].erase(std::unique(A[i].begin(), A[i].end()), A[i].end()); + } + + // If needed, sort every VV + if (sorted) + { + // Loop over faces + + // for every vertex v store a set of ordered edges not incident to v that belongs to triangle incident on v. + std::vector > > SR; + SR.resize(A.size()); + + for(int i = 0;i d + int s = F(i,j); + int d = F(i,(j+1)%F.cols()); + // Get index of opposing vertex v + int v = F(i,(j+2)%F.cols()); + + std::vector e(2); + e[0] = d; + e[1] = v; + SR[s].push_back(e); + } + } + + for(int v=0; v<(int)SR.size();++v) + { + std::vector& vv = A.at(v); + std::vector >& sr = SR[v]; + + std::vector > pn = sr; + + // Compute previous/next for every element in sr + for(int i=0;i<(int)sr.size();++i) + { + int a = sr[i][0]; + int b = sr[i][1]; + + // search for previous + int p = -1; + for(int j=0;j<(int)sr.size();++j) + if(sr[j][1] == a) + p = j; + pn[i][0] = p; + + // search for next + int n = -1; + for(int j=0;j<(int)sr.size();++j) + if(sr[j][0] == b) + n = j; + pn[i][1] = n; + + } + + // assume manifoldness (look for beginning of a single chain) + int c = 0; + for(int j=0; j<=(int)sr.size();++j) + if (pn[c][0] != -1) + c = pn[c][0]; + + if (pn[c][0] == -1) // border case + { + // finally produce the new vv relation + for(int j=0; j<(int)sr.size();++j) + { + vv[j] = sr[c][0]; + if (pn[c][1] != -1) + c = pn[c][1]; + } + vv.back() = sr[c][1]; + } + else + { + // finally produce the new vv relation + for(int j=0; j<(int)sr.size();++j) + { + vv[j] = sr[c][0]; + + c = pn[c][1]; + } + } + } + } +} + +template +IGL_INLINE void igl::adjacency_list( + const std::vector > & F, + std::vector >& A) +{ + A.clear(); + A.resize(F.maxCoeff()+1); + + // Loop over faces + for(int i = 0;i d + int s = F(i,j); + int d = F(i,(j+1)%F[i].size()); + A.at(s).push_back(d); + A.at(d).push_back(s); + } + } + + // Remove duplicates + for(int i=0; i<(int)A.size();++i) + { + std::sort(A[i].begin(), A[i].end()); + A[i].erase(std::unique(A[i].begin(), A[i].end()), A[i].end()); + } + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::adjacency_list, int>(Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > >&, bool); +// generated by autoexplicit.sh +template void igl::adjacency_list, int>(Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > >&, bool); +template void igl::adjacency_list, int>(Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > >&, bool); +#endif diff --git a/src/igl/adjacency_list.h b/src/igl/adjacency_list.h new file mode 100644 index 000000000..7438cde42 --- /dev/null +++ b/src/igl/adjacency_list.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ADJACENCY_LIST_H +#define IGL_ADJACENCY_LIST_H +#include "igl_inline.h" + +#include +#include +#include +namespace igl +{ + // Constructs the graph adjacency list of a given mesh (V,F) + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // F #F by dim list of mesh faces (must be triangles) + // sorted flag that indicates if the list should be sorted counter-clockwise + // Outputs: + // A vector > containing at row i the adjacent vertices of vertex i + // + // Example: + // // Mesh in (V,F) + // vector > A; + // adjacency_list(F,A); + // + // See also: edges, cotmatrix, diag + template + IGL_INLINE void adjacency_list( + const Eigen::PlainObjectBase & F, + std::vector >& A, + bool sorted = false); + + // Variant that accepts polygonal faces. + // Each element of F is a set of indices of a polygonal face. + template + IGL_INLINE void adjacency_list( + const std::vector > & F, + std::vector >& A); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "adjacency_list.cpp" +#endif + +#endif diff --git a/src/igl/adjacency_matrix.cpp b/src/igl/adjacency_matrix.cpp new file mode 100644 index 000000000..f6db955c2 --- /dev/null +++ b/src/igl/adjacency_matrix.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "adjacency_matrix.h" + +#include "verbose.h" + +#include + +template +IGL_INLINE void igl::adjacency_matrix( + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& A) +{ + using namespace std; + using namespace Eigen; + typedef typename DerivedF::Scalar Index; + + typedef Triplet IJV; + vector ijv; + ijv.reserve(F.size()*2); + // Loop over **simplex** (i.e., **not quad**) + for(int i = 0;i d + Index s = F(i,j); + Index d = F(i,k); + ijv.push_back(IJV(s,d,1)); + ijv.push_back(IJV(d,s,1)); + } + } + + const Index n = F.maxCoeff()+1; + A.resize(n,n); + switch(F.cols()) + { + case 3: + A.reserve(6*(F.maxCoeff()+1)); + break; + case 4: + A.reserve(26*(F.maxCoeff()+1)); + break; + } + A.setFromTriplets(ijv.begin(),ijv.end()); + + // Force all non-zeros to be one + + // Iterate over outside + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + assert(it.value() != 0); + A.coeffRef(it.row(),it.col()) = 1; + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::adjacency_matrix, bool>(Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::adjacency_matrix, double>(Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::adjacency_matrix, int>(Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/adjacency_matrix.h b/src/igl/adjacency_matrix.h new file mode 100644 index 000000000..b117bccf7 --- /dev/null +++ b/src/igl/adjacency_matrix.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ADJACENCY_MATRIX_H +#define IGL_ADJACENCY_MATRIX_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + // Constructs the graph adjacency matrix of a given mesh (V,F) + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // F #F by dim list of mesh simplices + // Outputs: + // A max(F) by max(F) cotangent matrix, each row i corresponding to V(i,:) + // + // Example: + // // Mesh in (V,F) + // Eigen::SparseMatrix A; + // adjacency_matrix(F,A); + // // sum each row + // SparseVector Asum; + // sum(A,1,Asum); + // // Convert row sums into diagonal of sparse matrix + // SparseMatrix Adiag; + // diag(Asum,Adiag); + // // Build uniform laplacian + // SparseMatrix U; + // U = A-Adiag; + // + // See also: edges, cotmatrix, diag + template + IGL_INLINE void adjacency_matrix( + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& A); +} + +#ifndef IGL_STATIC_LIBRARY +# include "adjacency_matrix.cpp" +#endif + +#endif diff --git a/src/igl/all.cpp b/src/igl/all.cpp new file mode 100644 index 000000000..35245eacb --- /dev/null +++ b/src/igl/all.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "all.h" +#include "redux.h" + + +template +IGL_INLINE void igl::all( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B) +{ + typedef typename DerivedB::Scalar Scalar; + igl::redux(A,dim,[](Scalar a, Scalar b){ return a && b!=0;},B); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif + + diff --git a/src/igl/all.h b/src/igl/all.h new file mode 100644 index 000000000..6e84fdd52 --- /dev/null +++ b/src/igl/all.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ALL_H +#define IGL_ALL_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // For Dense matrices use: A.rowwise().all() or A.colwise().all() + // + // Inputs: + // A m by n sparse matrix + // dim dimension along which to check for all (1 or 2) + // Output: + // B n-long vector (if dim == 1) + // or + // B m-long vector (if dim == 2) + // + template + IGL_INLINE void all( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B); +} +#ifndef IGL_STATIC_LIBRARY +# include "all.cpp" +#endif +#endif + + diff --git a/src/igl/all_edges.cpp b/src/igl/all_edges.cpp new file mode 100644 index 000000000..eabad3038 --- /dev/null +++ b/src/igl/all_edges.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "all_edges.h" +#include "oriented_facets.h" + +template +IGL_INLINE void igl::all_edges( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E) +{ + return oriented_facets(F,E); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::all_edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::all_edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::all_edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::all_edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::all_edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/all_edges.h b/src/igl/all_edges.h new file mode 100644 index 000000000..d5fe13a9a --- /dev/null +++ b/src/igl/all_edges.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ALL_EDGES_H +#define IGL_ALL_EDGES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Deprecated: call oriented_facets instead. + // + // ALL_EDGES Determines all "directed edges" of a given set of simplices. For + // a manifold mesh, this computes all of the half-edges + // + // Inputs: + // F #F by simplex_size list of "faces" + // Outputs: + // E #E by simplex_size-1 list of edges + // + // Note: this is not the same as igl::edges because this includes every + // directed edge including repeats (meaning interior edges on a surface will + // show up once for each direction and non-manifold edges may appear more than + // once for each direction). + template + IGL_INLINE void all_edges( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E); +} + +#ifndef IGL_STATIC_LIBRARY +# include "all_edges.cpp" +#endif + +#endif diff --git a/src/igl/all_pairs_distances.cpp b/src/igl/all_pairs_distances.cpp new file mode 100644 index 000000000..608cf9b85 --- /dev/null +++ b/src/igl/all_pairs_distances.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "all_pairs_distances.h" +#include + +template +IGL_INLINE void igl::all_pairs_distances( + const Mat & V, + const Mat & U, + const bool squared, + Mat & D) +{ + // dimension should be the same + assert(V.cols() == U.cols()); + // resize output + D.resize(V.rows(),U.rows()); + for(int i = 0;i >(Eigen::Matrix const&, Eigen::Matrix const&, bool, Eigen::Matrix&); +#endif diff --git a/src/igl/all_pairs_distances.h b/src/igl/all_pairs_distances.h new file mode 100644 index 000000000..9acc1b730 --- /dev/null +++ b/src/igl/all_pairs_distances.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ALL_PAIRS_DISTANCES_H +#define IGL_ALL_PAIRS_DISTANCES_H +#include "igl_inline.h" + +namespace igl +{ + // ALL_PAIRS_DISTANCES compute distances between each point i in V and point j + // in U + // + // D = all_pairs_distances(V,U) + // + // Templates: + // Mat matrix class like MatrixXd + // Inputs: + // V #V by dim list of points + // U #U by dim list of points + // squared whether to return squared distances + // Outputs: + // D #V by #U matrix of distances, where D(i,j) gives the distance or + // squareed distance between V(i,:) and U(j,:) + // + template + IGL_INLINE void all_pairs_distances( + const Mat & V, + const Mat & U, + const bool squared, + Mat & D); +} + +#ifndef IGL_STATIC_LIBRARY +# include "all_pairs_distances.cpp" +#endif + +#endif diff --git a/src/igl/ambient_occlusion.cpp b/src/igl/ambient_occlusion.cpp new file mode 100644 index 000000000..343f8ae8a --- /dev/null +++ b/src/igl/ambient_occlusion.cpp @@ -0,0 +1,137 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ambient_occlusion.h" +#include "random_dir.h" +#include "ray_mesh_intersect.h" +#include "EPS.h" +#include "Hit.h" +#include "parallel_for.h" +#include +#include +#include + +template < + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::ambient_occlusion( + const std::function< + bool( + const Eigen::Vector3f&, + const Eigen::Vector3f&) + > & shoot_ray, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + using namespace Eigen; + const int n = P.rows(); + // Resize output + S.resize(n,1); + // Embree seems to be parallel when constructing but not when tracing rays + const MatrixXf D = random_dir_stratified(num_samples).cast(); + + const auto & inner = [&P,&N,&num_samples,&D,&S,&shoot_ray](const int p) + { + const Vector3f origin = P.row(p).template cast(); + const Vector3f normal = N.row(p).template cast(); + int num_hits = 0; + for(int s = 0;s +IGL_INLINE void igl::ambient_occlusion( + const igl::AABB & aabb, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + const auto & shoot_ray = [&aabb,&V,&F]( + const Eigen::Vector3f& _s, + const Eigen::Vector3f& dir)->bool + { + Eigen::Vector3f s = _s+1e-4*dir; + igl::Hit hit; + return aabb.intersect_ray( + V, + F, + s .cast().eval(), + dir.cast().eval(), + hit); + }; + return ambient_occlusion(shoot_ray,P,N,num_samples,S); + +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::ambient_occlusion( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + if(F.rows() < 100) + { + // Super naive + const auto & shoot_ray = [&V,&F]( + const Eigen::Vector3f& _s, + const Eigen::Vector3f& dir)->bool + { + Eigen::Vector3f s = _s+1e-4*dir; + igl::Hit hit; + return ray_mesh_intersect(s,dir,V,F,hit); + }; + return ambient_occlusion(shoot_ray,P,N,num_samples,S); + } + AABB aabb; + aabb.init(V,F); + return ambient_occlusion(aabb,V,F,P,N,num_samples,S); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ambient_occlusion.h b/src/igl/ambient_occlusion.h new file mode 100644 index 000000000..1c173121e --- /dev/null +++ b/src/igl/ambient_occlusion.h @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AMBIENT_OCCLUSION_H +#define IGL_AMBIENT_OCCLUSION_H +#include "igl_inline.h" +#include "AABB.h" +#include +#include +namespace igl +{ + // Compute ambient occlusion per given point + // + // Inputs: + // shoot_ray function handle that outputs hits of a given ray against a + // mesh (embedded in function handles as captured variable/data) + // P #P by 3 list of origin points + // N #P by 3 list of origin normals + // Outputs: + // S #P list of ambient occlusion values between 1 (fully occluded) and + // 0 (not occluded) + // + template < + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void ambient_occlusion( + const std::function< + bool( + const Eigen::Vector3f&, + const Eigen::Vector3f&) + > & shoot_ray, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Inputs: + // AABB axis-aligned bounding box hierarchy around (V,F) + template < + typename DerivedV, + int DIM, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void ambient_occlusion( + const igl::AABB & aabb, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh face indices into V + template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void ambient_occlusion( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + +}; +#ifndef IGL_STATIC_LIBRARY +# include "ambient_occlusion.cpp" +#endif + +#endif diff --git a/src/igl/angular_distance.cpp b/src/igl/angular_distance.cpp new file mode 100644 index 000000000..803c290c9 --- /dev/null +++ b/src/igl/angular_distance.cpp @@ -0,0 +1,20 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "angular_distance.h" +#include +#include +IGL_INLINE double igl::angular_distance( + const Eigen::Quaterniond & A, + const Eigen::Quaterniond & B) +{ + assert(fabs(A.norm()-1) +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ANGULAR_DISTANCE_H +#define IGL_ANGULAR_DISTANCE_H +#include "igl_inline.h" +#include +namespace igl +{ + // The "angular distance" between two unit quaternions is the angle of the + // smallest rotation (treated as an Axis and Angle) that takes A to B. + // + // Inputs: + // A unit quaternion + // B unit quaternion + // Returns angular distance + IGL_INLINE double angular_distance( + const Eigen::Quaterniond & A, + const Eigen::Quaterniond & B); +} + +#ifndef IGL_STATIC_LIBRARY +#include "angular_distance.cpp" +#endif + +#endif diff --git a/src/igl/anttweakbar/ReAntTweakBar.cpp b/src/igl/anttweakbar/ReAntTweakBar.cpp new file mode 100644 index 000000000..3664c6bce --- /dev/null +++ b/src/igl/anttweakbar/ReAntTweakBar.cpp @@ -0,0 +1,934 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ReAntTweakBar.h" + +#include +#include +#include +#include +#include +#include + +// GLOBAL WRAPPERS +namespace +{ + std::map< + TwType,std::pair > + > ReTw_custom_types; +} + +IGL_INLINE TwType igl::anttweakbar::ReTwDefineEnum( + const char *name, + const TwEnumVal *enumValues, + unsigned int nbValues) +{ + using namespace std; + // copy enum valus into vector + std::vector enum_vals; + enum_vals.resize(nbValues); + for(unsigned int j = 0; j >(name,enum_vals); + + return type; +} + +IGL_INLINE TwType igl::anttweakbar::ReTwDefineEnumFromString( + const char * _Name, + const char * _EnumString) +{ + // Taken directly from TwMgr.cpp, just replace TwDefineEnum with + // ReTwDefineEnum + using namespace std; + { + if (_EnumString == NULL) + return ReTwDefineEnum(_Name, NULL, 0); + + // split enumString + stringstream EnumStream(_EnumString); + string Label; + vector Labels; + while( getline(EnumStream, Label, ',') ) { + // trim Label + size_t Start = Label.find_first_not_of(" \n\r\t"); + size_t End = Label.find_last_not_of(" \n\r\t"); + if( Start==string::npos || End==string::npos ) + Label = ""; + else + Label = Label.substr(Start, (End-Start)+1); + // store Label + Labels.push_back(Label); + } + // create TwEnumVal array + vector Vals(Labels.size()); + for( int i=0; i<(int)Labels.size(); i++ ) + { + Vals[i].Value = i; + // Wrong: + //Vals[i].Label = Labels[i].c_str(); + // Allocate char on heap + // http://stackoverflow.com/a/10050258/148668 + char * c_label = new char[Labels[i].length()+1]; + std::strcpy(c_label, Labels[i].c_str()); + Vals[i].Label = c_label; + } + + const TwType type = + ReTwDefineEnum(_Name, Vals.empty() ? + NULL : + &(Vals[0]), (unsigned int)Vals.size()); + return type; + } +} + +namespace +{ + struct ReTwTypeString + { + TwType type; + const char * type_str; + }; + + #define RETW_NUM_DEFAULT_TYPE_STRINGS 23 + ReTwTypeString ReTwDefaultTypeStrings[RETW_NUM_DEFAULT_TYPE_STRINGS] = + { + {TW_TYPE_UNDEF,"TW_TYPE_UNDEF"}, + {TW_TYPE_BOOLCPP,"TW_TYPE_BOOLCPP"}, + {TW_TYPE_BOOL8,"TW_TYPE_BOOL8"}, + {TW_TYPE_BOOL16,"TW_TYPE_BOOL16"}, + {TW_TYPE_BOOL32,"TW_TYPE_BOOL32"}, + {TW_TYPE_CHAR,"TW_TYPE_CHAR"}, + {TW_TYPE_INT8,"TW_TYPE_INT8"}, + {TW_TYPE_UINT8,"TW_TYPE_UINT8"}, + {TW_TYPE_INT16,"TW_TYPE_INT16"}, + {TW_TYPE_UINT16,"TW_TYPE_UINT16"}, + {TW_TYPE_INT32,"TW_TYPE_INT32"}, + {TW_TYPE_UINT32,"TW_TYPE_UINT32"}, + {TW_TYPE_FLOAT,"TW_TYPE_FLOAT"}, + {TW_TYPE_DOUBLE,"TW_TYPE_DOUBLE"}, + {TW_TYPE_COLOR32,"TW_TYPE_COLOR32"}, + {TW_TYPE_COLOR3F,"TW_TYPE_COLOR3F"}, + {TW_TYPE_COLOR4F,"TW_TYPE_COLOR4F"}, + {TW_TYPE_CDSTRING,"TW_TYPE_CDSTRING"}, + {TW_TYPE_STDSTRING,"TW_TYPE_STDSTRING"}, + {TW_TYPE_QUAT4F,"TW_TYPE_QUAT4F"}, + {TW_TYPE_QUAT4D,"TW_TYPE_QUAT4D"}, + {TW_TYPE_DIR3F,"TW_TYPE_DIR3F"}, + {TW_TYPE_DIR3D,"TW_TYPE_DIR3D"} + }; +} + +IGL_INLINE igl::anttweakbar::ReTwBar::ReTwBar(): + bar(NULL), + name(), + rw_items(),cb_items() +{ +} + +IGL_INLINE igl::anttweakbar::ReTwBar::ReTwBar( + const igl::anttweakbar::ReTwBar & that): + bar(that.bar), + name(that.name), + rw_items(that.rw_items), + cb_items(that.cb_items) +{ +} + +IGL_INLINE igl::anttweakbar::ReTwBar & +igl::anttweakbar::ReTwBar::operator=(const igl::anttweakbar::ReTwBar & that) +{ + // check for self assignment + if(this != &that) + { + bar = that.bar; + rw_items = that.rw_items; + cb_items = that.cb_items; + } + return *this; +} + + +// BAR WRAPPERS +IGL_INLINE void igl::anttweakbar::ReTwBar::TwNewBar(const char * _name) +{ + this->bar = ::TwNewBar(_name); + // Alec: This causes trouble (not sure why) in multiple applications + // (medit, puppet) Probably there is some sort of memory corrpution. + // this->name = _name; + // Suspiciously this also fails: + //this->name = "foobar"; +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwAddVarRW( + const char *name, + TwType type, + void *var, + const char *def, + const bool record) +{ + int ret = ::TwAddVarRW(this->bar,name,type,var,def); + if(ret && record) + { + rw_items.push_back(ReTwRWItem(name,type,var)); + } + return ret; +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwAddVarCB( + const char *name, + TwType type, + TwSetVarCallback setCallback, + TwGetVarCallback getCallback, + void *clientData, + const char *def, + const bool record) +{ + int ret = + ::TwAddVarCB(this->bar,name,type,setCallback,getCallback,clientData,def); + if(ret && record) + { + cb_items.push_back(ReTwCBItem(name,type,setCallback,getCallback,clientData)); + } + return ret; +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwAddVarRO( + const char *name, + TwType type, + void *var, + const char *def) +{ + int ret = ::TwAddVarRO(this->bar,name,type,var,def); + // Read only variables are not recorded + //if(ret) + //{ + // rw_items.push_back(ReTwRWItem(name,type,var)); + //} + return ret; +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwAddButton( + const char *name, + TwButtonCallback buttonCallback, + void *clientData, + const char *def) +{ + int ret = + ::TwAddButton(this->bar,name,buttonCallback,clientData,def); + // buttons are not recorded + //if(ret) + //{ + // cb_items.push_back(ReTwCBItem(name,type,setCallback,getCallback,clientData)); + //} + return ret; +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwSetParam( + const char *varName, + const char *paramName, + TwParamValueType paramValueType, + unsigned int inValueCount, + const void *inValues) +{ + // For now just pass these along + return + ::TwSetParam( + this->bar, + varName, + paramName, + paramValueType, + inValueCount, + inValues); +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwGetParam( + const char *varName, + const char *paramName, + TwParamValueType paramValueType, + unsigned int outValueMaxCount, + void *outValues) +{ + return + ::TwGetParam( + this->bar, + varName, + paramName, + paramValueType, + outValueMaxCount, + outValues); +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwRefreshBar() +{ + return ::TwRefreshBar(this->bar); +} + +IGL_INLINE int igl::anttweakbar::ReTwBar::TwTerminate() +{ + //std::cout<<"TwTerminate"<::iterator it = rw_items.begin(); + it != rw_items.end(); + it++) + { + std::string s = (*it).name; + const char * name = s.c_str(); + TwType type = (*it).type; + void * var = (*it).var; + fprintf(fp,"%s: %s\n", + name, + get_value_as_string(var,type).c_str()); + } + + char var[REANTTWEAKBAR_MAX_CB_VAR_SIZE]; + // Print all CB variables + for( + std::vector::iterator it = cb_items.begin(); + it != cb_items.end(); + it++) + { + const char * name = it->name.c_str(); + TwType type = it->type; + //TwSetVarCallback setCallback = it->setCallback; + TwGetVarCallback getCallback = it->getCallback; + void * clientData = it->clientData; + // I'm not sure how to do what I want to do. getCallback needs to be sure + // that it can write to var. So var needs to point to a valid and big + // enough chunk of memory + getCallback(var,clientData); + fprintf(fp,"%s: %s\n", + name, + get_value_as_string(var,type).c_str()); + } + + fprintf(fp,"\n"); + + if(file_name != NULL) + { + fclose(fp); + } + // everything succeeded + return true; +} + +IGL_INLINE std::string igl::anttweakbar::ReTwBar::get_value_as_string( + void * var, + TwType type) +{ + std::stringstream sstr; + switch(type) + { + case TW_TYPE_BOOLCPP: + { + sstr << "TW_TYPE_BOOLCPP" << " "; + sstr << *(static_cast(var)); + break; + } + case TW_TYPE_QUAT4D: + { + sstr << "TW_TYPE_QUAT4D" << " "; + // Q: Why does casting to double* work? shouldn't I have to cast to + // double**? + double * q = static_cast(var); + sstr << std::setprecision(15) << q[0] << " " << q[1] << " " << q[2] << " " << q[3]; + break; + } + case TW_TYPE_QUAT4F: + { + sstr << "TW_TYPE_QUAT4F" << " "; + // Q: Why does casting to float* work? shouldn't I have to cast to + // float**? + float * q = static_cast(var); + sstr << q[0] << " " << q[1] << " " << q[2] << " " << q[3]; + break; + } + case TW_TYPE_COLOR4F: + { + sstr << "TW_TYPE_COLOR4F" << " "; + float * c = static_cast(var); + sstr << c[0] << " " << c[1] << " " << c[2] << " " << c[3]; + break; + } + case TW_TYPE_COLOR3F: + { + sstr << "TW_TYPE_COLOR3F" << " "; + float * c = static_cast(var); + sstr << c[0] << " " << c[1] << " " << c[2]; + break; + } + case TW_TYPE_DIR3D: + { + sstr << "TW_TYPE_DIR3D" << " "; + double * d = static_cast(var); + sstr << std::setprecision(15) << d[0] << " " << d[1] << " " << d[2]; + break; + } + case TW_TYPE_DIR3F: + { + sstr << "TW_TYPE_DIR3F" << " "; + float * d = static_cast(var); + sstr << d[0] << " " << d[1] << " " << d[2]; + break; + } + case TW_TYPE_BOOL32: + { + sstr << "TW_TYPE_BOOL32" << " "; + sstr << *(static_cast(var)); + break; + } + case TW_TYPE_UINT8: + { + sstr << "TW_TYPE_UINT8" << " "; + // Cast to int so that it's human readable + sstr << (int)*(static_cast(var)); + break; + } + case TW_TYPE_INT32: + { + sstr << "TW_TYPE_INT32" << " "; + sstr << *(static_cast(var)); + break; + } + case TW_TYPE_UINT32: + { + sstr << "TW_TYPE_UINT32" << " "; + sstr << *(static_cast(var)); + break; + } + case TW_TYPE_FLOAT: + { + sstr << "TW_TYPE_FLOAT" << " "; + sstr << *(static_cast(var)); + break; + } + case TW_TYPE_DOUBLE: + { + sstr << "TW_TYPE_DOUBLE" << " "; + sstr << std::setprecision(15) << *(static_cast(var)); + break; + } + case TW_TYPE_STDSTRING: + { + sstr << "TW_TYPE_STDSTRING" << " "; + std::string *destPtr = static_cast(var); + sstr << destPtr->c_str(); + break; + } + default: + { + using namespace std; + std::map > >::const_iterator iter = + ReTw_custom_types.find(type); + if(iter != ReTw_custom_types.end()) + { + sstr << (*iter).second.first << " "; + int enum_val = *(static_cast(var)); + // try find display name for enum value + std::vector::const_iterator eit = (*iter).second.second.begin(); + bool found = false; + for(;eit<(*iter).second.second.end();eit++) + { + if(enum_val == eit->Value) + { + sstr << eit->Label; + found = true; + break; + } + } + if(!found) + { + sstr << "ERROR_ENUM_VALUE_NOT_DEFINED"; + } + }else + { + sstr << "ERROR_TYPE_NOT_SUPPORTED"; + } + break; + } + } + return sstr.str(); +} + +IGL_INLINE bool igl::anttweakbar::ReTwBar::load(const char *file_name) +{ + FILE * fp; + fp = fopen(file_name,"r"); + + if(fp == NULL) + { + printf("ERROR: not able to open %s for reading...\n",file_name); + return false; + } + + // go through file line by line + char line[REANTTWEAKBAR_MAX_LINE]; + bool still_comments; + char name[REANTTWEAKBAR_MAX_WORD]; + char type_str[REANTTWEAKBAR_MAX_WORD]; + char value_str[REANTTWEAKBAR_MAX_WORD]; + + + // line number + int j = 0; + bool finished = false; + while(true) + { + // Eat comments + still_comments = true; + while(still_comments) + { + if(fgets(line,REANTTWEAKBAR_MAX_LINE,fp) == NULL) + { + finished = true; + break; + } + // Blank lines and lines that begin with # are comments + still_comments = (line[0] == '#' || line[0] == '\n'); + j++; + } + if(finished) + { + break; + } + + sscanf(line,"%[^:]: %s %[^\n]",name,type_str,value_str); + //printf("%s: %s %s\n",name, type_str,value_str); + + TwType type; + if(!type_from_string(type_str,type)) + { + printf("ERROR: %s type not found... Skipping...\n",type_str); + continue; + } + set_value_from_string(name,type,value_str); + + } + + fclose(fp); + + // everything succeeded + return true; +} + +IGL_INLINE bool igl::anttweakbar::ReTwBar::type_from_string( + const char *type_str, TwType & type) +{ + // first check default types + for(int j = 0; j < RETW_NUM_DEFAULT_TYPE_STRINGS; j++) + { + if(strcmp(type_str,ReTwDefaultTypeStrings[j].type_str) == 0) + { + type = ReTwDefaultTypeStrings[j].type; + return true; + break; + } + } + + // then check custom types + std::map< + TwType,std::pair > + >::const_iterator iter = + ReTw_custom_types.begin(); + for(;iter != ReTw_custom_types.end(); iter++) + { + if(strcmp((*iter).second.first,type_str)==0) + { + type = (*iter).first; + return true; + } + } + return false; +} + +bool igl::anttweakbar::ReTwBar::set_value_from_string( + const char * name, + TwType type, + const char * value_str) +{ + void * value = NULL; + // possible value slots + int i; + float v; + double dv; + float f[4]; + double d[4]; + bool b; + unsigned int u; + unsigned char uc; + std::string s; + + // First try to get value from default types + switch(type) + { + case TW_TYPE_BOOLCPP: + { + int ib; + if(sscanf(value_str," %d",&ib) == 1) + { + b = ib!=0; + value = &b; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_QUAT4D: + //case TW_TYPE_COLOR4D: + { + if(sscanf(value_str," %lf %lf %lf %lf",&d[0],&d[1],&d[2],&d[3]) == 4) + { + value = &d; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_QUAT4F: + case TW_TYPE_COLOR4F: + { + if(sscanf(value_str," %f %f %f %f",&f[0],&f[1],&f[2],&f[3]) == 4) + { + value = &f; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + //case TW_TYPE_COLOR3D: + case TW_TYPE_DIR3D: + { + if(sscanf(value_str," %lf %lf %lf",&d[0],&d[1],&d[2]) == 3) + { + value = &d; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_COLOR3F: + case TW_TYPE_DIR3F: + { + if(sscanf(value_str," %f %f %f",&f[0],&f[1],&f[2]) == 3) + { + value = &f; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_UINT8: + { + if(sscanf(value_str," %d",&i) == 1) + { + // Cast to unsigned char + uc = (unsigned char) i; + value = &uc; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_BOOL32: + case TW_TYPE_INT32: + { + if(sscanf(value_str," %d",&i) == 1) + { + value = &i; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_UINT32: + { + if(sscanf(value_str," %u",&u) == 1) + { + value = &u; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_FLOAT: + { + if(sscanf(value_str," %f",&v) == 1) + { + value = &v; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_DOUBLE: + { + if(sscanf(value_str," %lf",&dv) == 1) + { + value = &dv; + }else + { + printf("ERROR: Bad value format...\n"); + return false; + } + break; + } + case TW_TYPE_STDSTRING: + { + s = value_str; + value = &s; + break; + } + default: + // Try to find type in custom enum types + std::map > >::const_iterator iter = + ReTw_custom_types.find(type); + if(iter != ReTw_custom_types.end()) + { + std::vector::const_iterator eit = (*iter).second.second.begin(); + bool found = false; + for(;eit<(*iter).second.second.end();eit++) + { + if(strcmp(value_str,eit->Label) == 0) + { + i = eit->Value; + value = &i; + found = true; + break; + } + } + if(!found) + { + printf("ERROR_ENUM_VALUE_NOT_DEFINED"); + } + }else + { + printf("ERROR_TYPE_NOT_SUPPORTED\n"); + } + + break; + } + + + // Find variable based on name + // First look in RW items + bool item_found = false; + for( + std::vector::iterator it = rw_items.begin(); + it != rw_items.end(); + it++) + { + if(it->name == name) + { + void * var = it->var; + switch(type) + { + case TW_TYPE_BOOLCPP: + { + bool * bvar = static_cast(var); + bool * bvalue = static_cast(value); + *bvar = *bvalue; + break; + } + case TW_TYPE_QUAT4D: + //case TW_TYPE_COLOR4D: + { + double * dvar = static_cast(var); + double * dvalue = static_cast(value); + dvar[0] = dvalue[0]; + dvar[1] = dvalue[1]; + dvar[2] = dvalue[2]; + dvar[3] = dvalue[3]; + break; + } + case TW_TYPE_QUAT4F: + case TW_TYPE_COLOR4F: + { + float * fvar = static_cast(var); + float * fvalue = static_cast(value); + fvar[0] = fvalue[0]; + fvar[1] = fvalue[1]; + fvar[2] = fvalue[2]; + fvar[3] = fvalue[3]; + break; + } + //case TW_TYPE_COLOR3D: + case TW_TYPE_DIR3D: + { + double * dvar = static_cast(var); + double * dvalue = static_cast(value); + dvar[0] = dvalue[0]; + dvar[1] = dvalue[1]; + dvar[2] = dvalue[2]; + break; + } + case TW_TYPE_COLOR3F: + case TW_TYPE_DIR3F: + { + float * fvar = static_cast(var); + float * fvalue = static_cast(value); + fvar[0] = fvalue[0]; + fvar[1] = fvalue[1]; + fvar[2] = fvalue[2]; + break; + } + case TW_TYPE_UINT8: + { + unsigned char * ucvar = static_cast(var); + unsigned char * ucvalue = static_cast(value); + *ucvar = *ucvalue; + break; + } + case TW_TYPE_BOOL32: + case TW_TYPE_INT32: + { + int * ivar = static_cast(var); + int * ivalue = static_cast(value); + *ivar = *ivalue; + break; + } + case TW_TYPE_UINT32: + { + unsigned int * uvar = static_cast(var); + unsigned int * uvalue = static_cast(value); + *uvar = *uvalue; + break; + } + case TW_TYPE_FLOAT: + { + float * fvar = static_cast(var); + float * fvalue = static_cast(value); + *fvar = *fvalue; + break; + } + case TW_TYPE_DOUBLE: + { + double * dvar = static_cast(var); + double * fvalue = static_cast(value); + *dvar = *fvalue; + break; + } + case TW_TYPE_STDSTRING: + { + std::string * svar = static_cast(var); + std::string * svalue = static_cast(value); + *svar = *svalue; + break; + } + default: + // Try to find type in custom enum types + std::map > >::iterator iter = + ReTw_custom_types.find(type); + if(iter != ReTw_custom_types.end()) + { + int * ivar = static_cast(var); + std::vector::iterator eit = (*iter).second.second.begin(); + bool found = false; + for(;eit<(*iter).second.second.end();eit++) + { + if(strcmp(value_str,eit->Label) == 0) + { + *ivar = eit->Value; + found = true; + break; + } + } + if(!found) + { + printf("ERROR_ENUM_VALUE_NOT_DEFINED"); + } + }else + { + printf("ERROR_TYPE_NOT_SUPPORTED\n"); + } + break; + } + item_found = true; + break; + } + } + + // Try looking in CB items + if(!item_found) + { + for( + std::vector::iterator it = cb_items.begin(); + it != cb_items.end(); + it++) + { + if(it->name==name) + { + it->setCallback(value,it->clientData); + item_found = true; + break; + } + } + } + + if(!item_found) + { + printf("ERROR: item '%s' not found\n",name); + } + return true; +} + +IGL_INLINE const std::vector & + igl::anttweakbar::ReTwBar::get_rw_items() +{ + return rw_items; +} + +IGL_INLINE const std::vector & + igl::anttweakbar::ReTwBar::get_cb_items() +{ + return cb_items; +} diff --git a/src/igl/anttweakbar/ReAntTweakBar.h b/src/igl/anttweakbar/ReAntTweakBar.h new file mode 100644 index 000000000..d1dede927 --- /dev/null +++ b/src/igl/anttweakbar/ReAntTweakBar.h @@ -0,0 +1,286 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ANTTWEAKBAR_REANTTWEAKBAR_H +#define IGL_ANTTWEAKBAR_REANTTWEAKBAR_H +#include "../igl_inline.h" +// ReAntTweakBar is a minimal wrapper for the AntTweakBar library that allows +// "bars" to be saved and load from disk. Changing your existing app that uses +// AntTweakBar to use ReAntTweakBar is trivial. +// +// Many (but not all) variable types are supported. I'll try to keep track them +// here: +// TW_TYPE_BOOLCPP +// TW_TYPE_QUAT4F +// TW_TYPE_QUAT4D +// TW_TYPE_COLOR4F +// TW_TYPE_COLOR4D +// TW_TYPE_COLOR3F +// TW_TYPE_DIR3F +// TW_TYPE_DIR3D +// TW_TYPE_BOOL32 +// TW_TYPE_INT32 +// TW_TYPE_UINT32 +// TW_TYPE_FLOAT +// TW_TYPE_DOUBLE +// TW_TYPE_UINT8 +// and +// custom TwTypes made with TwDefineEnum +// +// I'm working on adding the rest on an as-needed basis. Adding a new type only +// requires changes in a few places... +// +// +// + +// This allows the user to have a non-global, static installation of +// AntTweakBar +#include +// Instead of including AntTweakBar.h, just define the necessary types +// Types used: +// - TwType +// - TwEnumVal +// - TwSetVarCallback +// - TwGetVarCallback +// - TwBar +// - TwButtonCallback + + +#include +#include + +#define REANTTWEAKBAR_MAX_CB_VAR_SIZE 1000 +// Max line size for reading files +#define REANTTWEAKBAR_MAX_LINE 1000 +#define REANTTWEAKBAR_MAX_WORD 100 + +namespace igl +{ + namespace anttweakbar + { + TwType ReTwDefineEnum( + const char *name, + const TwEnumVal *enumValues, + unsigned int nbValues); + TwType ReTwDefineEnumFromString(const char * name,const char * enumString); + + struct ReTwRWItem + { + //const char * name; + std::string name; + TwType type; + void * var; + // Default constructor + IGL_INLINE ReTwRWItem( + const std::string _name, + TwType _type, + void *_var): + name(_name), + type(_type), + var(_var) + { + } + // Shallow copy constructor + // I solemnly swear it's OK to copy var this way + // Q: Is it really? + IGL_INLINE ReTwRWItem(const ReTwRWItem & that): + name(that.name), + type(that.type), + var(that.var) + { + } + // Shallow assignment + // I solemnly swear it's OK to copy var this way + IGL_INLINE ReTwRWItem & operator=(const ReTwRWItem & that) + { + if(this != &that) + { + this->name = that.name; + this->type = that.type; + this->var = that.var; + } + return *this; + } + }; + + struct ReTwCBItem + { + //const char * name; + std::string name; + TwType type; + TwSetVarCallback setCallback; + TwGetVarCallback getCallback; + void * clientData; + // Default constructor + IGL_INLINE ReTwCBItem( + const std::string _name, + TwType _type, + TwSetVarCallback _setCallback, + TwGetVarCallback _getCallback, + void * _clientData): + name(_name), + type(_type), + setCallback(_setCallback), + getCallback(_getCallback), + clientData(_clientData) + { + } + // Shallow copy + // I solemnly swear it's OK to copy clientData this way + IGL_INLINE ReTwCBItem(const ReTwCBItem & that): + name(that.name), + type(that.type), + setCallback(that.setCallback), + getCallback(that.getCallback), + clientData(that.clientData) + { + } + // Shallow assignment + // I solemnly swear it's OK to copy clientData this way + IGL_INLINE ReTwCBItem & operator=(const ReTwCBItem & that) + { + if(this != &that) + { + name = that.name; + type = that.type; + setCallback = that.setCallback; + getCallback = that.getCallback; + clientData = that.clientData; + } + return *this; + } + + }; + + class ReTwBar + { + // VARIABLES + // Should be private, but seeing as I'm not going to implement all of the + // AntTweakBar public functions right away, I'll expose this so that at + // anytime AntTweakBar functions can be called directly on the bar + public: + TwBar * bar; + std::string name; + protected: + std::vector rw_items; + std::vector cb_items; + public: + // Default constructor with explicit initialization + IGL_INLINE ReTwBar(); + private: + // Copy constructor does shallow copy + IGL_INLINE ReTwBar(const ReTwBar & that); + // Assignment operator does shallow assignment + IGL_INLINE ReTwBar &operator=(const ReTwBar & that); + + // WRAPPERS FOR ANTTWEAKBAR FUNCTIONS + public: + IGL_INLINE void TwNewBar(const char *_name); + IGL_INLINE int TwAddVarRW( + const char *name, + TwType type, + void *var, + const char *def, + const bool record=true); + IGL_INLINE int TwAddVarCB( + const char *name, + TwType type, + TwSetVarCallback setCallback, + TwGetVarCallback getCallback, + void *clientData, + const char *def, + const bool record=true); + // Wrappers for convenience (not recorded, just passed on) + IGL_INLINE int TwAddVarRO(const char *name, TwType type, void *var, const char *def); + IGL_INLINE int TwAddButton( + const char *name, + TwButtonCallback buttonCallback, + void *clientData, + const char *def); + IGL_INLINE int TwSetParam( + const char *varName, + const char *paramName, + TwParamValueType paramValueType, + unsigned int inValueCount, + const void *inValues); + IGL_INLINE int TwGetParam( + const char *varName, + const char *paramName, + TwParamValueType paramValueType, + unsigned int outValueMaxCount, + void *outValues); + IGL_INLINE int TwRefreshBar(); + IGL_INLINE int TwTerminate(); + + + // IO FUNCTIONS + public: + // Save current items to file + // Input: + // file_name name of file to save data to, can be null which means print + // to stdout + // Return: + // true only if there were no (fatal) errors + IGL_INLINE bool save(const char *file_name); + std::string get_value_as_string( + void * var, + TwType type); + // Load into current items from file + // Input: + // file_name name of input file to load + // Return: + // true only if there were no (fatal) errors + IGL_INLINE bool load(const char *file_name); + // Get TwType from string + // Input + // type_str string of type + // Output + // type TwType converted from string + // Returns + // true only if string matched a valid type + IGL_INLINE bool type_from_string(const char *type_str, TwType & type); + // I realize that I mix std::string and const char * all over the place. + // What can you do... + IGL_INLINE bool set_value_from_string( + const char * name, + TwType type, + const char * value_str); + IGL_INLINE const std::vector & get_rw_items(); + IGL_INLINE const std::vector & get_cb_items(); + }; + } +} + +// List of TwBar functions +//TW_API TwBar * TW_CALL TwNewBar(const char *barName); +//TW_API int TW_CALL TwDeleteBar(TwBar *bar); +//TW_API int TW_CALL TwDeleteAllBars(); +//TW_API int TW_CALL TwSetTopBar(const TwBar *bar); +//TW_API TwBar * TW_CALL TwGetTopBar(); +//TW_API int TW_CALL TwSetBottomBar(const TwBar *bar); +//TW_API TwBar * TW_CALL TwGetBottomBar(); +//TW_API const char * TW_CALL TwGetBarName(TwBar *bar); +//TW_API int TW_CALL TwGetBarCount(); +//TW_API TwBar * TW_CALL TwGetBarByIndex(int barIndex); +//TW_API TwBar * TW_CALL TwGetBarByName(const char *barName); +//TW_API int TW_CALL TwRefreshBar(TwBar *bar); +//TW_API int TW_CALL TwTerminate(); +// +//TW_API int TW_CALL TwAddVarRW(TwBar *bar, const char *name, TwType type, void *var, const char *def); +//TW_API int TW_CALL TwAddVarRO(TwBar *bar, const char *name, TwType type, const void *var, const char *def); +//TW_API int TW_CALL TwAddVarCB(TwBar *bar, const char *name, TwType type, TwSetVarCallback setCallback, TwGetVarCallback getCallback, void *clientData, const char *def); +//TW_API int TW_CALL TwAddButton(TwBar *bar, const char *name, TwButtonCallback callback, void *clientData, const char *def); +//TW_API int TW_CALL TwAddSeparator(TwBar *bar, const char *name, const char *def); +//TW_API int TW_CALL TwRemoveVar(TwBar *bar, const char *name); +//TW_API int TW_CALL TwRemoveAllVars(TwBar *bar); + +// Until AntTweakBar dependency folder exists, this is header-only +#ifndef IGL_STATIC_LIBRARY +# include "ReAntTweakBar.cpp" +#endif + +#endif diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp b/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp new file mode 100644 index 000000000..bcd6e2be5 --- /dev/null +++ b/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp @@ -0,0 +1,81 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cocoa_key_to_anttweakbar_key.h" + +#include + +IGL_INLINE int igl::anttweakbar::cocoa_key_to_anttweakbar_key(int key) +{ + // I've left commented the AntTweakBar key codes that correspond to keys I + // don't have on my keyboard. Please fill this in if you have those keys + switch(key) + { + case 127: + return TW_KEY_BACKSPACE; + case 9: + return TW_KEY_TAB; + //TW_KEY_CLEAR = 0x0c, + case 3://ENTER + case 13: + return TW_KEY_RETURN; + case 27: + return TW_KEY_ESCAPE; + case 32: + return TW_KEY_SPACE; + // IN A GLUT APP 40 is ( + //case 40: + case 63272: + return TW_KEY_DELETE; + case 63232: + return TW_KEY_UP; + case 63233: + return TW_KEY_DOWN; + case 63235: + return TW_KEY_RIGHT; + case 63234: + return TW_KEY_LEFT; + //TW_KEY_INSERT, + //TW_KEY_HOME, + //TW_KEY_END, + //TW_KEY_PAGE_UP, + //TW_KEY_PAGE_DOWN, + case 63236: + return TW_KEY_F1; + case 63237: + return TW_KEY_F2; + case 63238: + return TW_KEY_F3; + case 63239: + return TW_KEY_F4; + case 63240: + return TW_KEY_F5; + case 63241: + return TW_KEY_F6; + case 63242: + return TW_KEY_F7; + case 63243: + return TW_KEY_F8; + case 63244: + return TW_KEY_F9; + case 63245: + return TW_KEY_F10; + case 63246: + return TW_KEY_F11; + case 63247: + return TW_KEY_F12; + case 63248: + return TW_KEY_F13; + case 63249: + return TW_KEY_F14; + case 63250: + return TW_KEY_F15; + default: + break; + } + return key; +} diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h b/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h new file mode 100644 index 000000000..fdf3d3bea --- /dev/null +++ b/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ANTTWEAKBAR_COCOA_KEY_TO_ANTTWEAKBAR_KEY_H +#define IGL_ANTTWEAKBAR_COCOA_KEY_TO_ANTTWEAKBAR_KEY_H +#include "../igl_inline.h" + + +namespace igl +{ + namespace anttweakbar + { + // Convert an unsigned char (like that from Cocoa apps) to AntTweakBar key + // code. + // See also: TranslateKey() in TwMgr.cpp in AntTweakBar source + // Inputs: + // key unsigned char key from keyboard + // Returns int of new key code + IGL_INLINE int cocoa_key_to_anttweakbar_key(int key); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "cocoa_key_to_anttweakbar_key.cpp" +#endif + +#endif diff --git a/src/igl/any.cpp b/src/igl/any.cpp new file mode 100644 index 000000000..0488c8ffb --- /dev/null +++ b/src/igl/any.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "any.h" +#include "redux.h" + + +template +IGL_INLINE void igl::any( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B) +{ + typedef typename DerivedB::Scalar Scalar; + igl::redux(A,dim,[](Scalar a, Scalar b){ return a || b!=0;},B); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::any >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/any.h b/src/igl/any.h new file mode 100644 index 000000000..08a80e4cb --- /dev/null +++ b/src/igl/any.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ANY_H +#define IGL_ANY_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // For Dense matrices use: A.rowwise().any() or A.colwise().any() + // + // Inputs: + // A m by n sparse matrix + // dim dimension along which to check for any (1 or 2) + // Output: + // B n-long vector (if dim == 1) + // or + // B m-long vector (if dim == 2) + // + template + IGL_INLINE void any( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B); +} +#ifndef IGL_STATIC_LIBRARY +# include "any.cpp" +#endif +#endif + diff --git a/src/igl/any_of.cpp b/src/igl/any_of.cpp new file mode 100644 index 000000000..9defd0339 --- /dev/null +++ b/src/igl/any_of.cpp @@ -0,0 +1,20 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "any_of.h" +#include +template +IGL_INLINE bool igl::any_of(const Mat & S) +{ + return std::any_of(S.data(),S.data()+S.size(),[](bool s){return s;}); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::any_of >(Eigen::Matrix const&); +#endif + diff --git a/src/igl/any_of.h b/src/igl/any_of.h new file mode 100644 index 000000000..95ec1d373 --- /dev/null +++ b/src/igl/any_of.h @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ANY_OF_H +#define IGL_ANY_OF_H +#include "igl_inline.h" +namespace igl +{ + // Wrapper for STL `any_of` for matrix types + // + // Inputs: + // S matrix + // Returns whether any entries are true + // + // Seems that Eigen (now) implements this for `Eigen::Array` + template + IGL_INLINE bool any_of(const Mat & S); +} +#ifndef IGL_STATIC_LIBRARY +# include "any_of.cpp" +#endif +#endif diff --git a/src/igl/arap.cpp b/src/igl/arap.cpp new file mode 100644 index 000000000..dc5af4cf6 --- /dev/null +++ b/src/igl/arap.cpp @@ -0,0 +1,312 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "arap.h" +#include "colon.h" +#include "cotmatrix.h" +#include "massmatrix.h" +#include "group_sum_matrix.h" +#include "covariance_scatter_matrix.h" +#include "speye.h" +#include "mode.h" +#include "project_isometrically_to_plane.h" +#include "slice.h" +#include "arap_rhs.h" +#include "repdiag.h" +#include "columnize.h" +#include "fit_rotations.h" +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename Derivedb> +IGL_INLINE bool igl::arap_precomputation( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const int dim, + const Eigen::PlainObjectBase & b, + ARAPData & data) +{ + using namespace std; + using namespace Eigen; + typedef typename DerivedV::Scalar Scalar; + // number of vertices + const int n = V.rows(); + data.n = n; + assert((b.size() == 0 || b.maxCoeff() < n) && "b out of bounds"); + assert((b.size() == 0 || b.minCoeff() >=0) && "b out of bounds"); + // remember b + data.b = b; + //assert(F.cols() == 3 && "For now only triangles"); + // dimension + //const int dim = V.cols(); + assert((dim == 3 || dim ==2) && "dim should be 2 or 3"); + data.dim = dim; + //assert(dim == 3 && "Only 3d supported"); + // Defaults + data.f_ext = MatrixXd::Zero(n,data.dim); + + assert(data.dim <= V.cols() && "solve dim should be <= embedding"); + bool flat = (V.cols() - data.dim)==1; + + DerivedV plane_V; + DerivedF plane_F; + typedef SparseMatrix SparseMatrixS; + SparseMatrixS ref_map,ref_map_dim; + if(flat) + { + project_isometrically_to_plane(V,F,plane_V,plane_F,ref_map); + repdiag(ref_map,dim,ref_map_dim); + } + const PlainObjectBase& ref_V = (flat?plane_V:V); + const PlainObjectBase& ref_F = (flat?plane_F:F); + SparseMatrixS L; + cotmatrix(V,F,L); + + ARAPEnergyType eff_energy = data.energy; + if(eff_energy == ARAP_ENERGY_TYPE_DEFAULT) + { + switch(F.cols()) + { + case 3: + if(data.dim == 3) + { + eff_energy = ARAP_ENERGY_TYPE_SPOKES_AND_RIMS; + }else + { + eff_energy = ARAP_ENERGY_TYPE_ELEMENTS; + } + break; + case 4: + eff_energy = ARAP_ENERGY_TYPE_ELEMENTS; + break; + default: + assert(false); + } + } + + + // Get covariance scatter matrix, when applied collects the covariance + // matrices used to fit rotations to during optimization + covariance_scatter_matrix(ref_V,ref_F,eff_energy,data.CSM); + if(flat) + { + data.CSM = (data.CSM * ref_map_dim.transpose()).eval(); + } + assert(data.CSM.cols() == V.rows()*data.dim); + + // Get group sum scatter matrix, when applied sums all entries of the same + // group according to G + SparseMatrix G_sum; + if(data.G.size() == 0) + { + if(eff_energy == ARAP_ENERGY_TYPE_ELEMENTS) + { + speye(F.rows(),G_sum); + }else + { + speye(n,G_sum); + } + }else + { + // groups are defined per vertex, convert to per face using mode + if(eff_energy == ARAP_ENERGY_TYPE_ELEMENTS) + { + Eigen::Matrix GG; + MatrixXi GF(F.rows(),F.cols()); + for(int j = 0;j GFj; + slice(data.G,F.col(j),GFj); + GF.col(j) = GFj; + } + mode(GF,2,GG); + data.G=GG; + } + //printf("group_sum_matrix()\n"); + group_sum_matrix(data.G,G_sum); + } + SparseMatrix G_sum_dim; + repdiag(G_sum,data.dim,G_sum_dim); + assert(G_sum_dim.cols() == data.CSM.rows()); + data.CSM = (G_sum_dim * data.CSM).eval(); + + + arap_rhs(ref_V,ref_F,data.dim,eff_energy,data.K); + if(flat) + { + data.K = (ref_map_dim * data.K).eval(); + } + assert(data.K.rows() == data.n*data.dim); + + SparseMatrix Q = (-L).eval(); + + if(data.with_dynamics) + { + const double h = data.h; + assert(h != 0); + SparseMatrix M; + massmatrix(V,F,MASSMATRIX_TYPE_DEFAULT,data.M); + const double dw = (1./data.ym)*(h*h); + SparseMatrix DQ = dw * 1./(h*h)*data.M; + Q += DQ; + // Dummy external forces + data.f_ext = MatrixXd::Zero(n,data.dim); + data.vel = MatrixXd::Zero(n,data.dim); + } + + return min_quad_with_fixed_precompute( + Q,b,SparseMatrix(),true,data.solver_data); +} + +template < + typename Derivedbc, + typename DerivedU> +IGL_INLINE bool igl::arap_solve( + const Eigen::PlainObjectBase & bc, + ARAPData & data, + Eigen::PlainObjectBase & U) +{ + using namespace Eigen; + using namespace std; + assert(data.b.size() == bc.rows()); + if(bc.size() > 0) + { + assert(bc.cols() == data.dim && "bc.cols() match data.dim"); + } + const int n = data.n; + int iter = 0; + if(U.size() == 0) + { + // terrible initial guess.. should at least copy input mesh +#ifndef NDEBUG + cerr<<"arap_solve: Using terrible initial guess for U. Try U = V."<0) + { + bcc = bc.col(c); + } + min_quad_with_fixed_solve( + data.solver_data, + Bc,bcc,Beq, + Uc); + U.col(c) = Uc; + } + + iter++; + } + if(data.with_dynamics) + { + // Keep track of velocity for next time + data.vel = (U-U0)/data.h; + } + + return true; +} + +#ifdef IGL_STATIC_LIBRARY +template bool igl::arap_solve, Eigen::Matrix >(Eigen::PlainObjectBase > const&, igl::ARAPData&, Eigen::PlainObjectBase >&); +template bool igl::arap_precomputation, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase > const&, igl::ARAPData&); +#endif diff --git a/src/igl/arap.h b/src/igl/arap.h new file mode 100644 index 000000000..b2210e64f --- /dev/null +++ b/src/igl/arap.h @@ -0,0 +1,104 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ARAP_H +#define IGL_ARAP_H +#include "igl_inline.h" +#include "min_quad_with_fixed.h" +#include "ARAPEnergyType.h" +#include +#include + +namespace igl +{ + struct ARAPData + { + // n #V + // G #V list of group indices (1 to k) for each vertex, such that vertex i + // is assigned to group G(i) + // energy type of energy to use + // with_dynamics whether using dynamics (need to call arap_precomputation + // after changing) + // f_ext #V by dim list of external forces + // vel #V by dim list of velocities + // h dynamics time step + // ym ~Young's modulus smaller is softer, larger is more rigid/stiff + // max_iter maximum inner iterations + // K rhs pre-multiplier + // M mass matrix + // solver_data quadratic solver data + // b list of boundary indices into V + // dim dimension being used for solving + int n; + Eigen::VectorXi G; + ARAPEnergyType energy; + bool with_dynamics; + Eigen::MatrixXd f_ext,vel; + double h; + double ym; + int max_iter; + Eigen::SparseMatrix K,M; + Eigen::SparseMatrix CSM; + min_quad_with_fixed_data solver_data; + Eigen::VectorXi b; + int dim; + ARAPData(): + n(0), + G(), + energy(ARAP_ENERGY_TYPE_DEFAULT), + with_dynamics(false), + f_ext(), + h(1), + ym(1), + max_iter(10), + K(), + CSM(), + solver_data(), + b(), + dim(-1) // force this to be set by _precomputation + { + }; + }; + + // Compute necessary information to start using an ARAP deformation + // + // Inputs: + // V #V by dim list of mesh positions + // F #F by simplex-size list of triangle|tet indices into V + // dim dimension being used at solve time. For deformation usually dim = + // V.cols(), for surface parameterization V.cols() = 3 and dim = 2 + // b #b list of "boundary" fixed vertex indices into V + // Outputs: + // data struct containing necessary precomputation + template < + typename DerivedV, + typename DerivedF, + typename Derivedb> + IGL_INLINE bool arap_precomputation( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const int dim, + const Eigen::PlainObjectBase & b, + ARAPData & data); + // Inputs: + // bc #b by dim list of boundary conditions + // data struct containing necessary precomputation and parameters + // U #V by dim initial guess + template < + typename Derivedbc, + typename DerivedU> + IGL_INLINE bool arap_solve( + const Eigen::PlainObjectBase & bc, + ARAPData & data, + Eigen::PlainObjectBase & U); +}; + +#ifndef IGL_STATIC_LIBRARY +#include "arap.cpp" +#endif + +#endif diff --git a/src/igl/arap_dof.cpp b/src/igl/arap_dof.cpp new file mode 100644 index 000000000..033d1deb3 --- /dev/null +++ b/src/igl/arap_dof.cpp @@ -0,0 +1,884 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "arap_dof.h" + +#include "cotmatrix.h" +#include "massmatrix.h" +#include "speye.h" +#include "repdiag.h" +#include "repmat.h" +#include "slice.h" +#include "colon.h" +#include "is_sparse.h" +#include "mode.h" +#include "is_symmetric.h" +#include "group_sum_matrix.h" +#include "arap_rhs.h" +#include "covariance_scatter_matrix.h" +#include "fit_rotations.h" + +#include "verbose.h" +#include "print_ijv.h" + +#include "get_seconds_hires.h" +//#include "MKLEigenInterface.h" +#include "min_quad_dense.h" +#include "get_seconds.h" +#include "columnize.h" + +// defined if no early exit is supported, i.e., always take a fixed number of iterations +#define IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT + +// A careful derivation of this implementation is given in the corresponding +// matlab function arap_dof.m +template +IGL_INLINE bool igl::arap_dof_precomputation( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const LbsMatrixType & M, + const Eigen::Matrix & G, + ArapDOFData & data) +{ + using namespace Eigen; + typedef Matrix MatrixXS; + // number of mesh (domain) vertices + int n = V.rows(); + // cache problem size + data.n = n; + // dimension of mesh + data.dim = V.cols(); + assert(data.dim == M.rows()/n); + assert(data.dim*n == M.rows()); + if(data.dim == 3) + { + // Check if z-coordinate is all zeros + if(V.col(2).minCoeff() == 0 && V.col(2).maxCoeff() == 0) + { + data.effective_dim = 2; + } + }else + { + data.effective_dim = data.dim; + } + // Number of handles + data.m = M.cols()/data.dim/(data.dim+1); + assert(data.m*data.dim*(data.dim+1) == M.cols()); + //assert(m == C.rows()); + + //printf("n=%d; dim=%d; m=%d;\n",n,data.dim,data.m); + + // Build cotangent laplacian + SparseMatrix Lcot; + //printf("cotmatrix()\n"); + cotmatrix(V,F,Lcot); + // Discrete laplacian (should be minus matlab version) + SparseMatrix Lapl = -2.0*Lcot; +#ifdef EXTREME_VERBOSE + cout<<"LaplIJV=["< G_sum; + if(G.size() == 0) + { + speye(n,G_sum); + }else + { + // groups are defined per vertex, convert to per face using mode + Eigen::Matrix GG; + if(data.energy == ARAP_ENERGY_TYPE_ELEMENTS) + { + MatrixXi GF(F.rows(),F.cols()); + for(int j = 0;j GFj; + slice(G,F.col(j),GFj); + GF.col(j) = GFj; + } + mode(GF,2,GG); + }else + { + GG=G; + } + //printf("group_sum_matrix()\n"); + group_sum_matrix(GG,G_sum); + } + +#ifdef EXTREME_VERBOSE + cout<<"G_sumIJV=["< CSM; + //printf("covariance_scatter_matrix()\n"); + covariance_scatter_matrix(V,F,data.energy,CSM); +#ifdef EXTREME_VERBOSE + cout<<"CSMIJV=["< G_sum_dim; + repdiag(G_sum,data.dim,G_sum_dim); + CSM = (G_sum_dim * CSM).eval(); +#ifdef EXTREME_VERBOSE + cout<<"CSMIJV=["< span_n(n); + for(int i = 0;i span_mlbs_cols(M.cols()); + for(int i = 0;i CSMj; + //printf("CSM_M(): slice\n"); + slice( + CSM, + colon(j*k,(j+1)*k-1), + colon(j*n,(j+1)*n-1), + CSMj); + assert(CSMj.rows() == k); + assert(CSMj.cols() == n); + LbsMatrixType CSMjM_i = CSMj * M_i; + if(is_sparse(CSMjM_i)) + { + // Convert to full + //printf("CSM_M(): full\n"); + MatrixXd CSMjM_ifull(CSMjM_i); +// printf("CSM_M[%d]: %d %d\n",i,data.CSM_M[i].rows(),data.CSM_M[i].cols()); +// printf("CSM_M[%d].block(%d*%d=%d,0,%d,%d): %d %d\n",i,j,k,CSMjM_i.rows(),CSMjM_i.cols(), +// data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()).rows(), +// data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()).cols()); +// printf("CSM_MjMi: %d %d\n",i,CSMjM_i.rows(),CSMjM_i.cols()); +// printf("CSM_MjM_ifull: %d %d\n",i,CSMjM_ifull.rows(),CSMjM_ifull.cols()); + data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()) = CSMjM_ifull; + }else + { + data.CSM_M[i].block(j*k,0,CSMjM_i.rows(),CSMjM_i.cols()) = CSMjM_i; + } + } +#ifdef EXTREME_VERBOSE + cout<<"CSM_Mi=["< K; + arap_rhs(V,F,V.cols(),data.energy,K); +//#ifdef EXTREME_VERBOSE +// cout<<"KIJV=["< G_sumT = G_sum.transpose(); + SparseMatrix G_sumT_dim_dim; + repdiag(G_sumT,data.dim*data.dim,G_sumT_dim_dim); + LbsMatrixType MT = M.transpose(); + // If this is a bottle neck then consider reordering matrix multiplication + data.M_KG = -4.0 * (MT * (K * G_sumT_dim_dim)); +//#ifdef EXTREME_VERBOSE +// cout<<"data.M_KGIJV=["< A; + repdiag(Lapl,data.dim,A); + data.Q = MT * (A * M); +//#ifdef EXTREME_VERBOSE +// cout<<"QIJV=["< Mass; + //printf("massmatrix()\n"); + massmatrix(V,F,(F.cols()>3?MASSMATRIX_TYPE_BARYCENTRIC:MASSMATRIX_TYPE_VORONOI),Mass); + //cout<<"MIJV=["< Mass_rep; + repdiag(Mass,data.dim,Mass_rep); + + // Multiply either side by weights matrix (should be dense) + data.Mass_tilde = MT * Mass_rep * M; + MatrixXd ones(data.dim*data.n,data.dim); + for(int i = 0;i + inline static SSCALAR maxBlokErr(const Eigen::Matrix3f &blok) + { + SSCALAR mD; + SSCALAR value = blok(0,0); + SSCALAR diff1 = fabs(blok(1,1) - value); + SSCALAR diff2 = fabs(blok(2,2) - value); + if (diff1 > diff2) mD = diff1; + else mD = diff2; + + for (int v=0; v<3; v++) + { + for (int w=0; w<3; w++) + { + if (v == w) + { + continue; + } + if (mD < fabs(blok(v, w))) + { + mD = fabs(blok(v, w)); + } + } + } + + return mD; + } + + // converts CSM_M_SSCALAR[0], CSM_M_SSCALAR[1], CSM_M_SSCALAR[2] into one + // "condensed" matrix CSM while checking we're not losing any information by + // this process; specifically, returns maximal difference from scaled 3x3 + // identity blocks, which should be pretty small number + template + static typename MatrixXS::Scalar condense_CSM( + const std::vector &CSM_M_SSCALAR, + int numBones, + int dim, + MatrixXS &CSM) + { + const int numRows = CSM_M_SSCALAR[0].rows(); + assert(CSM_M_SSCALAR[0].cols() == dim*(dim+1)*numBones); + assert(CSM_M_SSCALAR[1].cols() == dim*(dim+1)*numBones); + assert(CSM_M_SSCALAR[2].cols() == dim*(dim+1)*numBones); + assert(CSM_M_SSCALAR[1].rows() == numRows); + assert(CSM_M_SSCALAR[2].rows() == numRows); + + const int numCols = (dim + 1)*numBones; + CSM.resize(numRows, numCols); + + typedef typename MatrixXS::Scalar SSCALAR; + SSCALAR maxDiff = 0.0f; + + for (int r=0; r(blok); + if (mD > maxDiff) maxDiff = mD; + + // use the first value: + CSM(r, coord*numBones + b) = blok(0,0); + } + } + } + + return maxDiff; + } + + // splits x_0, ... , x_dim coordinates in column vector 'L' into a numBones*(dimp1) x dim matrix 'Lsep'; + // assumes 'Lsep' has already been preallocated + // + // is this the same as uncolumnize? no. + template + static void splitColumns( + const MatL &L, + int numBones, + int dim, + int dimp1, + MatLsep &Lsep) + { + assert(L.cols() == 1); + assert(L.rows() == dim*(dimp1)*numBones); + + assert(Lsep.rows() == (dimp1)*numBones && Lsep.cols() == dim); + + for (int b=0; b + static void mergeColumns(const MatrixXS &Lsep, int numBones, int dim, int dimp1, MatrixXS &L) + { + assert(L.cols() == 1); + assert(L.rows() == dim*(dimp1)*numBones); + + assert(Lsep.rows() == (dimp1)*numBones && Lsep.cols() == dim); + + for (int b=0; b + static typename MatrixXS::Scalar condense_Solve1(MatrixXS &Solve1, int numBones, int numGroups, int dim, MatrixXS &CSolve1) + { + assert(Solve1.rows() == dim*(dim + 1)*numBones); + assert(Solve1.cols() == dim*dim*numGroups); + + typedef typename MatrixXS::Scalar SSCALAR; + SSCALAR maxDiff = 0.0f; + + CSolve1.resize((dim + 1)*numBones, dim*numGroups); + for (int rowCoord=0; rowCoord(blok); + if (mD > maxDiff) maxDiff = mD; + + CSolve1(rowCoord*numBones + b, colCoord*numGroups + g) = blok(0,0); + } + } + } + } + + return maxDiff; + } +} + +template +IGL_INLINE bool igl::arap_dof_recomputation( + const Eigen::Matrix & fixed_dim, + const Eigen::SparseMatrix & A_eq, + ArapDOFData & data) +{ + using namespace Eigen; + typedef Matrix MatrixXS; + + LbsMatrixType * Q; + LbsMatrixType Qdyn; + if(data.with_dynamics) + { + // multiply by 1/timestep and to quadratic coefficients matrix + // Might be missing a 0.5 here + LbsMatrixType Q_copy = data.Q; + Qdyn = Q_copy + (1.0/(data.h*data.h))*data.Mass_tilde; + Q = &Qdyn; + + // This may/should be superfluous + //printf("is_symmetric()\n"); + if(!is_symmetric(*Q)) + { + //printf("Fixing symmetry...\n"); + // "Fix" symmetry + LbsMatrixType QT = (*Q).transpose(); + LbsMatrixType Q_copy = *Q; + *Q = 0.5*(Q_copy+QT); + // Check that ^^^ this really worked. It doesn't always + //assert(is_symmetric(*Q)); + } + }else + { + Q = &data.Q; + } + + assert((int)data.CSM_M.size() == data.dim); + assert(A_eq.cols() == data.m*data.dim*(data.dim+1)); + data.fixed_dim = fixed_dim; + + if(fixed_dim.size() > 0) + { + assert(fixed_dim.maxCoeff() < data.m*data.dim*(data.dim+1)); + assert(fixed_dim.minCoeff() >= 0); + } + +#ifdef EXTREME_VERBOSE + cout<<"data.fixed_dim=["<(), + M_Solve.block(0, fsRows, fsRows, fsCols2).template cast(); + + if(data.with_dynamics) + { + printf( + "---------------------------------------------------------------------\n" + "\n\n\nWITH DYNAMICS recomputation\n\n\n" + "---------------------------------------------------------------------\n" + ); + // Also need to save Π1 before it gets multiplied by Ktilde (aka M_KG) + data.Pi_1 = M_Solve.block(0, 0, fsRows, fsRows).template cast(); + } + + // Precompute condensed matrices, + // first CSM: + std::vector CSM_M_SSCALAR; + CSM_M_SSCALAR.resize(data.dim); + for (int i=0; i(); + SSCALAR maxErr1 = condense_CSM(CSM_M_SSCALAR, data.m, data.dim, data.CSM); + verbose("condense_CSM maxErr = %.15f (this should be close to zero)\n", maxErr1); + assert(fabs(maxErr1) < 1e-5); + + // and then solveBlock1: + // number of groups + const int k = data.CSM_M[0].rows()/data.dim; + MatrixXS SolveBlock1 = data.M_FullSolve.block(0, 0, data.M_FullSolve.rows(), data.dim * data.dim * k); + SSCALAR maxErr2 = condense_Solve1(SolveBlock1, data.m, k, data.dim, data.CSolveBlock1); + verbose("condense_Solve1 maxErr = %.15f (this should be close to zero)\n", maxErr2); + assert(fabs(maxErr2) < 1e-5); + + return true; +} + +template +IGL_INLINE bool igl::arap_dof_update( + const ArapDOFData & data, + const Eigen::Matrix & B_eq, + const Eigen::MatrixXd & L0, + const int max_iters, + const double +#ifdef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT + tol, +#else + /*tol*/, +#endif + Eigen::MatrixXd & L + ) +{ + using namespace Eigen; + typedef Matrix MatrixXS; +#ifdef ARAP_GLOBAL_TIMING + double timer_start = get_seconds_hires(); +#endif + + // number of dimensions + assert((int)data.CSM_M.size() == data.dim); + assert((int)L0.size() == (data.m)*data.dim*(data.dim+1)); + assert(max_iters >= 0); + assert(tol >= 0); + + // timing variables + double + sec_start, + sec_covGather, + sec_fitRotations, + //sec_rhs, + sec_prepMult, + sec_solve, sec_end; + + assert(L0.cols() == 1); +#ifdef EXTREME_VERBOSE + cout<<"dim="<(); + + int iters = 0; +#ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT + double max_diff = tol+1; +#endif + + MatrixXS S(k*data.dim,data.dim); + MatrixXS R(data.dim,data.dim*k); + Eigen::Matrix Rcol(data.dim * data.dim * k); + Matrix B_eq_SSCALAR = B_eq.cast(); + Matrix B_eq_fix_SSCALAR; + Matrix L0SSCALAR = L0.cast(); + slice(L0SSCALAR, data.fixed_dim, B_eq_fix_SSCALAR); + //MatrixXS rhsFull(Rcol.rows() + B_eq.rows() + B_eq_fix_SSCALAR.rows(), 1); + + MatrixXS Lsep(data.m*(data.dim + 1), 3); + const MatrixXS L_part2 = + data.M_FullSolve.block(0, Rcol.rows(), data.M_FullSolve.rows(), B_eq_SSCALAR.rows()) * B_eq_SSCALAR; + const MatrixXS L_part3 = + data.M_FullSolve.block(0, Rcol.rows() + B_eq_SSCALAR.rows(), data.M_FullSolve.rows(), B_eq_fix_SSCALAR.rows()) * B_eq_fix_SSCALAR; + MatrixXS L_part2and3 = L_part2 + L_part3; + + // preallocate workspace variables: + MatrixXS Rxyz(k*data.dim, data.dim); + MatrixXS L_part1xyz((data.dim + 1) * data.m, data.dim); + MatrixXS L_part1(data.dim * (data.dim + 1) * data.m, 1); + +#ifdef ARAP_GLOBAL_TIMING + double timer_prepFinished = get_seconds_hires(); +#endif + +#ifdef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT + while(iters < max_iters) +#else + while(iters < max_iters && max_diff > tol) +#endif + { + if(data.print_timings) + { + sec_start = get_seconds_hires(); + } + +#ifndef IGL_ARAP_DOF_FIXED_ITERATIONS_COUNT + L_prev = L_SSCALAR; +#endif + /////////////////////////////////////////////////////////////////////////// + // Local step: Fix positions, fit rotations + /////////////////////////////////////////////////////////////////////////// + + // Gather covariance matrices + + splitColumns(L_SSCALAR, data.m, data.dim, data.dim + 1, Lsep); + + S = data.CSM * Lsep; + // interestingly, this doesn't seem to be so slow, but + //MKL is still 2x faster (probably due to AVX) + //#ifdef IGL_ARAP_DOF_DOUBLE_PRECISION_SOLVE + // MKL_matMatMult_double(S, data.CSM, Lsep); + //#else + // MKL_matMatMult_single(S, data.CSM, Lsep); + //#endif + + if(data.print_timings) + { + sec_covGather = get_seconds_hires(); + } + +#ifdef EXTREME_VERBOSE + cout<<"S=["<(); + + MatrixXd temp_g = data.fgrav*(data.grav_mag*data.grav_dir); + + assert(data.fext.rows() == temp_g.rows()); + assert(data.fext.cols() == temp_g.cols()); + MatrixXd temp2 = data.Mass_tilde * temp_d + temp_g + data.fext.template cast(); + MatrixXS temp2_f = temp2.template cast(); + L_part1_dyn = data.Pi_1 * temp2_f; + L_part1.array() = L_part1.array() + L_part1_dyn.array(); + } + + //L_SSCALAR = L_part1 + L_part2and3; + assert(L_SSCALAR.rows() == L_part1.rows() && L_SSCALAR.rows() == L_part2and3.rows()); + for (int i=0; i(); + assert(L.cols() == 1); + +#ifdef ARAP_GLOBAL_TIMING + double timer_finito = get_seconds_hires(); + printf( + "ARAP preparation = %f, " + "all %i iterations = %f [ms]\n", + (timer_prepFinished - timer_start)*1000.0, + max_iters, + (timer_finito - timer_prepFinished)*1000.0); +#endif + + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::arap_dof_update, double>(ArapDOFData, double> const&, Eigen::Matrix const&, Eigen::Matrix const&, int, double, Eigen::Matrix&); +template bool igl::arap_dof_recomputation, double>(Eigen::Matrix const&, Eigen::SparseMatrix const&, ArapDOFData, double>&); +template bool igl::arap_dof_precomputation, double>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, ArapDOFData, double>&); +template bool igl::arap_dof_update, float>(igl::ArapDOFData, float> const&, Eigen::Matrix const&, Eigen::Matrix const&, int, double, Eigen::Matrix&); +template bool igl::arap_dof_recomputation, float>(Eigen::Matrix const&, Eigen::SparseMatrix const&, igl::ArapDOFData, float>&); +template bool igl::arap_dof_precomputation, float>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::ArapDOFData, float>&); +#endif diff --git a/src/igl/arap_dof.h b/src/igl/arap_dof.h new file mode 100644 index 000000000..f3647a3a1 --- /dev/null +++ b/src/igl/arap_dof.h @@ -0,0 +1,244 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ARAP_ENERGY_TYPE_DOF_H +#define IGL_ARAP_ENERGY_TYPE_DOF_H +#include "igl_inline.h" + +#include +#include +#include "ARAPEnergyType.h" +#include + +namespace igl +{ + // Caller example: + // + // Once: + // arap_dof_precomputation(...) + // + // Each frame: + // while(not satisfied) + // arap_dof_update(...) + // end + + template + struct ArapDOFData; + + /////////////////////////////////////////////////////////////////////////// + // + // Arap DOF precomputation consists of two parts the computation. The first is + // that which depends solely on the mesh (V,F), the linear blend skinning + // weights (M) and the groups G. Then there's the part that depends on the + // previous precomputation and the list of free and fixed vertices. + // + /////////////////////////////////////////////////////////////////////////// + + + // The code and variables differ from the description in Section 3 of "Fast + // Automatic Skinning Transformations" by [Jacobson et al. 2012] + // + // Here is a useful conversion table: + // + // [article] [code] + // S = \tilde{K} T S = CSM * Lsep + // S --> R S --> R --shuffled--> Rxyz + // Gamma_solve RT = Pi_1 \tilde{K} RT L_part1xyz = CSolveBlock1 * Rxyz + // Pi_1 \tilde{K} CSolveBlock1 + // Peq = [T_full; P_pos] + // T_full B_eq_fix <--- L0 + // P_pos B_eq + // Pi_2 * P_eq = Lpart2and3 = Lpart2 + Lpart3 + // Pi_2_left T_full + Lpart3 = M_fullsolve(right) * B_eq_fix + // Pi_2_right P_pos Lpart2 = M_fullsolve(left) * B_eq + // T = [Pi_1 Pi_2] [\tilde{K}TRT P_eq] L = Lpart1 + Lpart2and3 + // + + // Precomputes the system we are going to optimize. This consists of building + // constructor matrices (to compute covariance matrices from transformations + // and to build the poisson solve right hand side from rotation matrix entries) + // and also prefactoring the poisson system. + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by {3|4} list of face indices + // M #V * dim by #handles * dim * (dim+1) matrix such that + // new_V(:) = LBS(V,W,A) = reshape(M * A,size(V)), where A is a column + // vectors formed by the entries in each handle's dim by dim+1 + // transformation matrix. Specifcally, A = + // reshape(permute(Astack,[3 1 2]),n*dim*(dim+1),1) + // or A = [Lxx;Lyx;Lxy;Lyy;tx;ty], and likewise for other dim + // if Astack(:,:,i) is the dim by (dim+1) transformation at handle i + // handles are ordered according to P then BE (point handles before bone + // handles) + // G #V list of group indices (1 to k) for each vertex, such that vertex i + // is assigned to group G(i) + // Outputs: + // data structure containing all necessary precomputation for calling + // arap_dof_update + // Returns true on success, false on error + // + // See also: lbs_matrix_column + template + IGL_INLINE bool arap_dof_precomputation( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const LbsMatrixType & M, + const Eigen::Matrix & G, + ArapDOFData & data); + + // Should always be called after arap_dof_precomputation, but may be called in + // between successive calls to arap_dof_update, recomputes precomputation + // given that there are only changes in free and fixed + // + // Inputs: + // fixed_dim list of transformation element indices for fixed (or partailly + // fixed) handles: not necessarily the complement of 'free' + // NOTE: the constraints for fixed transformations still need to be + // present in A_eq + // A_eq dim*#constraint_points by m*dim*(dim+1) matrix of linear equality + // constraint coefficients. Each row corresponds to a linear constraint, + // so that A_eq * L = Beq says that the linear transformation entries in + // the column L should produce the user supplied positional constraints + // for each handle in Beq. The row A_eq(i*dim+d) corresponds to the + // constrain on coordinate d of position i + // Outputs: + // data structure containing all necessary precomputation for calling + // arap_dof_update + // Returns true on success, false on error + // + // See also: lbs_matrix_column + template + IGL_INLINE bool arap_dof_recomputation( + const Eigen::Matrix & fixed_dim, + const Eigen::SparseMatrix & A_eq, + ArapDOFData & data); + + // Optimizes the transformations attached to each weight function based on + // precomputed system. + // + // Inputs: + // data precomputation data struct output from arap_dof_precomputation + // Beq dim*#constraint_points constraint values. + // L0 #handles * dim * dim+1 list of initial guess transformation entries, + // also holds fixed transformation entries for fixed handles + // max_iters maximum number of iterations + // tol stopping criteria parameter. If variables (linear transformation + // matrix entries) change by less than 'tol' the optimization terminates, + // 0.75 (weak tolerance) + // 0.0 (extreme tolerance) + // Outputs: + // L #handles * dim * dim+1 list of final optimized transformation entries, + // allowed to be the same as L + template + IGL_INLINE bool arap_dof_update( + const ArapDOFData & data, + const Eigen::Matrix & B_eq, + const Eigen::MatrixXd & L0, + const int max_iters, + const double tol, + Eigen::MatrixXd & L + ); + + // Structure that contains fields for all precomputed data or data that needs + // to be remembered at update + template + struct ArapDOFData + { + typedef Eigen::Matrix MatrixXS; + // Type of arap energy we're solving + igl::ARAPEnergyType energy; + //// LU decomposition precomptation data; note: not used by araf_dop_update + //// any more, replaced by M_FullSolve + //igl::min_quad_with_fixed_data lu_data; + // List of indices of fixed transformation entries + Eigen::Matrix fixed_dim; + // List of precomputed covariance scatter matrices multiplied by lbs + // matrices + //std::vector > CSM_M; + std::vector CSM_M; + LbsMatrixType M_KG; + // Number of mesh vertices + int n; + // Number of weight functions + int m; + // Number of dimensions + int dim; + // Effective dimensions + int effective_dim; + // List of indices into C of positional constraints + Eigen::Matrix interpolated; + std::vector free_mask; + // Full quadratic coefficients matrix before lagrangian (should be dense) + LbsMatrixType Q; + + + //// Solve matrix for the global step + //Eigen::MatrixXd M_Solve; // TODO: remove from here + + // Full solve matrix that contains also conversion from rotations to the right hand side, + // i.e., solves Poisson transformations just from rotations and positional constraints + MatrixXS M_FullSolve; + + // Precomputed condensed matrices (3x3 commutators folded to 1x1): + MatrixXS CSM; + MatrixXS CSolveBlock1; + + // Print timings at each update + bool print_timings; + + // Dynamics + bool with_dynamics; + // I'm hiding the extra dynamics stuff in this struct, which sort of defeats + // the purpose of this function-based coding style... + + // Time step + double h; + + // L0 #handles * dim * dim+1 list of transformation entries from + // previous solve + MatrixXS L0; + //// Lm1 #handles * dim * dim+1 list of transformation entries from + //// previous-previous solve + //MatrixXS Lm1; + // "Velocity" + MatrixXS Lvel0; + + // #V by dim matrix of external forces + // fext + MatrixXS fext; + + // Mass_tilde: MT * Mass * M + LbsMatrixType Mass_tilde; + + // Force due to gravity (premultiplier) + Eigen::MatrixXd fgrav; + // Direction of gravity + Eigen::Vector3d grav_dir; + // Magnitude of gravity + double grav_mag; + + // Π1 from the paper + MatrixXS Pi_1; + + // Default values + ArapDOFData(): + energy(igl::ARAP_ENERGY_TYPE_SPOKES), + with_dynamics(false), + h(1), + grav_dir(0,-1,0), + grav_mag(0) + { + } + }; +} + +#ifndef IGL_STATIC_LIBRARY +# include "arap_dof.cpp" +#endif + +#endif diff --git a/src/igl/arap_linear_block.cpp b/src/igl/arap_linear_block.cpp new file mode 100644 index 000000000..14886b2bc --- /dev/null +++ b/src/igl/arap_linear_block.cpp @@ -0,0 +1,253 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "arap_linear_block.h" +#include "verbose.h" +#include "cotmatrix_entries.h" +#include + +template +IGL_INLINE void igl::arap_linear_block( + const MatV & V, + const MatF & F, + const int d, + const igl::ARAPEnergyType energy, + Eigen::SparseMatrix & Kd) +{ + switch(energy) + { + case ARAP_ENERGY_TYPE_SPOKES: + return igl::arap_linear_block_spokes(V,F,d,Kd); + break; + case ARAP_ENERGY_TYPE_SPOKES_AND_RIMS: + return igl::arap_linear_block_spokes_and_rims(V,F,d,Kd); + break; + case ARAP_ENERGY_TYPE_ELEMENTS: + return igl::arap_linear_block_elements(V,F,d,Kd); + break; + default: + verbose("Unsupported energy type: %d\n",energy); + assert(false); + } +} + + +template +IGL_INLINE void igl::arap_linear_block_spokes( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd) +{ + using namespace std; + using namespace Eigen; + // simplex size (3: triangles, 4: tetrahedra) + int simplex_size = F.cols(); + // Number of elements + int m = F.rows(); + // Temporary output + Matrix edges; + Kd.resize(V.rows(), V.rows()); + vector > Kd_IJV; + if(simplex_size == 3) + { + // triangles + Kd.reserve(7*V.rows()); + Kd_IJV.reserve(7*V.rows()); + edges.resize(3,2); + edges << + 1,2, + 2,0, + 0,1; + }else if(simplex_size == 4) + { + // tets + Kd.reserve(17*V.rows()); + Kd_IJV.reserve(17*V.rows()); + edges.resize(6,2); + edges << + 1,2, + 2,0, + 0,1, + 3,0, + 3,1, + 3,2; + } + // gather cotangent weights + Matrix C; + cotmatrix_entries(V,F,C); + // should have weights for each edge + assert(C.cols() == edges.rows()); + // loop over elements + for(int i = 0;i(source,dest,v)); + Kd_IJV.push_back(Triplet(dest,source,-v)); + Kd_IJV.push_back(Triplet(source,source,v)); + Kd_IJV.push_back(Triplet(dest,dest,-v)); + } + } + Kd.setFromTriplets(Kd_IJV.begin(),Kd_IJV.end()); + Kd.makeCompressed(); +} + +template +IGL_INLINE void igl::arap_linear_block_spokes_and_rims( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd) +{ + using namespace std; + using namespace Eigen; + // simplex size (3: triangles, 4: tetrahedra) + int simplex_size = F.cols(); + // Number of elements + int m = F.rows(); + // Temporary output + Kd.resize(V.rows(), V.rows()); + vector > Kd_IJV; + Matrix edges; + if(simplex_size == 3) + { + // triangles + Kd.reserve(7*V.rows()); + Kd_IJV.reserve(7*V.rows()); + edges.resize(3,2); + edges << + 1,2, + 2,0, + 0,1; + }else if(simplex_size == 4) + { + // tets + Kd.reserve(17*V.rows()); + Kd_IJV.reserve(17*V.rows()); + edges.resize(6,2); + edges << + 1,2, + 2,0, + 0,1, + 3,0, + 3,1, + 3,2; + // Not implemented yet for tets + assert(false); + } + // gather cotangent weights + Matrix C; + cotmatrix_entries(V,F,C); + // should have weights for each edge + assert(C.cols() == edges.rows()); + // loop over elements + for(int i = 0;i(Rs,Rd,v)); + Kd_IJV.push_back(Triplet(Rd,Rs,-v)); + }else if(Rd == source) + { + Kd_IJV.push_back(Triplet(Rd,Rs,v)); + }else if(Rs == dest) + { + Kd_IJV.push_back(Triplet(Rs,Rd,-v)); + } + } + Kd_IJV.push_back(Triplet(source,source,v)); + Kd_IJV.push_back(Triplet(dest,dest,-v)); + } + } + Kd.setFromTriplets(Kd_IJV.begin(),Kd_IJV.end()); + Kd.makeCompressed(); +} + +template +IGL_INLINE void igl::arap_linear_block_elements( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd) +{ + using namespace std; + using namespace Eigen; + // simplex size (3: triangles, 4: tetrahedra) + int simplex_size = F.cols(); + // Number of elements + int m = F.rows(); + // Temporary output + Kd.resize(V.rows(), F.rows()); + vector > Kd_IJV; + Matrix edges; + if(simplex_size == 3) + { + // triangles + Kd.reserve(7*V.rows()); + Kd_IJV.reserve(7*V.rows()); + edges.resize(3,2); + edges << + 1,2, + 2,0, + 0,1; + }else if(simplex_size == 4) + { + // tets + Kd.reserve(17*V.rows()); + Kd_IJV.reserve(17*V.rows()); + edges.resize(6,2); + edges << + 1,2, + 2,0, + 0,1, + 3,0, + 3,1, + 3,2; + } + // gather cotangent weights + Matrix C; + cotmatrix_entries(V,F,C); + // should have weights for each edge + assert(C.cols() == edges.rows()); + // loop over elements + for(int i = 0;i(source,i,v)); + Kd_IJV.push_back(Triplet(dest,i,-v)); + } + } + Kd.setFromTriplets(Kd_IJV.begin(),Kd_IJV.end()); + Kd.makeCompressed(); +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template IGL_INLINE void igl::arap_linear_block, Eigen::Matrix, double>(Eigen::Matrix const&, Eigen::Matrix const&, int, igl::ARAPEnergyType, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/arap_linear_block.h b/src/igl/arap_linear_block.h new file mode 100644 index 000000000..8dfb744f6 --- /dev/null +++ b/src/igl/arap_linear_block.h @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ARAP_LINEAR_BLOCK_H +#define IGL_ARAP_LINEAR_BLOCK_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // ARAP_LINEAR_BLOCK constructs a block of the matrix which constructs the + // linear terms of a given arap energy. When treating rotations as knowns + // (arranged in a column) then this constructs Kd of K such that the linear + // portion of the energy is as a column: + // K * R = [Kx Z ... Ky Z ... + // Z Kx ... Z Ky ... + // ... ] + // These blocks are also used to build the "covariance scatter matrices". + // Here we want to build a scatter matrix that multiplies against positions + // (treated as known) producing covariance matrices to fit each rotation. + // Notice that in the case of the RHS of the poisson solve the rotations are + // known and the positions unknown, and vice versa for rotation fitting. + // These linear block just relate the rotations to the positions, linearly in + // each. + // + // Templates: + // MatV vertex position matrix, e.g. Eigen::MatrixXd + // MatF face index matrix, e.g. Eigen::MatrixXd + // Scalar e.g. double + // Inputs: + // V #V by dim list of initial domain positions + // F #F by #simplex size list of triangle indices into V + // d coordinate of linear constructor to build + // energy ARAPEnergyType enum value defining which energy is being used. + // See ARAPEnergyType.h for valid options and explanations. + // Outputs: + // Kd #V by #V/#F block of the linear constructor matrix corresponding to + // coordinate d + // + template + IGL_INLINE void arap_linear_block( + const MatV & V, + const MatF & F, + const int d, + const igl::ARAPEnergyType energy, + Eigen::SparseMatrix & Kd); + // Helper functions for each energy type + template + IGL_INLINE void arap_linear_block_spokes( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd); + template + IGL_INLINE void arap_linear_block_spokes_and_rims( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd); + template + IGL_INLINE void arap_linear_block_elements( + const MatV & V, + const MatF & F, + const int d, + Eigen::SparseMatrix & Kd); +} + +#ifndef IGL_STATIC_LIBRARY +# include "arap_linear_block.cpp" +#endif + +#endif diff --git a/src/igl/arap_rhs.cpp b/src/igl/arap_rhs.cpp new file mode 100644 index 000000000..b46462168 --- /dev/null +++ b/src/igl/arap_rhs.cpp @@ -0,0 +1,89 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "arap_rhs.h" +#include "arap_linear_block.h" +#include "verbose.h" +#include "repdiag.h" +#include "cat.h" +#include + +IGL_INLINE void igl::arap_rhs( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const int dim, + const igl::ARAPEnergyType energy, + Eigen::SparseMatrix& K) +{ + using namespace std; + using namespace Eigen; + // Number of dimensions + int Vdim = V.cols(); + //// Number of mesh vertices + //int n = V.rows(); + //// Number of mesh elements + //int m = F.rows(); + //// number of rotations + //int nr; + switch(energy) + { + case ARAP_ENERGY_TYPE_SPOKES: + //nr = n; + break; + case ARAP_ENERGY_TYPE_SPOKES_AND_RIMS: + //nr = n; + break; + case ARAP_ENERGY_TYPE_ELEMENTS: + //nr = m; + break; + default: + fprintf( + stderr, + "arap_rhs.h: Error: Unsupported arap energy %d\n", + energy); + return; + } + + SparseMatrix KX,KY,KZ; + arap_linear_block(V,F,0,energy,KX); + arap_linear_block(V,F,1,energy,KY); + if(Vdim == 2) + { + K = cat(2,repdiag(KX,dim),repdiag(KY,dim)); + }else if(Vdim == 3) + { + arap_linear_block(V,F,2,energy,KZ); + if(dim == 3) + { + K = cat(2,cat(2,repdiag(KX,dim),repdiag(KY,dim)),repdiag(KZ,dim)); + }else if(dim ==2) + { + SparseMatrix ZZ(KX.rows()*2,KX.cols()); + K = cat(2,cat(2, + cat(2,repdiag(KX,dim),ZZ), + cat(2,repdiag(KY,dim),ZZ)), + cat(2,repdiag(KZ,dim),ZZ)); + }else + { + assert(false); + fprintf( + stderr, + "arap_rhs.h: Error: Unsupported dimension %d\n", + dim); + } + }else + { + assert(false); + fprintf( + stderr, + "arap_rhs.h: Error: Unsupported dimension %d\n", + Vdim); + return; + } + +} + diff --git a/src/igl/arap_rhs.h b/src/igl/arap_rhs.h new file mode 100644 index 000000000..5a1c370c1 --- /dev/null +++ b/src/igl/arap_rhs.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ARAP_RHS_H +#define IGL_ARAP_RHS_H +#include "igl_inline.h" + +#include +#include +#include + +namespace igl +{ + // ARAP_RHS build right-hand side constructor of global poisson solve for + // various Arap energies + // Inputs: + // V #V by Vdim list of initial domain positions + // F #F by 3 list of triangle indices into V + // dim dimension being used at solve time. For deformation usually dim = + // V.cols(), for surface parameterization V.cols() = 3 and dim = 2 + // energy igl::ARAPEnergyType enum value defining which energy is being + // used. See igl::ARAPEnergyType.h for valid options and explanations. + // Outputs: + // K #V*dim by #(F|V)*dim*dim matrix such that: + // b = K * reshape(permute(R,[3 1 2]),size(V|F,1)*size(V,2)*size(V,2),1); + // + // See also: arap_linear_block + IGL_INLINE void arap_rhs( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const int dim, + const igl::ARAPEnergyType energy, + Eigen::SparseMatrix& K); +} +#ifndef IGL_STATIC_LIBRARY +#include "arap_rhs.cpp" +#endif +#endif diff --git a/src/igl/average_onto_faces.cpp b/src/igl/average_onto_faces.cpp new file mode 100644 index 000000000..b5cec91ad --- /dev/null +++ b/src/igl/average_onto_faces.cpp @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "average_onto_faces.h" + +template +IGL_INLINE void igl::average_onto_faces(const Eigen::Matrix &V, + const Eigen::Matrix &F, + const Eigen::Matrix &S, + Eigen::Matrix &SF) +{ + + SF = Eigen::Matrix::Zero(F.rows(),S.cols()); + + for (int i = 0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AVERAGE_ONTO_FACES_H +#define IGL_AVERAGE_ONTO_FACES_H +#include "igl_inline.h" + +#include +namespace igl +{ + // average_onto_vertices + // Move a scalar field defined on faces to vertices by averaging + // + // Input: + // V,F: mesh + // S: scalar field defined on vertices, Vx1 + // + // Output: + // SV: scalar field defined on faces + template + IGL_INLINE void average_onto_faces( + const Eigen::Matrix &V, + const Eigen::Matrix &F, + const Eigen::Matrix &S, + Eigen::Matrix &SF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "average_onto_faces.cpp" +#endif + +#endif diff --git a/src/igl/average_onto_vertices.cpp b/src/igl/average_onto_vertices.cpp new file mode 100644 index 000000000..a30854edb --- /dev/null +++ b/src/igl/average_onto_vertices.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "average_onto_vertices.h" + +template +IGL_INLINE void igl::average_onto_vertices(const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + const Eigen::MatrixBase &S, + Eigen::MatrixBase &SV) +{ + SV = DerivedS::Zero(V.rows(),S.cols()); + Eigen::Matrix COUNT(V.rows()); + COUNT.setZero(); + for (int i = 0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AVERAGE_ONTO_VERTICES_H +#define IGL_AVERAGE_ONTO_VERTICES_H +#include "igl_inline.h" + +#include +namespace igl +{ + // average_onto_vertices + // Move a scalar field defined on faces to vertices by averaging + // + // Input: + // V,F: mesh + // S: scalar field defined on faces, Fx1 + // + // Output: + // SV: scalar field defined on vertices + template + IGL_INLINE void average_onto_vertices(const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + const Eigen::MatrixBase &S, + Eigen::MatrixBase &SV); +} + +#ifndef IGL_STATIC_LIBRARY +# include "average_onto_vertices.cpp" +#endif + +#endif diff --git a/src/igl/avg_edge_length.cpp b/src/igl/avg_edge_length.cpp new file mode 100644 index 000000000..b813d484a --- /dev/null +++ b/src/igl/avg_edge_length.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "avg_edge_length.h" + +#include + +template +IGL_INLINE double igl::avg_edge_length( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F) +{ + double avg = 0; + long int count = 0; + + // Augh. Technically this is double counting interior edges... + for (unsigned i=0;i, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template double igl::avg_edge_length, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +#endif diff --git a/src/igl/avg_edge_length.h b/src/igl/avg_edge_length.h new file mode 100644 index 000000000..613f8f5da --- /dev/null +++ b/src/igl/avg_edge_length.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AVERAGEEDGELENGTH_H +#define IGL_AVERAGEEDGELENGTH_H + +#include "igl_inline.h" +#include +#include +#include + +namespace igl +{ + // Compute the average edge length for the given triangle mesh + // Templates: + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // DerivedF derived from face indices matrix type: i.e. MatrixXi + // DerivedL derived from edge lengths matrix type: i.e. MatrixXd + // Inputs: + // V eigen matrix #V by 3 + // F #F by simplex-size list of mesh faces (must be simplex) + // Outputs: + // l average edge length + // + // See also: adjacency_matrix + template + IGL_INLINE double avg_edge_length( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "avg_edge_length.cpp" +#endif + +#endif diff --git a/src/igl/axis_angle_to_quat.cpp b/src/igl/axis_angle_to_quat.cpp new file mode 100644 index 000000000..009d0522c --- /dev/null +++ b/src/igl/axis_angle_to_quat.cpp @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "axis_angle_to_quat.h" +#include "EPS.h" +#include + +// http://www.antisphere.com/Wiki/tools:anttweakbar +template +IGL_INLINE void igl::axis_angle_to_quat( + const Q_type *axis, + const Q_type angle, + Q_type *out) +{ + Q_type n = axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2]; + if( fabs(n)>igl::EPS()) + { + Q_type f = 0.5*angle; + out[3] = cos(f); + f = sin(f)/sqrt(n); + out[0] = axis[0]*f; + out[1] = axis[1]*f; + out[2] = axis[2]*f; + } + else + { + out[3] = 1.0; + out[0] = out[1] = out[2] = 0.0; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::axis_angle_to_quat(double const*, double, double*); +// generated by autoexplicit.sh +template void igl::axis_angle_to_quat(float const*, float, float*); +#endif diff --git a/src/igl/axis_angle_to_quat.h b/src/igl/axis_angle_to_quat.h new file mode 100644 index 000000000..6533b1bec --- /dev/null +++ b/src/igl/axis_angle_to_quat.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_AXIS_ANGLE_TO_QUAT_H +#define IGL_AXIS_ANGLE_TO_QUAT_H +#include "igl_inline.h" + +namespace igl +{ + // Convert axis angle representation of a rotation to a quaternion + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // axis 3d vector + // angle scalar + // Outputs: + // quaternion + template + IGL_INLINE void axis_angle_to_quat( + const Q_type *axis, + const Q_type angle, + Q_type *out); +} + +#ifndef IGL_STATIC_LIBRARY +# include "axis_angle_to_quat.cpp" +#endif + +#endif diff --git a/src/igl/barycenter.cpp b/src/igl/barycenter.cpp new file mode 100644 index 000000000..065f82aab --- /dev/null +++ b/src/igl/barycenter.cpp @@ -0,0 +1,57 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "barycenter.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedBC> +IGL_INLINE void igl::barycenter( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & BC) +{ + BC.setZero(F.rows(),V.cols()); + // Loop over faces + for(int i = 0;i, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/barycenter.h b/src/igl/barycenter.h new file mode 100644 index 000000000..ef78e94a6 --- /dev/null +++ b/src/igl/barycenter.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BARYCENTER_H +#define IGL_BARYCENTER_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes the barycenter of every simplex + // + // Inputs: + // V #V x dim matrix of vertex coordinates + // F #F x simplex_size matrix of indices of simplex corners into V + // Output: + // BC #F x dim matrix of 3d vertices + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedBC> + IGL_INLINE void barycenter( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & BC); +} + +#ifndef IGL_STATIC_LIBRARY +# include "barycenter.cpp" +#endif + +#endif diff --git a/src/igl/barycentric_coordinates.cpp b/src/igl/barycentric_coordinates.cpp new file mode 100644 index 000000000..998a737bf --- /dev/null +++ b/src/igl/barycentric_coordinates.cpp @@ -0,0 +1,113 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "barycentric_coordinates.h" +#include "volume.h" + +template < + typename DerivedP, + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedD, + typename DerivedL> +IGL_INLINE void igl::barycentric_coordinates( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & L) +{ + using namespace Eigen; + assert(P.cols() == 3 && "query must be in 3d"); + assert(A.cols() == 3 && "corners must be in 3d"); + assert(B.cols() == 3 && "corners must be in 3d"); + assert(C.cols() == 3 && "corners must be in 3d"); + assert(D.cols() == 3 && "corners must be in 3d"); + assert(P.rows() == A.rows() && "Must have same number of queries as corners"); + assert(A.rows() == B.rows() && "Corners must be same size"); + assert(A.rows() == C.rows() && "Corners must be same size"); + assert(A.rows() == D.rows() && "Corners must be same size"); + typedef Matrix + VectorXS; + // Total volume + VectorXS vol,LA,LB,LC,LD; + volume(B,D,C,P,LA); + volume(A,C,D,P,LB); + volume(A,D,B,P,LC); + volume(A,B,C,P,LD); + volume(A,B,C,D,vol); + L.resize(P.rows(),4); + L< +IGL_INLINE void igl::barycentric_coordinates( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & L) +{ + using namespace Eigen; +#ifndef NDEBUG + const int DIM = P.cols(); + assert(A.cols() == DIM && "corners must be in same dimension as query"); + assert(B.cols() == DIM && "corners must be in same dimension as query"); + assert(C.cols() == DIM && "corners must be in same dimension as query"); + assert(P.rows() == A.rows() && "Must have same number of queries as corners"); + assert(A.rows() == B.rows() && "Corners must be same size"); + assert(A.rows() == C.rows() && "Corners must be same size"); +#endif + + // http://gamedev.stackexchange.com/a/23745 + typedef + Eigen::Array< + typename DerivedP::Scalar, + DerivedP::RowsAtCompileTime, + DerivedP::ColsAtCompileTime> + ArrayS; + typedef + Eigen::Array< + typename DerivedP::Scalar, + DerivedP::RowsAtCompileTime, + 1> + VectorS; + + const ArrayS v0 = B.array() - A.array(); + const ArrayS v1 = C.array() - A.array(); + const ArrayS v2 = P.array() - A.array(); + VectorS d00 = (v0*v0).rowwise().sum(); + VectorS d01 = (v0*v1).rowwise().sum(); + VectorS d11 = (v1*v1).rowwise().sum(); + VectorS d20 = (v2*v0).rowwise().sum(); + VectorS d21 = (v2*v1).rowwise().sum(); + VectorS denom = d00 * d11 - d01 * d01; + L.resize(P.rows(),3); + L.col(1) = (d11 * d20 - d01 * d21) / denom; + L.col(2) = (d00 * d21 - d01 * d20) / denom; + L.col(0) = 1.0f -(L.col(1) + L.col(2)).array(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::PlainObjectBase >&); +template void igl::barycentric_coordinates, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/barycentric_coordinates.h b/src/igl/barycentric_coordinates.h new file mode 100644 index 000000000..ec08669f2 --- /dev/null +++ b/src/igl/barycentric_coordinates.h @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BARYCENTRIC_COORDINATES_H +#define IGL_BARYCENTRIC_COORDINATES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute barycentric coordinates in a tet + // + // Inputs: + // P #P by 3 Query points in 3d + // A #P by 3 Tet corners in 3d + // B #P by 3 Tet corners in 3d + // C #P by 3 Tet corners in 3d + // D #P by 3 Tet corners in 3d + // Outputs: + // L #P by 4 list of barycentric coordinates + // + template < + typename DerivedP, + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedD, + typename DerivedL> + IGL_INLINE void barycentric_coordinates( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & L); + // Compute barycentric coordinates in a triangle + // + // Inputs: + // P #P by dim Query points + // A #P by dim Triangle corners + // B #P by dim Triangle corners + // C #P by dim Triangle corners + // Outputs: + // L #P by 3 list of barycentric coordinates + // + template < + typename DerivedP, + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedL> + IGL_INLINE void barycentric_coordinates( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & L); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "barycentric_coordinates.cpp" +#endif + +#endif diff --git a/src/igl/barycentric_to_global.cpp b/src/igl/barycentric_to_global.cpp new file mode 100755 index 000000000..e2656ad70 --- /dev/null +++ b/src/igl/barycentric_to_global.cpp @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "barycentric_to_global.h" + +// For error printing +#include +#include + +namespace igl +{ + template + IGL_INLINE Eigen::Matrix barycentric_to_global( + const Eigen::Matrix & V, + const Eigen::Matrix & F, + const Eigen::Matrix & bc) + { + Eigen::Matrix R; + R.resize(bc.rows(),3); + + for (unsigned i=0; i igl::barycentric_to_global(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +#endif diff --git a/src/igl/barycentric_to_global.h b/src/igl/barycentric_to_global.h new file mode 100755 index 000000000..466187173 --- /dev/null +++ b/src/igl/barycentric_to_global.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BARYCENTRIC2GLOBAL_H +#define IGL_BARYCENTRIC2GLOBAL_H +#include + +#include +#include + +namespace igl +{ + // Converts barycentric coordinates in the embree form to 3D coordinates + // Embree stores barycentric coordinates as triples: fid, bc1, bc2 + // fid is the id of a face, bc1 is the displacement of the point wrt the + // first vertex v0 and the edge v1-v0. Similarly, bc2 is the displacement + // wrt v2-v0. + // + // Input: + // V: #Vx3 Vertices of the mesh + // F: #Fxe Faces of the mesh + // bc: #Xx3 Barycentric coordinates, one row per point + // + // Output: + // #X: #Xx3 3D coordinates of all points in bc + template + IGL_INLINE Eigen::Matrix + barycentric_to_global( + const Eigen::Matrix & V, + const Eigen::Matrix & F, + const Eigen::Matrix & bc); +} + +#ifndef IGL_STATIC_LIBRARY +# include "barycentric_to_global.cpp" +#endif + +#endif diff --git a/src/igl/basename.cpp b/src/igl/basename.cpp new file mode 100644 index 000000000..ae4fb118e --- /dev/null +++ b/src/igl/basename.cpp @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "basename.h" + +#include + +IGL_INLINE std::string igl::basename(const std::string & path) +{ + if(path == "") + { + return std::string(""); + } + // http://stackoverflow.com/questions/5077693/dirnamephp-similar-function-in-c + std::string::const_reverse_iterator last_slash = + std::find( + path.rbegin(), + path.rend(), '/'); + if( last_slash == path.rend() ) + { + // No slashes found + return path; + }else if(1 == (last_slash.base() - path.begin())) + { + // Slash is first char + return std::string(path.begin()+1,path.end()); + }else if(path.end() == last_slash.base() ) + { + // Slash is last char + std::string redo = std::string(path.begin(),path.end()-1); + return igl::basename(redo); + } + return std::string(last_slash.base(),path.end()); +} diff --git a/src/igl/basename.h b/src/igl/basename.h new file mode 100644 index 000000000..fb333b5c4 --- /dev/null +++ b/src/igl/basename.h @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BASENAME_H +#define IGL_BASENAME_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Function like PHP's basename: /etc/sudoers.d --> sudoers.d + // Input: + // path string containing input path + // Returns string containing basename (see php's basename) + // + // See also: dirname, pathinfo + IGL_INLINE std::string basename(const std::string & path); +} + +#ifndef IGL_STATIC_LIBRARY +# include "basename.cpp" +#endif + +#endif diff --git a/src/igl/bbw.cpp b/src/igl/bbw.cpp new file mode 100644 index 000000000..c7df7dd3b --- /dev/null +++ b/src/igl/bbw.cpp @@ -0,0 +1,143 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bbw.h" +#include "min_quad_with_fixed.h" +#include "harmonic.h" +#include "parallel_for.h" +#include +#include +#include +#include + +igl::BBWData::BBWData(): + partition_unity(false), + W0(), + active_set_params(), + verbosity(0) +{ + // We know that the Bilaplacian is positive semi-definite + active_set_params.Auu_pd = true; +} + +void igl::BBWData::print() +{ + using namespace std; + cout<<"partition_unity: "< +IGL_INLINE bool igl::bbw( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + const Eigen::PlainObjectBase & b, + const Eigen::PlainObjectBase & bc, + igl::BBWData & data, + Eigen::PlainObjectBase & W + ) +{ + using namespace std; + using namespace Eigen; + assert(!data.partition_unity && "partition_unity not implemented yet"); + // number of domain vertices + int n = V.rows(); + // number of handles + int m = bc.cols(); + // Build biharmonic operator + Eigen::SparseMatrix Q; + harmonic(V,Ele,2,Q); + W.derived().resize(n,m); + // No linear terms + VectorXd c = VectorXd::Zero(n); + // No linear constraints + SparseMatrix A(0,n),Aeq(0,n),Aieq(0,n); + VectorXd Beq(0,1),Bieq(0,1); + // Upper and lower box constraints (Constant bounds) + VectorXd ux = VectorXd::Ones(n); + VectorXd lx = VectorXd::Zero(n); + active_set_params eff_params = data.active_set_params; + if(data.verbosity >= 1) + { + cout<<"BBW: max_iter: "<= 1) + { + cout<<"BBW: Computing initial weights for "< mqwf; + min_quad_with_fixed_precompute(Q,b,Aeq,true,mqwf); + min_quad_with_fixed_solve(mqwf,c,bc,Beq,W); + // decrement + eff_params.max_iter--; + bool error = false; + // Loop over handles + std::mutex critical; + const auto & optimize_weight = [&](const int i) + { + // Quicker exit for paralle_for + if(error) + { + return; + } + if(data.verbosity >= 1) + { + std::lock_guard lock(critical); + cout<<"BBW: Computing weight for handle "<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::BBWData&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/bbw.h b/src/igl/bbw.h new file mode 100644 index 000000000..bec20bb98 --- /dev/null +++ b/src/igl/bbw.h @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BBW_H +#define IGL_BBW_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Container for BBW computation related data and flags + class BBWData + { + public: + // Enforce partition of unity during optimization (optimize all weight + // simultaneously) + bool partition_unity; + // Initial guess + Eigen::MatrixXd W0; + igl::active_set_params active_set_params; + // Verbosity level + // 0: quiet + // 1: loud + // 2: louder + int verbosity; + public: + IGL_INLINE BBWData(); + // Print current state of object + IGL_INLINE void print(); + }; + + // Compute Bounded Biharmonic Weights on a given domain (V,Ele) with a given + // set of boundary conditions + // + // Templates + // DerivedV derived type of eigen matrix for V (e.g. MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. MatrixXi) + // Derivedb derived type of eigen matrix for b (e.g. VectorXi) + // Derivedbc derived type of eigen matrix for bc (e.g. MatrixXd) + // DerivedW derived type of eigen matrix for W (e.g. MatrixXd) + // Inputs: + // V #V by dim vertex positions + // Ele #Elements by simplex-size list of element indices + // b #b boundary indices into V + // bc #b by #W list of boundary values + // data object containing options, initial guess --> solution and results + // Outputs: + // W #V by #W list of *unnormalized* weights to normalize use + // igl::normalize_row_sums(W,W); + // Returns true on success, false on failure + template < + typename DerivedV, + typename DerivedEle, + typename Derivedb, + typename Derivedbc, + typename DerivedW> + IGL_INLINE bool bbw( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + const Eigen::PlainObjectBase & b, + const Eigen::PlainObjectBase & bc, + BBWData & data, + Eigen::PlainObjectBase & W); +} + +#ifndef IGL_STATIC_LIBRARY +# include "bbw.cpp" +#endif + +#endif + diff --git a/src/igl/bfs.cpp b/src/igl/bfs.cpp new file mode 100644 index 000000000..8ff51c050 --- /dev/null +++ b/src/igl/bfs.cpp @@ -0,0 +1,93 @@ +#include "bfs.h" +#include "list_to_matrix.h" +#include +#include + +template < + typename AType, + typename DerivedD, + typename DerivedP> +IGL_INLINE void igl::bfs( + const AType & A, + const size_t s, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & P) +{ + std::vector vD; + std::vector vP; + bfs(A,s,vD,vP); + list_to_matrix(vD,D); + list_to_matrix(vP,P); +} + +template < + typename AType, + typename DType, + typename PType> +IGL_INLINE void igl::bfs( + const std::vector > & A, + const size_t s, + std::vector & D, + std::vector & P) +{ + // number of nodes + int N = s+1; + for(const auto & Ai : A) for(const auto & a : Ai) N = std::max(N,a+1); + std::vector seen(N,false); + P.resize(N,-1); + std::queue > Q; + Q.push({s,-1}); + while(!Q.empty()) + { + const int f = Q.front().first; + const int p = Q.front().second; + Q.pop(); + if(seen[f]) + { + continue; + } + D.push_back(f); + P[f] = p; + seen[f] = true; + for(const auto & n : A[f]) Q.push({n,f}); + } +} + + +template < + typename AType, + typename DType, + typename PType> +IGL_INLINE void igl::bfs( + const Eigen::SparseMatrix & A, + const size_t s, + std::vector & D, + std::vector & P) +{ + // number of nodes + int N = A.rows(); + assert(A.rows() == A.cols()); + std::vector seen(N,false); + P.resize(N,-1); + std::queue > Q; + Q.push({s,-1}); + while(!Q.empty()) + { + const int f = Q.front().first; + const int p = Q.front().second; + Q.pop(); + if(seen[f]) + { + continue; + } + D.push_back(f); + P[f] = p; + seen[f] = true; + for(typename Eigen::SparseMatrix::InnerIterator it (A,f); it; ++it) + { + if(it.value()) Q.push({it.index(),f}); + } + } + +} + diff --git a/src/igl/bfs.h b/src/igl/bfs.h new file mode 100644 index 000000000..6f49e93eb --- /dev/null +++ b/src/igl/bfs.h @@ -0,0 +1,54 @@ +#ifndef IGL_BFS_H +#define IGL_BFS_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Traverse a **directed** graph represented by an adjacency list using + // breadth first search + // + // Inputs: + // A #V list of adjacency lists or #V by #V adjacency matrix + // s starting node (index into A) + // Outputs: + // D #V list of indices into rows of A in the order in which graph nodes + // are discovered. + // P #V list of indices into rows of A of predecessor in resulting + // spanning tree {-1 indicates root/not discovered), order corresponds to + // V **not** D. + template < + typename AType, + typename DerivedD, + typename DerivedP> + IGL_INLINE void bfs( + const AType & A, + const size_t s, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & P); + + template < + typename AType, + typename DType, + typename PType> + IGL_INLINE void bfs( + const std::vector > & A, + const size_t s, + std::vector & D, + std::vector & P); + template < + typename AType, + typename DType, + typename PType> + IGL_INLINE void bfs( + const Eigen::SparseMatrix & A, + const size_t s, + std::vector & D, + std::vector & P); +} +#ifndef IGL_STATIC_LIBRARY +# include "bfs.cpp" +#endif +#endif + diff --git a/src/igl/bfs_orient.cpp b/src/igl/bfs_orient.cpp new file mode 100644 index 000000000..23dd93a49 --- /dev/null +++ b/src/igl/bfs_orient.cpp @@ -0,0 +1,100 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bfs_orient.h" +#include "orientable_patches.h" +#include +#include + +template +IGL_INLINE void igl::bfs_orient( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & C) +{ + using namespace Eigen; + using namespace std; + SparseMatrix A; + orientable_patches(F,C,A); + + // number of faces + const int m = F.rows(); + // number of patches + const int num_cc = C.maxCoeff()+1; + VectorXi seen = VectorXi::Zero(m); + + // Edge sets + const int ES[3][2] = {{1,2},{2,0},{0,1}}; + + if(&FF != &F) + { + FF = F; + } + // loop over patches +#pragma omp parallel for + for(int c = 0;c Q; + // find first member of patch c + for(int f = 0;f 0) + { + continue; + } + seen(f)++; + // loop over neighbors of f + for(typename SparseMatrix::InnerIterator it (A,f); it; ++it) + { + // might be some lingering zeros, and skip self-adjacency + if(it.value() != 0 && it.row() != f) + { + const int n = it.row(); + assert(n != f); + // loop over edges of f + for(int efi = 0;efi<3;efi++) + { + // efi'th edge of face f + Vector2i ef(FF(f,ES[efi][0]),FF(f,ES[efi][1])); + // loop over edges of n + for(int eni = 0;eni<3;eni++) + { + // eni'th edge of face n + Vector2i en(FF(n,ES[eni][0]),FF(n,ES[eni][1])); + // Match (half-edges go same direction) + if(ef(0) == en(0) && ef(1) == en(1)) + { + // flip face n + FF.row(n) = FF.row(n).reverse().eval(); + } + } + } + // add neighbor to queue + Q.push(n); + } + } + } + } + + // make sure flip is OK if &FF = &F +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::bfs_orient, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/bfs_orient.h b/src/igl/bfs_orient.h new file mode 100644 index 000000000..b37f397d8 --- /dev/null +++ b/src/igl/bfs_orient.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BFS_ORIENT_H +#define IGL_BFS_ORIENT_H +#include +#include + +namespace igl +{ + // Consistently orient faces in orientable patches using BFS + // + // F = bfs_orient(F,V); + // + // Inputs: + // F #F by 3 list of faces + // Outputs: + // FF #F by 3 list of faces (OK if same as F) + // C #F list of component ids + // + // + template + IGL_INLINE void bfs_orient( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & C); +}; +#ifndef IGL_STATIC_LIBRARY +# include "bfs_orient.cpp" +#endif + +#endif diff --git a/src/igl/biharmonic_coordinates.cpp b/src/igl/biharmonic_coordinates.cpp new file mode 100644 index 000000000..686ecd5e4 --- /dev/null +++ b/src/igl/biharmonic_coordinates.cpp @@ -0,0 +1,203 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "biharmonic_coordinates.h" +#include "cotmatrix.h" +#include "sum.h" +#include "massmatrix.h" +#include "min_quad_with_fixed.h" +#include "crouzeix_raviart_massmatrix.h" +#include "crouzeix_raviart_cotmatrix.h" +#include "normal_derivative.h" +#include "on_boundary.h" +#include + +template < + typename DerivedV, + typename DerivedT, + typename SType, + typename DerivedW> +IGL_INLINE bool igl::biharmonic_coordinates( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const std::vector > & S, + Eigen::PlainObjectBase & W) +{ + return biharmonic_coordinates(V,T,S,2,W); +} + +template < + typename DerivedV, + typename DerivedT, + typename SType, + typename DerivedW> +IGL_INLINE bool igl::biharmonic_coordinates( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const std::vector > & S, + const int k, + Eigen::PlainObjectBase & W) +{ + using namespace Eigen; + using namespace std; + // This is not the most efficient way to build A, but follows "Linear + // Subspace Design for Real-Time Shape Deformation" [Wang et al. 2015]. + SparseMatrix A; + { + DiagonalMatrix Minv; + SparseMatrix L,K; + Array C; + { + Array I; + on_boundary(T,I,C); + } +#ifdef false + // Version described in paper is "wrong" + // http://www.cs.toronto.edu/~jacobson/images/error-in-linear-subspace-design-for-real-time-shape-deformation-2017-wang-et-al.pdf + SparseMatrix N,Z,M; + normal_derivative(V,T,N); + { + std::vector >ZIJV; + for(int t =0;t M; + Eigen::MatrixXi E; + Eigen::VectorXi EMAP; + crouzeix_raviart_massmatrix(V,T,M,E,EMAP); + crouzeix_raviart_cotmatrix(V,T,E,EMAP,L); + // Ad #E by #V facet-vertex incidence matrix + Eigen::SparseMatrix Ad(E.rows(),V.rows()); + { + std::vector > AIJV(E.size()); + for(int e = 0;e(e,E(e,c),1); + } + } + Ad.setFromTriplets(AIJV.begin(),AIJV.end()); + } + // Degrees + Eigen::VectorXd De; + sum(Ad,2,De); + Eigen::DiagonalMatrix De_diag = + De.array().inverse().matrix().asDiagonal(); + K = L*(De_diag*Ad); + // normalize + M /= ((VectorXd)M.diagonal()).array().abs().maxCoeff(); + Minv = ((VectorXd)M.diagonal().array().inverse()).asDiagonal(); + // kill boundary edges + for(int f = 0;f & h){return h.size()==1;}); + // number of region handles + const size_t r = S.size()-mp; + // Vertices in region handles + size_t mr = 0; + for(const auto & h : S) + { + if(h.size() > 1) + { + mr += h.size(); + } + } + const size_t dim = T.cols()-1; + // Might as well be dense... I think... + MatrixXd J = MatrixXd::Zero(mp+mr,mp+r*(dim+1)); + VectorXi b(mp+mr); + MatrixXd H(mp+r*(dim+1),dim); + { + int v = 0; + int c = 0; + for(int h = 0;h= dim+1); + for(int p = 0;p(),VectorXd(),true,W); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::biharmonic_coordinates, Eigen::Matrix, int, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/biharmonic_coordinates.h b/src/igl/biharmonic_coordinates.h new file mode 100644 index 000000000..26f67329a --- /dev/null +++ b/src/igl/biharmonic_coordinates.h @@ -0,0 +1,90 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BIHARMONIC_COORDINATES_H +#define IGL_BIHARMONIC_COORDINATES_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Compute "discrete biharmonic generalized barycentric coordinates" as + // described in "Linear Subspace Design for Real-Time Shape Deformation" + // [Wang et al. 2015]. Not to be confused with "Bounded Biharmonic Weights + // for Real-Time Deformation" [Jacobson et al. 2011] or "Biharmonic + // Coordinates" (2D complex barycentric coordinates) [Weber et al. 2012]. + // These weights minimize a discrete version of the squared Laplacian energy + // subject to positional interpolation constraints at selected vertices + // (point handles) and transformation interpolation constraints at regions + // (region handles). + // + // Templates: + // HType should be a simple index type e.g. `int`,`size_t` + // Inputs: + // V #V by dim list of mesh vertex positions + // T #T by dim+1 list of / triangle indices into V if dim=2 + // \ tetrahedron indices into V if dim=3 + // S #point-handles+#region-handles list of lists of selected vertices for + // each handle. Point handles should have singleton lists and region + // handles should have lists of size at least dim+1 (and these points + // should be in general position). + // Outputs: + // W #V by #points-handles+(#region-handles * dim+1) matrix of weights so + // that columns correspond to each handles generalized barycentric + // coordinates (for point-handles) or animation space weights (for region + // handles). + // returns true only on success + // + // Example: + // + // MatrixXd W; + // igl::biharmonic_coordinates(V,F,S,W); + // const size_t dim = T.cols()-1; + // MatrixXd H(W.cols(),dim); + // { + // int c = 0; + // for(int h = 0;h + IGL_INLINE bool biharmonic_coordinates( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const std::vector > & S, + Eigen::PlainObjectBase & W); + // k 2-->biharmonic, 3-->triharmonic + template < + typename DerivedV, + typename DerivedT, + typename SType, + typename DerivedW> + IGL_INLINE bool biharmonic_coordinates( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const std::vector > & S, + const int k, + Eigen::PlainObjectBase & W); + +}; +# ifndef IGL_STATIC_LIBRARY +# include "biharmonic_coordinates.cpp" +# endif +#endif diff --git a/src/igl/bijective_composite_harmonic_mapping.cpp b/src/igl/bijective_composite_harmonic_mapping.cpp new file mode 100644 index 000000000..517109c04 --- /dev/null +++ b/src/igl/bijective_composite_harmonic_mapping.cpp @@ -0,0 +1,115 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bijective_composite_harmonic_mapping.h" + +#include "slice.h" +#include "doublearea.h" +#include "harmonic.h" +//#include "matlab/MatlabWorkspace.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedU> +IGL_INLINE bool igl::bijective_composite_harmonic_mapping( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + Eigen::PlainObjectBase & U) +{ + return bijective_composite_harmonic_mapping(V,F,b,bc,1,200,20,true,U); +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedU> +IGL_INLINE bool igl::bijective_composite_harmonic_mapping( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int min_steps, + const int max_steps, + const int num_inner_iters, + const bool test_for_flips, + Eigen::PlainObjectBase & U) +{ + typedef typename Derivedbc::Scalar Scalar; + assert(V.cols() == 2 && bc.cols() == 2 && "Input should be 2D"); + assert(F.cols() == 3 && "F should contain triangles"); + int tries = 0; + int nsteps = min_steps; + Derivedbc bc0; + slice(V,b,1,bc0); + + // It's difficult to check for flips "robustly" in the sense that the input + // mesh might not have positive/consistent sign to begin with. + + while(nsteps<=max_steps) + { + U = V; + int flipped = 0; + int nans = 0; + int step = 0; + for(;step<=nsteps;step++) + { + const Scalar t = ((Scalar)step)/((Scalar)nsteps); + // linearly interpolate boundary conditions + // TODO: replace this with something that guarantees a homotopic "morph" + // of the boundary conditions. Something like "Homotopic Morphing of + // Planar Curves" [Dym et al. 2015] but also handling multiple connected + // components. + Derivedbc bct = bc0 + t*(bc - bc0); + // Compute dsicrete harmonic map using metric of previous step + for(int iter = 0;iter A; + doublearea(U,F,A); + flipped = (A.array() < 0 ).count(); + //std::cout<<" "< 0 || nans>0) break; + } + if(flipped == 0 && nans == 0) + { + return step == nsteps+1; + } + nsteps *= 2; + } + //std::cout<<"failed to finish in "<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::bijective_composite_harmonic_mapping, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, int, int, bool, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/bijective_composite_harmonic_mapping.h b/src/igl/bijective_composite_harmonic_mapping.h new file mode 100644 index 000000000..f055e5293 --- /dev/null +++ b/src/igl/bijective_composite_harmonic_mapping.h @@ -0,0 +1,79 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BIJECTIVE_COMPOSITE_HARMONIC_MAPPING_H +#define IGL_BIJECTIVE_COMPOSITE_HARMONIC_MAPPING_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Compute a planar mapping of a triangulated polygon (V,F) subjected to + // boundary conditions (b,bc). The mapping should be bijective in the sense + // that no triangles' areas become negative (this assumes they started + // positive). This mapping is computed by "composing" harmonic mappings + // between incremental morphs of the boundary conditions. This is a bit like + // a discrete version of "Bijective Composite Mean Value Mappings" [Schneider + // et al. 2013] but with a discrete harmonic map (cf. harmonic coordinates) + // instead of mean value coordinates. This is inspired by "Embedding a + // triangular graph within a given boundary" [Xu et al. 2011]. + // + // Inputs: + // V #V by 2 list of triangle mesh vertex positions + // F #F by 3 list of triangle indices into V + // b #b list of boundary indices into V + // bc #b by 2 list of boundary conditions corresponding to b + // Outputs: + // U #V by 2 list of output mesh vertex locations + // Returns true if and only if U contains a successful bijectie mapping + // + // + template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedU> + IGL_INLINE bool bijective_composite_harmonic_mapping( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + Eigen::PlainObjectBase & U); + // + // Inputs: + // min_steps minimum number of steps to take from V(b,:) to bc + // max_steps minimum number of steps to take from V(b,:) to bc (if + // max_steps == min_steps then no further number of steps will be tried) + // num_inner_iters number of iterations of harmonic solves to run after + // for each morph step (to try to push flips back in) + // test_for_flips whether to check if flips occurred (and trigger more + // steps). if test_for_flips = false then this function always returns + // true + // + template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedU> + IGL_INLINE bool bijective_composite_harmonic_mapping( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int min_steps, + const int max_steps, + const int num_inner_iters, + const bool test_for_flips, + Eigen::PlainObjectBase & U); +} + +#ifndef IGL_STATIC_LIBRARY +# include "bijective_composite_harmonic_mapping.cpp" +#endif +#endif diff --git a/src/igl/bone_parents.cpp b/src/igl/bone_parents.cpp new file mode 100644 index 000000000..d5cd6ddfa --- /dev/null +++ b/src/igl/bone_parents.cpp @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bone_parents.h" + +template +IGL_INLINE void igl::bone_parents( + const Eigen::PlainObjectBase& BE, + Eigen::PlainObjectBase& P) +{ + P.resize(BE.rows(),1); + // Stupid O(n²) version + for(int e = 0;e, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/bone_parents.h b/src/igl/bone_parents.h new file mode 100644 index 000000000..e0091cdfc --- /dev/null +++ b/src/igl/bone_parents.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BONE_PARENTS_H +#define IGL_BONE_PARENTS_H +#include "igl_inline.h" +#include +namespace igl +{ + // BONE_PARENTS Recover "parent" bones from directed graph representation. + // + // Inputs: + // BE #BE by 2 list of directed bone edges + // Outputs: + // P #BE by 1 list of parent indices into BE, -1 means root. + // + template + IGL_INLINE void bone_parents( + const Eigen::PlainObjectBase& BE, + Eigen::PlainObjectBase& P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "bone_parents.cpp" +#endif + +#endif + diff --git a/src/igl/boundary_conditions.cpp b/src/igl/boundary_conditions.cpp new file mode 100644 index 000000000..f7146cd70 --- /dev/null +++ b/src/igl/boundary_conditions.cpp @@ -0,0 +1,192 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "boundary_conditions.h" + +#include "verbose.h" +#include "EPS.h" +#include "project_to_line.h" + +#include +#include +#include + +IGL_INLINE bool igl::boundary_conditions( + const Eigen::MatrixXd & V , + const Eigen::MatrixXi & /*Ele*/, + const Eigen::MatrixXd & C , + const Eigen::VectorXi & P , + const Eigen::MatrixXi & BE , + const Eigen::MatrixXi & CE , + Eigen::VectorXi & b , + Eigen::MatrixXd & bc ) +{ + using namespace Eigen; + using namespace std; + + if(P.size()+BE.rows() == 0) + { + verbose("^%s: Error: no handles found\n",__FUNCTION__); + return false; + } + + vector bci; + vector bcj; + vector bcv; + + // loop over points + for(int p = 0;p FLOAT_EPS) + { + verbose("^%s: Error: handle %d does not receive 0 weight\n",__FUNCTION__,i); + return false; + } + if(max_c< (1-FLOAT_EPS)) + { + verbose("^%s: Error: handle %d does not receive 1 weight\n",__FUNCTION__,i); + return false; + } + } + + return true; +} diff --git a/src/igl/boundary_conditions.h b/src/igl/boundary_conditions.h new file mode 100644 index 000000000..643dbe7c1 --- /dev/null +++ b/src/igl/boundary_conditions.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BOUNDARY_CONDITIONS_H +#define IGL_BOUNDARY_CONDITIONS_H +#include "igl_inline.h" +#include + +namespace igl +{ + + // Compute boundary conditions for automatic weights computation. This + // function expects that the given mesh (V,Ele) has sufficient samples + // (vertices) exactly at point handle locations and exactly along bone and + // cage edges. + // + // Inputs: + // V #V by dim list of domain vertices + // Ele #Ele by simplex-size list of simplex indices + // C #C by dim list of handle positions + // P #P by 1 list of point handle indices into C + // BE #BE by 2 list of bone edge indices into C + // CE #CE by 2 list of cage edge indices into *P* + // Outputs: + // b #b list of boundary indices (indices into V of vertices which have + // known, fixed values) + // bc #b by #weights list of known/fixed values for boundary vertices + // (notice the #b != #weights in general because #b will include all the + // intermediary samples along each bone, etc.. The ordering of the + // weights corresponds to [P;BE] + // Returns false if boundary conditions are suspicious: + // P and BE are empty + // bc is empty + // some column of bc doesn't have a 0 (assuming bc has >1 columns) + // some column of bc doesn't have a 1 (assuming bc has >1 columns) + IGL_INLINE bool boundary_conditions( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & Ele, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & P, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + Eigen::VectorXi & b, + Eigen::MatrixXd & bc); +} + +#ifndef IGL_STATIC_LIBRARY +# include "boundary_conditions.cpp" +#endif + +#endif diff --git a/src/igl/boundary_facets.cpp b/src/igl/boundary_facets.cpp new file mode 100644 index 000000000..67e74db4f --- /dev/null +++ b/src/igl/boundary_facets.cpp @@ -0,0 +1,141 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "boundary_facets.h" +#include "face_occurrences.h" + +// IGL includes +#include "sort.h" + +// STL includes +#include +#include + +template +IGL_INLINE void igl::boundary_facets( + const std::vector > & T, + std::vector > & F) +{ + using namespace std; + + if(T.size() == 0) + { + F.clear(); + return; + } + + int simplex_size = T[0].size(); + // Get a list of all faces + vector > allF( + T.size()*simplex_size, + vector(simplex_size-1)); + + // Gather faces, loop over tets + for(int i = 0; i< (int)T.size();i++) + { + assert((int)T[i].size() == simplex_size); + switch(simplex_size) + { + case 4: + // get face in correct order + allF[i*simplex_size+0][0] = T[i][1]; + allF[i*simplex_size+0][1] = T[i][3]; + allF[i*simplex_size+0][2] = T[i][2]; + // get face in correct order + allF[i*simplex_size+1][0] = T[i][0]; + allF[i*simplex_size+1][1] = T[i][2]; + allF[i*simplex_size+1][2] = T[i][3]; + // get face in correct order + allF[i*simplex_size+2][0] = T[i][0]; + allF[i*simplex_size+2][1] = T[i][3]; + allF[i*simplex_size+2][2] = T[i][1]; + // get face in correct order + allF[i*simplex_size+3][0] = T[i][0]; + allF[i*simplex_size+3][1] = T[i][1]; + allF[i*simplex_size+3][2] = T[i][2]; + break; + case 3: + allF[i*simplex_size+0][0] = T[i][1]; + allF[i*simplex_size+0][1] = T[i][2]; + allF[i*simplex_size+1][0] = T[i][2]; + allF[i*simplex_size+1][1] = T[i][0]; + allF[i*simplex_size+2][0] = T[i][0]; + allF[i*simplex_size+2][1] = T[i][1]; + break; + } + } + + // Counts + vector C; + face_occurrences(allF,C); + + // Q: Why not just count the number of ones? + // A: because we are including non-manifold edges as boundary edges + int twos = (int) count(C.begin(),C.end(),2); + //int ones = (int) count(C.begin(),C.end(),1); + // Resize output to fit number of ones + F.resize(allF.size() - twos); + //F.resize(ones); + int k = 0; + for(int i = 0;i< (int)allF.size();i++) + { + if(C[i] != 2) + { + assert(k<(int)F.size()); + F[k] = allF[i]; + k++; + } + } + assert(k==(int)F.size()); + //if(k != F.size()) + //{ + // printf("%d =? %d\n",k,F.size()); + //} + +} + +#include "list_to_matrix.h" +#include "matrix_to_list.h" + +template +IGL_INLINE void igl::boundary_facets( + const Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F) +{ + assert(T.cols() == 0 || T.cols() == 4 || T.cols() == 3); + using namespace std; + using namespace Eigen; + // Cop out: use vector of vectors version + vector > vT; + matrix_to_list(T,vT); + vector > vF; + boundary_facets(vT,vF); + list_to_matrix(vF,F); +} + +template +Ret igl::boundary_facets( + const Eigen::PlainObjectBase& T) +{ + Ret F; + igl::boundary_facets(T,F); + return F; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::boundary_facets, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::boundary_facets, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::boundary_facets, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::boundary_facets(std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&); +//template Eigen::PlainObjectBase > igl::boundary_facets(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::boundary_facets, Eigen::Matrix >(Eigen::PlainObjectBase > const&); +template void igl::boundary_facets, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/boundary_facets.h b/src/igl/boundary_facets.h new file mode 100644 index 000000000..74ac032a1 --- /dev/null +++ b/src/igl/boundary_facets.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BOUNDARY_FACETS_H +#define IGL_BOUNDARY_FACETS_H +#include "igl_inline.h" + +#include + +#include + +namespace igl +{ + // BOUNDARY_FACETS Determine boundary faces (edges) of tetrahedra (triangles) + // stored in T (analogous to qptoolbox's `outline` and `boundary_faces`). + // + // Templates: + // IntegerT integer-value: e.g. int + // IntegerF integer-value: e.g. int + // Input: + // T tetrahedron (triangle) index list, m by 4 (3), where m is the number of tetrahedra + // Output: + // F list of boundary faces, n by 3 (2), where n is the number of boundary faces + // + // + template + IGL_INLINE void boundary_facets( + const std::vector > & T, + std::vector > & F); + + // Templates: + // DerivedT integer-value: i.e. from MatrixXi + // DerivedF integer-value: i.e. from MatrixXi + template + IGL_INLINE void boundary_facets( + const Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F); + // Same as above but returns F + template + Ret boundary_facets( + const Eigen::PlainObjectBase& T); +} + +#ifndef IGL_STATIC_LIBRARY +# include "boundary_facets.cpp" +#endif + +#endif diff --git a/src/igl/boundary_loop.cpp b/src/igl/boundary_loop.cpp new file mode 100755 index 000000000..9a3ec625e --- /dev/null +++ b/src/igl/boundary_loop.cpp @@ -0,0 +1,153 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "boundary_loop.h" +#include "slice.h" +#include "triangle_triangle_adjacency.h" +#include "vertex_triangle_adjacency.h" +#include "is_border_vertex.h" +#include + +template +IGL_INLINE void igl::boundary_loop( + const Eigen::PlainObjectBase & F, + std::vector >& L) +{ + using namespace std; + using namespace Eigen; + + if(F.rows() == 0) + return; + + VectorXd Vdummy(F.maxCoeff()+1,1); + DerivedF TT,TTi; + vector > VF, VFi; + triangle_triangle_adjacency(F,TT,TTi); + vertex_triangle_adjacency(Vdummy,F,VF,VFi); + + vector unvisited = is_border_vertex(Vdummy,F); + set unseen; + for (size_t i = 0; i < unvisited.size(); ++i) + { + if (unvisited[i]) + unseen.insert(unseen.end(),i); + } + + while (!unseen.empty()) + { + vector l; + + // Get first vertex of loop + int start = *unseen.begin(); + unseen.erase(unseen.begin()); + unvisited[start] = false; + l.push_back(start); + + bool done = false; + while (!done) + { + // Find next vertex + bool newBndEdge = false; + int v = l[l.size()-1]; + int next; + for (int i = 0; i < (int)VF[v].size() && !newBndEdge; i++) + { + int fid = VF[v][i]; + + if (TT.row(fid).minCoeff() < 0.) // Face contains boundary edge + { + int vLoc = -1; + if (F(fid,0) == v) vLoc = 0; + if (F(fid,1) == v) vLoc = 1; + if (F(fid,2) == v) vLoc = 2; + + int vNext = F(fid,(vLoc + 1) % F.cols()); + + newBndEdge = false; + if (unvisited[vNext] && TT(fid,vLoc) < 0) + { + next = vNext; + newBndEdge = true; + } + } + } + + if (newBndEdge) + { + l.push_back(next); + unseen.erase(next); + unvisited[next] = false; + } + else + done = true; + } + L.push_back(l); + } +} + +template +IGL_INLINE void igl::boundary_loop( + const Eigen::PlainObjectBase& F, + std::vector& L) +{ + using namespace Eigen; + using namespace std; + + if(F.rows() == 0) + return; + + vector > Lall; + boundary_loop(F,Lall); + + int idxMax = -1; + size_t maxLen = 0; + for (size_t i = 0; i < Lall.size(); ++i) + { + if (Lall[i].size() > maxLen) + { + maxLen = Lall[i].size(); + idxMax = i; + } + } + + //Check for meshes without boundary + if (idxMax == -1) + { + L.clear(); + return; + } + + L.resize(Lall[idxMax].size()); + for (size_t i = 0; i < Lall[idxMax].size(); ++i) + { + L[i] = Lall[idxMax][i]; + } +} + +template +IGL_INLINE void igl::boundary_loop( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& L) +{ + using namespace Eigen; + using namespace std; + + if(F.rows() == 0) + return; + + vector Lvec; + boundary_loop(F,Lvec); + + L.resize(Lvec.size()); + for (size_t i = 0; i < Lvec.size(); ++i) + L(i) = Lvec[i]; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::boundary_loop, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/boundary_loop.h b/src/igl/boundary_loop.h new file mode 100755 index 000000000..ebb5072a5 --- /dev/null +++ b/src/igl/boundary_loop.h @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BOUNDARY_LOOP_H +#define IGL_BOUNDARY_LOOP_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Compute list of ordered boundary loops for a manifold mesh. + // + // Templates: + // Index index type + // Inputs: + // F #V by dim list of mesh faces + // Outputs: + // L list of loops where L[i] = ordered list of boundary vertices in loop i + // + template + IGL_INLINE void boundary_loop( + const Eigen::PlainObjectBase& F, + std::vector >& L); + + + // Compute ordered boundary loops for a manifold mesh and return the + // longest loop in terms of vertices. + // + // Templates: + // Index index type + // Inputs: + // F #V by dim list of mesh faces + // Outputs: + // L ordered list of boundary vertices of longest boundary loop + // + template + IGL_INLINE void boundary_loop( + const Eigen::PlainObjectBase& F, + std::vector& L); + + // Compute ordered boundary loops for a manifold mesh and return the + // longest loop in terms of vertices. + // + // Templates: + // Index index type + // Inputs: + // F #V by dim list of mesh faces + // Outputs: + // L ordered list of boundary vertices of longest boundary loop + // + template + IGL_INLINE void boundary_loop( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& L); +} + +#ifndef IGL_STATIC_LIBRARY +# include "boundary_loop.cpp" +#endif +#endif diff --git a/src/igl/bounding_box.cpp b/src/igl/bounding_box.cpp new file mode 100644 index 000000000..e740b058f --- /dev/null +++ b/src/igl/bounding_box.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bounding_box.h" +#include + +template +IGL_INLINE void igl::bounding_box( + const Eigen::MatrixBase& V, + Eigen::PlainObjectBase& BV, + Eigen::PlainObjectBase& BF) +{ + return bounding_box(V,0.,BV,BF); +} + +template +IGL_INLINE void igl::bounding_box( + const Eigen::MatrixBase& V, + const typename DerivedV::Scalar pad, + Eigen::PlainObjectBase& BV, + Eigen::PlainObjectBase& BF) +{ + using namespace std; + + const int dim = V.cols(); + const auto & minV = V.colwise().minCoeff().array()-pad; + const auto & maxV = V.colwise().maxCoeff().array()+pad; + // 2^n vertices + BV.resize((1< combos = + [&BV,&minV,&maxV,&combos]( + const int dim, + const int i, + int * X, + const int pre_index) + { + for(X[i] = 0;X[i]<2;X[i]++) + { + int index = pre_index*2+X[i]; + if((i+1), Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::bounding_box, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::bounding_box, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::bounding_box, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::bounding_box, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/bounding_box.h b/src/igl/bounding_box.h new file mode 100644 index 000000000..7e053d2cc --- /dev/null +++ b/src/igl/bounding_box.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BOUNDING_BOX_H +#define IGL_BOUNDING_BOX_H +#include "igl_inline.h" +#include +namespace igl +{ + // Build a triangle mesh of the bounding box of a given list of vertices + // + // Inputs: + // V #V by dim list of rest domain positions + // Outputs: + // BV 2^dim by dim list of bounding box corners positions + // BF #BF by dim list of simplex facets + template + IGL_INLINE void bounding_box( + const Eigen::MatrixBase& V, + Eigen::PlainObjectBase& BV, + Eigen::PlainObjectBase& BF); + template + IGL_INLINE void bounding_box( + const Eigen::MatrixBase& V, + const typename DerivedV::Scalar pad, + Eigen::PlainObjectBase& BV, + Eigen::PlainObjectBase& BF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "bounding_box.cpp" +#endif + +#endif + diff --git a/src/igl/bounding_box_diagonal.cpp b/src/igl/bounding_box_diagonal.cpp new file mode 100644 index 000000000..1023abb50 --- /dev/null +++ b/src/igl/bounding_box_diagonal.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bounding_box_diagonal.h" +#include "mat_max.h" +#include "mat_min.h" +#include + +IGL_INLINE double igl::bounding_box_diagonal( + const Eigen::MatrixXd & V) +{ + using namespace Eigen; + VectorXd maxV,minV; + VectorXi maxVI,minVI; + mat_max(V,1,maxV,maxVI); + mat_min(V,1,minV,minVI); + return sqrt((maxV-minV).array().square().sum()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/bounding_box_diagonal.h b/src/igl/bounding_box_diagonal.h new file mode 100644 index 000000000..d89025bd5 --- /dev/null +++ b/src/igl/bounding_box_diagonal.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_BOUNDING_BOX_DIAGONAL_H +#define IGL_BOUNDING_BOX_DIAGONAL_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the length of the diagonal of a given meshes axis-aligned bounding + // box + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // Returns length of bounding box diagonal + IGL_INLINE double bounding_box_diagonal( const Eigen::MatrixXd & V); +} + +#ifndef IGL_STATIC_LIBRARY +# include "bounding_box_diagonal.cpp" +#endif + +#endif diff --git a/src/igl/canonical_quaternions.cpp b/src/igl/canonical_quaternions.cpp new file mode 100644 index 000000000..55d68da86 --- /dev/null +++ b/src/igl/canonical_quaternions.cpp @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "canonical_quaternions.h" + +template <> IGL_INLINE float igl::CANONICAL_VIEW_QUAT(int i, int j) +{ + return (float)igl::CANONICAL_VIEW_QUAT_F[i][j]; +} +template <> IGL_INLINE double igl::CANONICAL_VIEW_QUAT(int i, int j) +{ + return (double)igl::CANONICAL_VIEW_QUAT_D[i][j]; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/canonical_quaternions.h b/src/igl/canonical_quaternions.h new file mode 100644 index 000000000..86d90112d --- /dev/null +++ b/src/igl/canonical_quaternions.h @@ -0,0 +1,129 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CANONICAL_QUATERNIONS_H +#define IGL_CANONICAL_QUATERNIONS_H +#include "igl_inline.h" +// Define some canonical quaternions for floats and doubles +// A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), +// such that q = x*i + y*j + z*k + w +namespace igl +{ + // Float versions +#define SQRT_2_OVER_2 0.707106781f + // Identity + const float IDENTITY_QUAT_F[4] = {0,0,0,1}; + // The following match the Matlab canonical views + // X point right, Y pointing up and Z point out + const float XY_PLANE_QUAT_F[4] = {0,0,0,1}; + // X points right, Y points *in* and Z points up + const float XZ_PLANE_QUAT_F[4] = {-SQRT_2_OVER_2,0,0,SQRT_2_OVER_2}; + // X points out, Y points right, and Z points up + const float YZ_PLANE_QUAT_F[4] = {-0.5,-0.5,-0.5,0.5}; + const float CANONICAL_VIEW_QUAT_F[][4] = + { + { 0, 0, 0, 1}, // 0 + { 0, 0, SQRT_2_OVER_2, SQRT_2_OVER_2}, // 1 + { 0, 0, 1, 0}, // 2 + { 0, 0, SQRT_2_OVER_2,-SQRT_2_OVER_2}, // 3 + + { 0, -1, 0, 0}, // 4 + {-SQRT_2_OVER_2, SQRT_2_OVER_2, 0, 0}, // 5 + { -1, 0, 0, 0}, // 6 + {-SQRT_2_OVER_2,-SQRT_2_OVER_2, 0, 0}, // 7 + + { -0.5, -0.5, -0.5, 0.5}, // 8 + { 0,-SQRT_2_OVER_2, 0, SQRT_2_OVER_2}, // 9 + { 0.5, -0.5, 0.5, 0.5}, // 10 + { SQRT_2_OVER_2, 0, SQRT_2_OVER_2, 0}, // 11 + + { SQRT_2_OVER_2, 0,-SQRT_2_OVER_2, 0}, // 12 + { 0.5, 0.5, -0.5, 0.5}, // 13 + { 0, SQRT_2_OVER_2, 0, SQRT_2_OVER_2}, // 14 + { -0.5, 0.5, 0.5, 0.5}, // 15 + + { 0, SQRT_2_OVER_2, SQRT_2_OVER_2, 0}, // 16 + { -0.5, 0.5, 0.5, -0.5}, // 17 + {-SQRT_2_OVER_2, 0, 0,-SQRT_2_OVER_2}, // 18 + { -0.5, -0.5, -0.5, -0.5}, // 19 + + {-SQRT_2_OVER_2, 0, 0, SQRT_2_OVER_2}, // 20 + { -0.5, -0.5, 0.5, 0.5}, // 21 + { 0,-SQRT_2_OVER_2, SQRT_2_OVER_2, 0}, // 22 + { 0.5, -0.5, 0.5, -0.5} // 23 + }; +#undef SQRT_2_OVER_2 + // Double versions +#define SQRT_2_OVER_2 0.70710678118654757 + // Identity + const double IDENTITY_QUAT_D[4] = {0,0,0,1}; + // The following match the Matlab canonical views + // X point right, Y pointing up and Z point out + const double XY_PLANE_QUAT_D[4] = {0,0,0,1}; + // X points right, Y points *in* and Z points up + const double XZ_PLANE_QUAT_D[4] = {-SQRT_2_OVER_2,0,0,SQRT_2_OVER_2}; + // X points out, Y points right, and Z points up + const double YZ_PLANE_QUAT_D[4] = {-0.5,-0.5,-0.5,0.5}; + const double CANONICAL_VIEW_QUAT_D[][4] = + { + { 0, 0, 0, 1}, + { 0, 0, SQRT_2_OVER_2, SQRT_2_OVER_2}, + { 0, 0, 1, 0}, + { 0, 0, SQRT_2_OVER_2,-SQRT_2_OVER_2}, + + { 0, -1, 0, 0}, + {-SQRT_2_OVER_2, SQRT_2_OVER_2, 0, 0}, + { -1, 0, 0, 0}, + {-SQRT_2_OVER_2,-SQRT_2_OVER_2, 0, 0}, + + { -0.5, -0.5, -0.5, 0.5}, + { 0,-SQRT_2_OVER_2, 0, SQRT_2_OVER_2}, + { 0.5, -0.5, 0.5, 0.5}, + { SQRT_2_OVER_2, 0, SQRT_2_OVER_2, 0}, + + { SQRT_2_OVER_2, 0,-SQRT_2_OVER_2, 0}, + { 0.5, 0.5, -0.5, 0.5}, + { 0, SQRT_2_OVER_2, 0, SQRT_2_OVER_2}, + { -0.5, 0.5, 0.5, 0.5}, + + { 0, SQRT_2_OVER_2, SQRT_2_OVER_2, 0}, + { -0.5, 0.5, 0.5, -0.5}, + {-SQRT_2_OVER_2, 0, 0,-SQRT_2_OVER_2}, + { -0.5, -0.5, -0.5, -0.5}, + + {-SQRT_2_OVER_2, 0, 0, SQRT_2_OVER_2}, + { -0.5, -0.5, 0.5, 0.5}, + { 0,-SQRT_2_OVER_2, SQRT_2_OVER_2, 0}, + { 0.5, -0.5, 0.5, -0.5} + }; +#undef SQRT_2_OVER_2 +#define NUM_CANONICAL_VIEW_QUAT 24 + + // NOTE: I want to rather be able to return a Q_type[][] but C++ is not + // making it easy. So instead I've written a per-element accessor + + // Return element [i][j] of the corresponding CANONICAL_VIEW_QUAT_* of the + // given templated type + // Inputs: + // i index of quaternion + // j index of coordinate in quaternion i + // Returns values of CANONICAL_VIEW_QUAT_*[i][j] + template + IGL_INLINE Q_type CANONICAL_VIEW_QUAT(int i, int j); + // Template specializations for float and double + template <> + IGL_INLINE float CANONICAL_VIEW_QUAT(int i, int j); + template <> + IGL_INLINE double CANONICAL_VIEW_QUAT(int i, int j); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "canonical_quaternions.cpp" +#endif + +#endif diff --git a/src/igl/cat.cpp b/src/igl/cat.cpp new file mode 100644 index 000000000..f297fc135 --- /dev/null +++ b/src/igl/cat.cpp @@ -0,0 +1,267 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cat.h" + +#include + +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include + + +// Sparse matrices need to be handled carefully. Because C++ does not +// Template: +// Scalar sparse matrix scalar type, e.g. double +template +IGL_INLINE void igl::cat( + const int dim, + const Eigen::SparseMatrix & A, + const Eigen::SparseMatrix & B, + Eigen::SparseMatrix & C) +{ + + assert(dim == 1 || dim == 2); + using namespace Eigen; + // Special case if B or A is empty + if(A.size() == 0) + { + C = B; + return; + } + if(B.size() == 0) + { + C = A; + return; + } + +#if false + // This **must** be DynamicSparseMatrix, otherwise this implementation is + // insanely slow + DynamicSparseMatrix dyn_C; + if(dim == 1) + { + assert(A.cols() == B.cols()); + dyn_C.resize(A.rows()+B.rows(),A.cols()); + }else if(dim == 2) + { + assert(A.rows() == B.rows()); + dyn_C.resize(A.rows(),A.cols()+B.cols()); + }else + { + fprintf(stderr,"cat.h: Error: Unsupported dimension %d\n",dim); + } + + dyn_C.reserve(A.nonZeros()+B.nonZeros()); + + // Iterate over outside of A + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + dyn_C.coeffRef(it.row(),it.col()) += it.value(); + } + } + + // Iterate over outside of B + for(int k=0; k::InnerIterator it (B,k); it; ++it) + { + int r = (dim == 1 ? A.rows()+it.row() : it.row()); + int c = (dim == 2 ? A.cols()+it.col() : it.col()); + dyn_C.coeffRef(r,c) += it.value(); + } + } + + C = SparseMatrix(dyn_C); +#elif false + std::vector > CIJV; + CIJV.reserve(A.nonZeros() + B.nonZeros()); + { + // Iterate over outside of A + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + CIJV.emplace_back(it.row(),it.col(),it.value()); + } + } + // Iterate over outside of B + for(int k=0; k::InnerIterator it (B,k); it; ++it) + { + int r = (dim == 1 ? A.rows()+it.row() : it.row()); + int c = (dim == 2 ? A.cols()+it.col() : it.col()); + CIJV.emplace_back(r,c,it.value()); + } + } + + } + + C = SparseMatrix( + dim == 1 ? A.rows()+B.rows() : A.rows(), + dim == 1 ? A.cols() : A.cols()+B.cols()); + C.reserve(A.nonZeros() + B.nonZeros()); + C.setFromTriplets(CIJV.begin(),CIJV.end()); +#else + C = SparseMatrix( + dim == 1 ? A.rows()+B.rows() : A.rows(), + dim == 1 ? A.cols() : A.cols()+B.cols()); + Eigen::VectorXi per_col = Eigen::VectorXi::Zero(C.cols()); + if(dim == 1) + { + assert(A.outerSize() == B.outerSize()); + for(int k = 0;k::InnerIterator it (A,k); it; ++it) + { + per_col(k)++; + } + for(typename SparseMatrix::InnerIterator it (B,k); it; ++it) + { + per_col(k)++; + } + } + }else + { + for(int k = 0;k::InnerIterator it (A,k); it; ++it) + { + per_col(k)++; + } + } + for(int k = 0;k::InnerIterator it (B,k); it; ++it) + { + per_col(A.cols() + k)++; + } + } + } + C.reserve(per_col); + if(dim == 1) + { + for(int k = 0;k::InnerIterator it (A,k); it; ++it) + { + C.insert(it.row(),k) = it.value(); + } + for(typename SparseMatrix::InnerIterator it (B,k); it; ++it) + { + C.insert(A.rows()+it.row(),k) = it.value(); + } + } + }else + { + for(int k = 0;k::InnerIterator it (A,k); it; ++it) + { + C.insert(it.row(),k) = it.value(); + } + } + for(int k = 0;k::InnerIterator it (B,k); it; ++it) + { + C.insert(it.row(),A.cols()+k) = it.value(); + } + } + } + C.makeCompressed(); + +#endif + +} + +template +IGL_INLINE void igl::cat( + const int dim, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + MatC & C) +{ + assert(dim == 1 || dim == 2); + // Special case if B or A is empty + if(A.size() == 0) + { + C = B; + return; + } + if(B.size() == 0) + { + C = A; + return; + } + + if(dim == 1) + { + assert(A.cols() == B.cols()); + C.resize(A.rows()+B.rows(),A.cols()); + C << A,B; + }else if(dim == 2) + { + assert(A.rows() == B.rows()); + C.resize(A.rows(),A.cols()+B.cols()); + C << A,B; + }else + { + fprintf(stderr,"cat.h: Error: Unsupported dimension %d\n",dim); + } +} + +template +IGL_INLINE Mat igl::cat(const int dim, const Mat & A, const Mat & B) +{ + assert(dim == 1 || dim == 2); + Mat C; + igl::cat(dim,A,B,C); + return C; +} + +template +IGL_INLINE void igl::cat(const std::vector > & A, Mat & C) +{ + using namespace std; + // Start with empty matrix + C.resize(0,0); + for(const auto & row_vec : A) + { + // Concatenate each row horizontally + // Start with empty matrix + Mat row(0,0); + for(const auto & element : row_vec) + { + row = cat(2,row,element); + } + // Concatenate rows vertically + C = cat(1,C,row); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Matrix igl::cat >(int, Eigen::Matrix const&, Eigen::Matrix const&); +// generated by autoexplicit.sh +template Eigen::SparseMatrix igl::cat >(int, Eigen::SparseMatrix const&, Eigen::SparseMatrix const&); +// generated by autoexplicit.sh +template Eigen::Matrix igl::cat >(int, Eigen::Matrix const&, Eigen::Matrix const&); +template void igl::cat, Eigen::Matrix >(int, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix&); +template Eigen::Matrix igl::cat >(int, Eigen::Matrix const&, Eigen::Matrix const&); +template Eigen::Matrix igl::cat >(int, Eigen::Matrix const&, Eigen::Matrix const&); +template void igl::cat, Eigen::Matrix >(int, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix&); +template void igl::cat, Eigen::Matrix >(int, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix&); +#endif diff --git a/src/igl/cat.h b/src/igl/cat.h new file mode 100644 index 000000000..615bda130 --- /dev/null +++ b/src/igl/cat.h @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CAT_H +#define IGL_CAT_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + // If you're using Dense matrices you might be better off using the << operator + + // This is an attempt to act like matlab's cat function. + + // Perform concatenation of a two matrices along a single dimension + // If dim == 1, then C = [A;B]. If dim == 2 then C = [A B] + // + // Template: + // Scalar scalar data type for sparse matrices like double or int + // Mat matrix type for all matrices (e.g. MatrixXd, SparseMatrix) + // MatC matrix type for output matrix (e.g. MatrixXd) needs to support + // resize + // Inputs: + // A first input matrix + // B second input matrix + // dim dimension along which to concatenate, 1 or 2 + // Outputs: + // C output matrix + // + template + IGL_INLINE void cat( + const int dim, + const Eigen::SparseMatrix & A, + const Eigen::SparseMatrix & B, + Eigen::SparseMatrix & C); + template + IGL_INLINE void cat( + const int dim, + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + MatC & C); + // Wrapper that returns C + template + IGL_INLINE Mat cat(const int dim, const Mat & A, const Mat & B); + + // Note: Maybe we can autogenerate a bunch of overloads D = cat(int,A,B,C), + // E = cat(int,A,B,C,D), etc. + + // Concatenate a "matrix" of blocks + // C = [A0;A1;A2;...;An] where Ai = [A[i][0] A[i][1] ... A[i][m]]; + // + // Inputs: + // A a matrix (vector of row vectors) + // Output: + // C + template + IGL_INLINE void cat(const std::vector > & A, Mat & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "cat.cpp" +#endif + +#endif diff --git a/src/igl/ceil.cpp b/src/igl/ceil.cpp new file mode 100644 index 000000000..278269ab2 --- /dev/null +++ b/src/igl/ceil.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ceil.h" +#include + +template < typename DerivedX, typename DerivedY> +IGL_INLINE void igl::ceil( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y) +{ + using namespace std; + //Y = DerivedY::Zero(m,n); +//#pragma omp parallel for + //for(int i = 0;iScalar{return std::ceil(x);}).template cast(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::ceil, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ceil.h b/src/igl/ceil.h new file mode 100644 index 000000000..b6acee904 --- /dev/null +++ b/src/igl/ceil.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CEIL_H +#define IGL_CEIL_H +#include "igl_inline.h" +#include +namespace igl +{ + // Ceil a given matrix to nearest integers + // + // Inputs: + // X m by n matrix of scalars + // Outputs: + // Y m by n matrix of ceiled integers + template < typename DerivedX, typename DerivedY> + IGL_INLINE void ceil( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "ceil.cpp" +#endif + +#endif diff --git a/src/igl/centroid.cpp b/src/igl/centroid.cpp new file mode 100644 index 000000000..a9ed25df2 --- /dev/null +++ b/src/igl/centroid.cpp @@ -0,0 +1,67 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "centroid.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename Derivedc, + typename Derivedvol> +IGL_INLINE void igl::centroid( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& cen, + Derivedvol & vol) +{ + using namespace Eigen; + assert(F.cols() == 3 && "F should contain triangles."); + assert(V.cols() == 3 && "V should contain 3d points."); + const int m = F.rows(); + cen.setZero(); + vol = 0; + // loop over faces + for(int f = 0;f RowVector3S; + const RowVector3S & a = V.row(F(f,0)); + const RowVector3S & b = V.row(F(f,1)); + const RowVector3S & c = V.row(F(f,2)); + // un-normalized normal + const RowVector3S & n = (b-a).cross(c-a); + // total volume via divergence theorem: ∫ 1 + vol += n.dot(a)/6.; + // centroid via divergence theorem and midpoint quadrature: ∫ x + cen.array() += (1./24.*n.array()*((a+b).array().square() + (b+c).array().square() + + (c+a).array().square()).array()); + } + cen *= 1./(2.*vol); +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedc> +IGL_INLINE void igl::centroid( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& c) +{ + typename Derivedc::Scalar vol; + return centroid(V,F,c,vol); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::centroid, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::centroid, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/centroid.h b/src/igl/centroid.h new file mode 100644 index 000000000..31fa7ae6b --- /dev/null +++ b/src/igl/centroid.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CENTROID_H +#define IGL_CENTROID_H +#include "igl_inline.h" +#include +namespace igl +{ + // CENTROID Computes the centroid of a closed mesh using a surface integral. + // + // Inputs: + // V #V by dim list of rest domain positions + // F #F by 3 list of triangle indices into V + // Outputs: + // c dim vector of centroid coordinates + // vol total volume of solid. + // + template < + typename DerivedV, + typename DerivedF, + typename Derivedc, + typename Derivedvol> + IGL_INLINE void centroid( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& c, + Derivedvol & vol); + template < + typename DerivedV, + typename DerivedF, + typename Derivedc> + IGL_INLINE void centroid( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& c); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "centroid.cpp" +#endif + +#endif + diff --git a/src/igl/circulation.cpp b/src/igl/circulation.cpp new file mode 100644 index 000000000..6f711b946 --- /dev/null +++ b/src/igl/circulation.cpp @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "circulation.h" +#include "list_to_matrix.h" + +IGL_INLINE std::vector igl::circulation( + const int e, + const bool ccw, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI) +{ + // prepare output + std::vector N; + N.reserve(6); + const int m = F.rows(); + const auto & step = [&]( + const int e, + const int ff, + int & ne, + int & nf) + { + assert((EF(e,1) == ff || EF(e,0) == ff) && "e should touch ff"); + //const int fside = EF(e,1)==ff?1:0; + const int nside = EF(e,0)==ff?1:0; + const int nv = EI(e,nside); + // get next face + nf = EF(e,nside); + // get next edge + const int dir = ccw?-1:1; + ne = EMAP(nf+m*((nv+dir+3)%3)); + }; + // Always start with first face (ccw in step will be sure to turn right + // direction) + const int f0 = EF(e,0); + int fi = f0; + int ei = e; + while(true) + { + step(ei,fi,ei,fi); + N.push_back(fi); + // back to start? + if(fi == f0) + { + assert(ei == e); + break; + } + } + return N; +} + +IGL_INLINE void igl::circulation( + const int e, + const bool ccw, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + Eigen::VectorXi & vN) +{ + std::vector N = circulation(e,ccw,F,E,EMAP,EF,EI); + igl::list_to_matrix(N,vN); +} diff --git a/src/igl/circulation.h b/src/igl/circulation.h new file mode 100644 index 000000000..d8d14c7ed --- /dev/null +++ b/src/igl/circulation.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CIRCULATION_H +#define IGL_CIRCULATION_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Return list of faces around the end point of an edge. Assumes + // data-structures are built from an edge-manifold **closed** mesh. + // + // Inputs: + // e index into E of edge to circulate + // ccw whether to _continue_ in ccw direction of edge (circulate around + // E(e,1)) + // F #F by 3 list of face indices + // E #E by 2 list of edge indices + // EMAP #F*3 list of indices into E, mapping each directed edge to unique + // unique edge in E + // EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // Returns list of faces touched by circulation (in cyclically order). + // + IGL_INLINE std::vector circulation( + const int e, + const bool ccw, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI); + // Wrapper with VectorXi output. + IGL_INLINE void circulation( + const int e, + const bool ccw, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + Eigen::VectorXi & vN); +} + +#ifndef IGL_STATIC_LIBRARY +# include "circulation.cpp" +#endif +#endif diff --git a/src/igl/circumradius.cpp b/src/igl/circumradius.cpp new file mode 100644 index 000000000..34424eae2 --- /dev/null +++ b/src/igl/circumradius.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "circumradius.h" +#include "edge_lengths.h" +#include "doublearea.h" +template < + typename DerivedV, + typename DerivedF, + typename DerivedR> +IGL_INLINE void igl::circumradius( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & R) +{ + Eigen::Matrix l; + igl::edge_lengths(V,F,l); + DerivedR A; + igl::doublearea(l,0.,A); + // use formula: R=abc/(4*area) to compute the circum radius + R = l.col(0).array() * l.col(1).array() * l.col(2).array() / (2.0*A.array()); +} diff --git a/src/igl/circumradius.h b/src/igl/circumradius.h new file mode 100644 index 000000000..12592ee3f --- /dev/null +++ b/src/igl/circumradius.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CIRCUMRADIUS_H +#define IGL_CIRCUMRADIUS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the circumradius of each triangle in a mesh (V,F) + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // R #F list of circumradii + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedR> + IGL_INLINE void circumradius( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & R); +} +#ifndef IGL_STATIC_LIBRARY +# include "circumradius.cpp" +#endif +#endif diff --git a/src/igl/collapse_edge.cpp b/src/igl/collapse_edge.cpp new file mode 100644 index 000000000..9f00b4323 --- /dev/null +++ b/src/igl/collapse_edge.cpp @@ -0,0 +1,394 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "collapse_edge.h" +#include "circulation.h" +#include "edge_collapse_is_valid.h" +#include + +IGL_INLINE bool igl::collapse_edge( + const int e, + const Eigen::RowVectorXd & p, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + int & a_e1, + int & a_e2, + int & a_f1, + int & a_f2) +{ + // Assign this to 0 rather than, say, -1 so that deleted elements will get + // draw as degenerate elements at vertex 0 (which should always exist and + // never get collapsed to anything else since it is the smallest index) + using namespace Eigen; + using namespace std; + const int eflip = E(e,0)>E(e,1); + // source and destination + const int s = eflip?E(e,1):E(e,0); + const int d = eflip?E(e,0):E(e,1); + + if(!edge_collapse_is_valid(e,F,E,EMAP,EF,EI)) + { + return false; + } + + // Important to grab neighbors of d before monkeying with edges + const std::vector nV2Fd = circulation(e,!eflip,F,E,EMAP,EF,EI); + + // The following implementation strongly relies on s & cost_and_placement, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C) +{ + int e,e1,e2,f1,f2; + const auto always_try = []( + const Eigen::MatrixXd & ,/*V*/ + const Eigen::MatrixXi & ,/*F*/ + const Eigen::MatrixXi & ,/*E*/ + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & ,/*EF*/ + const Eigen::MatrixXi & ,/*EI*/ + const std::set > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + ) -> bool { return true;}; + const auto never_care = []( + const Eigen::MatrixXd & , /*V*/ + const Eigen::MatrixXi & , /*F*/ + const Eigen::MatrixXi & , /*E*/ + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & , /*EF*/ + const Eigen::MatrixXi & , /*EI*/ + const std::set > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )-> void { }; + return + collapse_edge( + cost_and_placement,always_try,never_care, + V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2); +} + +IGL_INLINE bool igl::collapse_edge( + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C) +{ + int e,e1,e2,f1,f2; + return + collapse_edge( + cost_and_placement,pre_collapse,post_collapse, + V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2); +} + + +IGL_INLINE bool igl::collapse_edge( + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C, + int & e, + int & e1, + int & e2, + int & f1, + int & f2) +{ + using namespace Eigen; + if(Q.empty()) + { + // no edges to collapse + return false; + } + std::pair p = *(Q.begin()); + if(p.first == std::numeric_limits::infinity()) + { + // min cost edge is infinite cost + return false; + } + Q.erase(Q.begin()); + e = p.second; + Qit[e] = Q.end(); + std::vector N = circulation(e, true,F,E,EMAP,EF,EI); + std::vector Nd = circulation(e,false,F,E,EMAP,EF,EI); + N.insert(N.begin(),Nd.begin(),Nd.end()); + bool collapsed = true; + if(pre_collapse(V,F,E,EMAP,EF,EI,Q,Qit,C,e)) + { + collapsed = collapse_edge(e,C.row(e),V,F,E,EMAP,EF,EI,e1,e2,f1,f2); + }else + { + // Aborted by pre collapse callback + collapsed = false; + } + post_collapse(V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2,collapsed); + if(collapsed) + { + // Erase the two, other collapsed edges + Q.erase(Qit[e1]); + Qit[e1] = Q.end(); + Q.erase(Qit[e2]); + Qit[e2] = Q.end(); + // update local neighbors + // loop over original face neighbors + for(auto n : N) + { + if(F(n,0) != IGL_COLLAPSE_EDGE_NULL || + F(n,1) != IGL_COLLAPSE_EDGE_NULL || + F(n,2) != IGL_COLLAPSE_EDGE_NULL) + { + for(int v = 0;v<3;v++) + { + // get edge id + const int ei = EMAP(v*F.rows()+n); + // erase old entry + Q.erase(Qit[ei]); + // compute cost and potential placement + double cost; + RowVectorXd place; + cost_and_placement(ei,V,F,E,EMAP,EF,EI,cost,place); + // Replace in queue + Qit[ei] = Q.insert(std::pair(cost,ei)).first; + C.row(ei) = place; + } + } + } + }else + { + // reinsert with infinite weight (the provided cost function must **not** + // have given this un-collapsable edge inf cost already) + p.first = std::numeric_limits::infinity(); + Qit[e] = Q.insert(p).first; + } + return collapsed; +} diff --git a/src/igl/collapse_edge.h b/src/igl/collapse_edge.h new file mode 100644 index 000000000..90e562556 --- /dev/null +++ b/src/igl/collapse_edge.h @@ -0,0 +1,216 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COLLAPSE_EDGE_H +#define IGL_COLLAPSE_EDGE_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Assumes (V,F) is a closed manifold mesh (except for previously collapsed + // faces which should be set to: + // [IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL]. + // Collapses exactly two faces and exactly 3 edges from E (e and one side of + // each face gets collapsed to the other). This is implemented in a way that + // it can be repeatedly called until satisfaction and then the garbage in F + // can be collected by removing NULL faces. + // + // Inputs: + // e index into E of edge to try to collapse. E(e,:) = [s d] or [d s] so + // that sj) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // e1 index into E of edge collpased on left + // e2 index into E of edge collpased on left + // f1 index into E of edge collpased on left + // f2 index into E of edge collpased on left + // Returns true if edge was collapsed + #define IGL_COLLAPSE_EDGE_NULL 0 + IGL_INLINE bool collapse_edge( + const int e, + const Eigen::RowVectorXd & p, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + int & e1, + int & e2, + int & f1, + int & f2); + IGL_INLINE bool collapse_edge( + const int e, + const Eigen::RowVectorXd & p, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI); + // Collapse least-cost edge from a priority queue and update queue + // + // Inputs/Outputs: + // cost_and_placement function computing cost of collapsing an edge and 3d + // position where it should be placed: + // cost_and_placement(V,F,E,EMAP,EF,EI,cost,placement); + // **If the edges is collapsed** then this function will be called on all + // edges of all faces previously incident on the endpoints of the + // collapsed edge. + // Q queue containing pairs of costs and edge indices + // Qit list of iterators so that Qit[e] --> iterator of edge e in Q + // C #E by dim list of stored placements + IGL_INLINE bool collapse_edge( + const std::function & cost_and_placement, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C); + // Inputs: + // pre_collapse callback called with index of edge whose collapse is about + // to be attempted. This function should return whether to **proceed** + // with the collapse: returning true means "yes, try to collapse", + // returning false means "No, consider this edge 'uncollapsable', behave + // as if collapse_edge(e) returned false. + // post_collapse callback called with index of edge whose collapse was + // just attempted and a flag revealing whether this was successful. + IGL_INLINE bool collapse_edge( + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C); + + IGL_INLINE bool collapse_edge( + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI, + std::set > & Q, + std::vector >::iterator > & Qit, + Eigen::MatrixXd & C, + int & e, + int & e1, + int & e2, + int & f1, + int & f2); +} + +#ifndef IGL_STATIC_LIBRARY +# include "collapse_edge.cpp" +#endif +#endif diff --git a/src/igl/collapse_small_triangles.cpp b/src/igl/collapse_small_triangles.cpp new file mode 100644 index 000000000..5177c584f --- /dev/null +++ b/src/igl/collapse_small_triangles.cpp @@ -0,0 +1,139 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "collapse_small_triangles.h" + +#include "bounding_box_diagonal.h" +#include "doublearea.h" +#include "edge_lengths.h" +#include "colon.h" +#include "faces_first.h" + +#include + +#include + +void igl::collapse_small_triangles( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const double eps, + Eigen::MatrixXi & FF) +{ + using namespace Eigen; + using namespace std; + + // Compute bounding box diagonal length + double bbd = bounding_box_diagonal(V); + MatrixXd l; + edge_lengths(V,F,l); + VectorXd dblA; + doublearea(l,0.,dblA); + + // Minimum area tolerance + const double min_dblarea = 2.0*eps*bbd*bbd; + + Eigen::VectorXi FIM = colon(0,V.rows()-1); + int num_edge_collapses = 0; + // Loop over triangles + for(int f = 0;fmaxl) + { + maxli = e; + maxl = l(f,e); + } + } + // Be sure that min and max aren't the same + maxli = (minli==maxli?(minli+1)%3:maxli); + + // Collapse min edge maintaining max edge: i-->j + // Q: Why this direction? + int i = maxli; + int j = ((minli+1)%3 == maxli ? (minli+2)%3: (minli+1)%3); + assert(i != minli); + assert(j != minli); + assert(i != j); + FIM(F(f,i)) = FIM(F(f,j)); + num_edge_collapses++; + } + } + + // Reindex faces + MatrixXi rF = F; + // Loop over triangles + for(int f = 0;f +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COLLAPSE_SMALL_TRIANGLES_H +#define IGL_COLLAPSE_SMALL_TRIANGLES_H +#include +namespace igl +{ + // Given a triangle mesh (V,F) compute a new mesh (VV,FF) which contains the + // original faces and vertices of (V,F) except any small triangles have been + // removed via collapse. + // + // We are *not* following the rules in "Mesh Optimization" [Hoppe et al] + // Section 4.2. But for our purposes we don't care about this criteria. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // eps epsilon for smallest allowed area treated as fraction of squared bounding box + // diagonal + // Outputs: + // FF #FF by 3 list of triangle indices into V + // + // + void collapse_small_triangles( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const double eps, + Eigen::MatrixXi & FF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "collapse_small_triangles.cpp" +#endif + +#endif diff --git a/src/igl/colon.cpp b/src/igl/colon.cpp new file mode 100644 index 000000000..a10c2982e --- /dev/null +++ b/src/igl/colon.cpp @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "colon.h" +#include "LinSpaced.h" + +#include + +template +IGL_INLINE void igl::colon( + const L low, + const S step, + const H hi, + Eigen::Matrix & I) +{ + const int size = ((hi-low)/step)+1; + I = igl::LinSpaced >(size,low,low+step*(size-1)); +} + +template +IGL_INLINE void igl::colon( + const L low, + const H hi, + Eigen::Matrix & I) +{ + return igl::colon(low,(T)1,hi,I); +} + +template +IGL_INLINE Eigen::Matrix igl::colon( + const L low, + const H hi) +{ + Eigen::Matrix I; + igl::colon(low,hi,I); + return I; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Matrix igl::colon(int, int); +template Eigen::Matrix igl::colon(int,long); +template Eigen::Matrix igl::colon(int,long long int); +template Eigen::Matrix igl::colon(double, double); +// generated by autoexplicit.sh +template void igl::colon(int, long, int, Eigen::Matrix&); +template void igl::colon(int, int, long, Eigen::Matrix&); +template void igl::colon(int, long, Eigen::Matrix&); +template void igl::colon(int, int, Eigen::Matrix&); +template void igl::colon(int,long long int,Eigen::Matrix &); +template void igl::colon(int, int, int, Eigen::Matrix&); +template void igl::colon(int, long, Eigen::Matrix&); +template void igl::colon(int, double, double, Eigen::Matrix&); +template void igl::colon(double, double, Eigen::Matrix&); +template void igl::colon(double, double, double, Eigen::Matrix&); +template void igl::colon(int, int, Eigen::Matrix&); +#ifdef WIN32 +template void igl::colon(int, long long, class Eigen::Matrix &); +template void igl::colon(int, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1> &); +#endif +#endif diff --git a/src/igl/colon.h b/src/igl/colon.h new file mode 100644 index 000000000..1f2b9bf6f --- /dev/null +++ b/src/igl/colon.h @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COLON_H +#define IGL_COLON_H +#include "igl_inline.h" +#include +namespace igl +{ + // Note: + // This should be potentially replaced with eigen's LinSpaced() function + // + // If step = 1, it's about 5 times faster to use: + // X = Eigen::VectorXi::LinSpaced(n,0,n-1); + // than + // X = igl::colon(0,n-1); + // + + // Colon operator like matlab's colon operator. Enumerats values between low + // and hi with step step. + // Templates: + // L should be a eigen matrix primitive type like int or double + // S should be a eigen matrix primitive type like int or double + // H should be a eigen matrix primitive type like int or double + // T should be a eigen matrix primitive type like int or double + // Inputs: + // low starting value if step is valid then this is *always* the first + // element of I + // step step difference between sequential elements returned in I, + // remember this will be cast to template T at compile time. If lowhi then step must be negative. + // Otherwise I will be set to empty. + // hi ending value, if (hi-low)%step is zero then this will be the last + // element in I. If step is positive there will be no elements greater + // than hi, vice versa if hi + IGL_INLINE void colon( + const L low, + const S step, + const H hi, + Eigen::Matrix & I); + // Same as above but step == (T)1 + template + IGL_INLINE void colon( + const L low, + const H hi, + Eigen::Matrix & I); + // Return output rather than set in reference + template + IGL_INLINE Eigen::Matrix colon( + const L low, + const H hi); +} + +#ifndef IGL_STATIC_LIBRARY +# include "colon.cpp" +#endif + +#endif diff --git a/src/igl/colormap.cpp b/src/igl/colormap.cpp new file mode 100644 index 000000000..560cbe6c0 --- /dev/null +++ b/src/igl/colormap.cpp @@ -0,0 +1,1434 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Joe Graus , Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "colormap.h" +#include "jet.h" +#include + +// One of the new matplotlib colormaps by Nathaniel J.Smith, Stefan van der Walt, and (in the case of viridis) Eric Firing. +// Released under the CC0 license / public domain dedication + +namespace igl +{ +static double inferno_cm[256][3] = { + { 0.001462, 0.000466, 0.013866 }, + { 0.002267, 0.001270, 0.018570 }, + { 0.003299, 0.002249, 0.024239 }, + { 0.004547, 0.003392, 0.030909 }, + { 0.006006, 0.004692, 0.038558 }, + { 0.007676, 0.006136, 0.046836 }, + { 0.009561, 0.007713, 0.055143 }, + { 0.011663, 0.009417, 0.063460 }, + { 0.013995, 0.011225, 0.071862 }, + { 0.016561, 0.013136, 0.080282 }, + { 0.019373, 0.015133, 0.088767 }, + { 0.022447, 0.017199, 0.097327 }, + { 0.025793, 0.019331, 0.105930 }, + { 0.029432, 0.021503, 0.114621 }, + { 0.033385, 0.023702, 0.123397 }, + { 0.037668, 0.025921, 0.132232 }, + { 0.042253, 0.028139, 0.141141 }, + { 0.046915, 0.030324, 0.150164 }, + { 0.051644, 0.032474, 0.159254 }, + { 0.056449, 0.034569, 0.168414 }, + { 0.061340, 0.036590, 0.177642 }, + { 0.066331, 0.038504, 0.186962 }, + { 0.071429, 0.040294, 0.196354 }, + { 0.076637, 0.041905, 0.205799 }, + { 0.081962, 0.043328, 0.215289 }, + { 0.087411, 0.044556, 0.224813 }, + { 0.092990, 0.045583, 0.234358 }, + { 0.098702, 0.046402, 0.243904 }, + { 0.104551, 0.047008, 0.253430 }, + { 0.110536, 0.047399, 0.262912 }, + { 0.116656, 0.047574, 0.272321 }, + { 0.122908, 0.047536, 0.281624 }, + { 0.129285, 0.047293, 0.290788 }, + { 0.135778, 0.046856, 0.299776 }, + { 0.142378, 0.046242, 0.308553 }, + { 0.149073, 0.045468, 0.317085 }, + { 0.155850, 0.044559, 0.325338 }, + { 0.162689, 0.043554, 0.333277 }, + { 0.169575, 0.042489, 0.340874 }, + { 0.176493, 0.041402, 0.348111 }, + { 0.183429, 0.040329, 0.354971 }, + { 0.190367, 0.039309, 0.361447 }, + { 0.197297, 0.038400, 0.367535 }, + { 0.204209, 0.037632, 0.373238 }, + { 0.211095, 0.037030, 0.378563 }, + { 0.217949, 0.036615, 0.383522 }, + { 0.224763, 0.036405, 0.388129 }, + { 0.231538, 0.036405, 0.392400 }, + { 0.238273, 0.036621, 0.396353 }, + { 0.244967, 0.037055, 0.400007 }, + { 0.251620, 0.037705, 0.403378 }, + { 0.258234, 0.038571, 0.406485 }, + { 0.264810, 0.039647, 0.409345 }, + { 0.271347, 0.040922, 0.411976 }, + { 0.277850, 0.042353, 0.414392 }, + { 0.284321, 0.043933, 0.416608 }, + { 0.290763, 0.045644, 0.418637 }, + { 0.297178, 0.047470, 0.420491 }, + { 0.303568, 0.049396, 0.422182 }, + { 0.309935, 0.051407, 0.423721 }, + { 0.316282, 0.053490, 0.425116 }, + { 0.322610, 0.055634, 0.426377 }, + { 0.328921, 0.057827, 0.427511 }, + { 0.335217, 0.060060, 0.428524 }, + { 0.341500, 0.062325, 0.429425 }, + { 0.347771, 0.064616, 0.430217 }, + { 0.354032, 0.066925, 0.430906 }, + { 0.360284, 0.069247, 0.431497 }, + { 0.366529, 0.071579, 0.431994 }, + { 0.372768, 0.073915, 0.432400 }, + { 0.379001, 0.076253, 0.432719 }, + { 0.385228, 0.078591, 0.432955 }, + { 0.391453, 0.080927, 0.433109 }, + { 0.397674, 0.083257, 0.433183 }, + { 0.403894, 0.085580, 0.433179 }, + { 0.410113, 0.087896, 0.433098 }, + { 0.416331, 0.090203, 0.432943 }, + { 0.422549, 0.092501, 0.432714 }, + { 0.428768, 0.094790, 0.432412 }, + { 0.434987, 0.097069, 0.432039 }, + { 0.441207, 0.099338, 0.431594 }, + { 0.447428, 0.101597, 0.431080 }, + { 0.453651, 0.103848, 0.430498 }, + { 0.459875, 0.106089, 0.429846 }, + { 0.466100, 0.108322, 0.429125 }, + { 0.472328, 0.110547, 0.428334 }, + { 0.478558, 0.112764, 0.427475 }, + { 0.484789, 0.114974, 0.426548 }, + { 0.491022, 0.117179, 0.425552 }, + { 0.497257, 0.119379, 0.424488 }, + { 0.503493, 0.121575, 0.423356 }, + { 0.509730, 0.123769, 0.422156 }, + { 0.515967, 0.125960, 0.420887 }, + { 0.522206, 0.128150, 0.419549 }, + { 0.528444, 0.130341, 0.418142 }, + { 0.534683, 0.132534, 0.416667 }, + { 0.540920, 0.134729, 0.415123 }, + { 0.547157, 0.136929, 0.413511 }, + { 0.553392, 0.139134, 0.411829 }, + { 0.559624, 0.141346, 0.410078 }, + { 0.565854, 0.143567, 0.408258 }, + { 0.572081, 0.145797, 0.406369 }, + { 0.578304, 0.148039, 0.404411 }, + { 0.584521, 0.150294, 0.402385 }, + { 0.590734, 0.152563, 0.400290 }, + { 0.596940, 0.154848, 0.398125 }, + { 0.603139, 0.157151, 0.395891 }, + { 0.609330, 0.159474, 0.393589 }, + { 0.615513, 0.161817, 0.391219 }, + { 0.621685, 0.164184, 0.388781 }, + { 0.627847, 0.166575, 0.386276 }, + { 0.633998, 0.168992, 0.383704 }, + { 0.640135, 0.171438, 0.381065 }, + { 0.646260, 0.173914, 0.378359 }, + { 0.652369, 0.176421, 0.375586 }, + { 0.658463, 0.178962, 0.372748 }, + { 0.664540, 0.181539, 0.369846 }, + { 0.670599, 0.184153, 0.366879 }, + { 0.676638, 0.186807, 0.363849 }, + { 0.682656, 0.189501, 0.360757 }, + { 0.688653, 0.192239, 0.357603 }, + { 0.694627, 0.195021, 0.354388 }, + { 0.700576, 0.197851, 0.351113 }, + { 0.706500, 0.200728, 0.347777 }, + { 0.712396, 0.203656, 0.344383 }, + { 0.718264, 0.206636, 0.340931 }, + { 0.724103, 0.209670, 0.337424 }, + { 0.729909, 0.212759, 0.333861 }, + { 0.735683, 0.215906, 0.330245 }, + { 0.741423, 0.219112, 0.326576 }, + { 0.747127, 0.222378, 0.322856 }, + { 0.752794, 0.225706, 0.319085 }, + { 0.758422, 0.229097, 0.315266 }, + { 0.764010, 0.232554, 0.311399 }, + { 0.769556, 0.236077, 0.307485 }, + { 0.775059, 0.239667, 0.303526 }, + { 0.780517, 0.243327, 0.299523 }, + { 0.785929, 0.247056, 0.295477 }, + { 0.791293, 0.250856, 0.291390 }, + { 0.796607, 0.254728, 0.287264 }, + { 0.801871, 0.258674, 0.283099 }, + { 0.807082, 0.262692, 0.278898 }, + { 0.812239, 0.266786, 0.274661 }, + { 0.817341, 0.270954, 0.270390 }, + { 0.822386, 0.275197, 0.266085 }, + { 0.827372, 0.279517, 0.261750 }, + { 0.832299, 0.283913, 0.257383 }, + { 0.837165, 0.288385, 0.252988 }, + { 0.841969, 0.292933, 0.248564 }, + { 0.846709, 0.297559, 0.244113 }, + { 0.851384, 0.302260, 0.239636 }, + { 0.855992, 0.307038, 0.235133 }, + { 0.860533, 0.311892, 0.230606 }, + { 0.865006, 0.316822, 0.226055 }, + { 0.869409, 0.321827, 0.221482 }, + { 0.873741, 0.326906, 0.216886 }, + { 0.878001, 0.332060, 0.212268 }, + { 0.882188, 0.337287, 0.207628 }, + { 0.886302, 0.342586, 0.202968 }, + { 0.890341, 0.347957, 0.198286 }, + { 0.894305, 0.353399, 0.193584 }, + { 0.898192, 0.358911, 0.188860 }, + { 0.902003, 0.364492, 0.184116 }, + { 0.905735, 0.370140, 0.179350 }, + { 0.909390, 0.375856, 0.174563 }, + { 0.912966, 0.381636, 0.169755 }, + { 0.916462, 0.387481, 0.164924 }, + { 0.919879, 0.393389, 0.160070 }, + { 0.923215, 0.399359, 0.155193 }, + { 0.926470, 0.405389, 0.150292 }, + { 0.929644, 0.411479, 0.145367 }, + { 0.932737, 0.417627, 0.140417 }, + { 0.935747, 0.423831, 0.135440 }, + { 0.938675, 0.430091, 0.130438 }, + { 0.941521, 0.436405, 0.125409 }, + { 0.944285, 0.442772, 0.120354 }, + { 0.946965, 0.449191, 0.115272 }, + { 0.949562, 0.455660, 0.110164 }, + { 0.952075, 0.462178, 0.105031 }, + { 0.954506, 0.468744, 0.099874 }, + { 0.956852, 0.475356, 0.094695 }, + { 0.959114, 0.482014, 0.089499 }, + { 0.961293, 0.488716, 0.084289 }, + { 0.963387, 0.495462, 0.079073 }, + { 0.965397, 0.502249, 0.073859 }, + { 0.967322, 0.509078, 0.068659 }, + { 0.969163, 0.515946, 0.063488 }, + { 0.970919, 0.522853, 0.058367 }, + { 0.972590, 0.529798, 0.053324 }, + { 0.974176, 0.536780, 0.048392 }, + { 0.975677, 0.543798, 0.043618 }, + { 0.977092, 0.550850, 0.039050 }, + { 0.978422, 0.557937, 0.034931 }, + { 0.979666, 0.565057, 0.031409 }, + { 0.980824, 0.572209, 0.028508 }, + { 0.981895, 0.579392, 0.026250 }, + { 0.982881, 0.586606, 0.024661 }, + { 0.983779, 0.593849, 0.023770 }, + { 0.984591, 0.601122, 0.023606 }, + { 0.985315, 0.608422, 0.024202 }, + { 0.985952, 0.615750, 0.025592 }, + { 0.986502, 0.623105, 0.027814 }, + { 0.986964, 0.630485, 0.030908 }, + { 0.987337, 0.637890, 0.034916 }, + { 0.987622, 0.645320, 0.039886 }, + { 0.987819, 0.652773, 0.045581 }, + { 0.987926, 0.660250, 0.051750 }, + { 0.987945, 0.667748, 0.058329 }, + { 0.987874, 0.675267, 0.065257 }, + { 0.987714, 0.682807, 0.072489 }, + { 0.987464, 0.690366, 0.079990 }, + { 0.987124, 0.697944, 0.087731 }, + { 0.986694, 0.705540, 0.095694 }, + { 0.986175, 0.713153, 0.103863 }, + { 0.985566, 0.720782, 0.112229 }, + { 0.984865, 0.728427, 0.120785 }, + { 0.984075, 0.736087, 0.129527 }, + { 0.983196, 0.743758, 0.138453 }, + { 0.982228, 0.751442, 0.147565 }, + { 0.981173, 0.759135, 0.156863 }, + { 0.980032, 0.766837, 0.166353 }, + { 0.978806, 0.774545, 0.176037 }, + { 0.977497, 0.782258, 0.185923 }, + { 0.976108, 0.789974, 0.196018 }, + { 0.974638, 0.797692, 0.206332 }, + { 0.973088, 0.805409, 0.216877 }, + { 0.971468, 0.813122, 0.227658 }, + { 0.969783, 0.820825, 0.238686 }, + { 0.968041, 0.828515, 0.249972 }, + { 0.966243, 0.836191, 0.261534 }, + { 0.964394, 0.843848, 0.273391 }, + { 0.962517, 0.851476, 0.285546 }, + { 0.960626, 0.859069, 0.298010 }, + { 0.958720, 0.866624, 0.310820 }, + { 0.956834, 0.874129, 0.323974 }, + { 0.954997, 0.881569, 0.337475 }, + { 0.953215, 0.888942, 0.351369 }, + { 0.951546, 0.896226, 0.365627 }, + { 0.950018, 0.903409, 0.380271 }, + { 0.948683, 0.910473, 0.395289 }, + { 0.947594, 0.917399, 0.410665 }, + { 0.946809, 0.924168, 0.426373 }, + { 0.946392, 0.930761, 0.442367 }, + { 0.946403, 0.937159, 0.458592 }, + { 0.946903, 0.943348, 0.474970 }, + { 0.947937, 0.949318, 0.491426 }, + { 0.949545, 0.955063, 0.507860 }, + { 0.951740, 0.960587, 0.524203 }, + { 0.954529, 0.965896, 0.540361 }, + { 0.957896, 0.971003, 0.556275 }, + { 0.961812, 0.975924, 0.571925 }, + { 0.966249, 0.980678, 0.587206 }, + { 0.971162, 0.985282, 0.602154 }, + { 0.976511, 0.989753, 0.616760 }, + { 0.982257, 0.994109, 0.631017 }, + { 0.988362, 0.998364, 0.644924 } +}; + +static double magma_cm[256][3] = { + { 0.001462, 0.000466, 0.013866 }, + { 0.002258, 0.001295, 0.018331 }, + { 0.003279, 0.002305, 0.023708 }, + { 0.004512, 0.003490, 0.029965 }, + { 0.005950, 0.004843, 0.037130 }, + { 0.007588, 0.006356, 0.044973 }, + { 0.009426, 0.008022, 0.052844 }, + { 0.011465, 0.009828, 0.060750 }, + { 0.013708, 0.011771, 0.068667 }, + { 0.016156, 0.013840, 0.076603 }, + { 0.018815, 0.016026, 0.084584 }, + { 0.021692, 0.018320, 0.092610 }, + { 0.024792, 0.020715, 0.100676 }, + { 0.028123, 0.023201, 0.108787 }, + { 0.031696, 0.025765, 0.116965 }, + { 0.035520, 0.028397, 0.125209 }, + { 0.039608, 0.031090, 0.133515 }, + { 0.043830, 0.033830, 0.141886 }, + { 0.048062, 0.036607, 0.150327 }, + { 0.052320, 0.039407, 0.158841 }, + { 0.056615, 0.042160, 0.167446 }, + { 0.060949, 0.044794, 0.176129 }, + { 0.065330, 0.047318, 0.184892 }, + { 0.069764, 0.049726, 0.193735 }, + { 0.074257, 0.052017, 0.202660 }, + { 0.078815, 0.054184, 0.211667 }, + { 0.083446, 0.056225, 0.220755 }, + { 0.088155, 0.058133, 0.229922 }, + { 0.092949, 0.059904, 0.239164 }, + { 0.097833, 0.061531, 0.248477 }, + { 0.102815, 0.063010, 0.257854 }, + { 0.107899, 0.064335, 0.267289 }, + { 0.113094, 0.065492, 0.276784 }, + { 0.118405, 0.066479, 0.286321 }, + { 0.123833, 0.067295, 0.295879 }, + { 0.129380, 0.067935, 0.305443 }, + { 0.135053, 0.068391, 0.315000 }, + { 0.140858, 0.068654, 0.324538 }, + { 0.146785, 0.068738, 0.334011 }, + { 0.152839, 0.068637, 0.343404 }, + { 0.159018, 0.068354, 0.352688 }, + { 0.165308, 0.067911, 0.361816 }, + { 0.171713, 0.067305, 0.370771 }, + { 0.178212, 0.066576, 0.379497 }, + { 0.184801, 0.065732, 0.387973 }, + { 0.191460, 0.064818, 0.396152 }, + { 0.198177, 0.063862, 0.404009 }, + { 0.204935, 0.062907, 0.411514 }, + { 0.211718, 0.061992, 0.418647 }, + { 0.218512, 0.061158, 0.425392 }, + { 0.225302, 0.060445, 0.431742 }, + { 0.232077, 0.059889, 0.437695 }, + { 0.238826, 0.059517, 0.443256 }, + { 0.245543, 0.059352, 0.448436 }, + { 0.252220, 0.059415, 0.453248 }, + { 0.258857, 0.059706, 0.457710 }, + { 0.265447, 0.060237, 0.461840 }, + { 0.271994, 0.060994, 0.465660 }, + { 0.278493, 0.061978, 0.469190 }, + { 0.284951, 0.063168, 0.472451 }, + { 0.291366, 0.064553, 0.475462 }, + { 0.297740, 0.066117, 0.478243 }, + { 0.304081, 0.067835, 0.480812 }, + { 0.310382, 0.069702, 0.483186 }, + { 0.316654, 0.071690, 0.485380 }, + { 0.322899, 0.073782, 0.487408 }, + { 0.329114, 0.075972, 0.489287 }, + { 0.335308, 0.078236, 0.491024 }, + { 0.341482, 0.080564, 0.492631 }, + { 0.347636, 0.082946, 0.494121 }, + { 0.353773, 0.085373, 0.495501 }, + { 0.359898, 0.087831, 0.496778 }, + { 0.366012, 0.090314, 0.497960 }, + { 0.372116, 0.092816, 0.499053 }, + { 0.378211, 0.095332, 0.500067 }, + { 0.384299, 0.097855, 0.501002 }, + { 0.390384, 0.100379, 0.501864 }, + { 0.396467, 0.102902, 0.502658 }, + { 0.402548, 0.105420, 0.503386 }, + { 0.408629, 0.107930, 0.504052 }, + { 0.414709, 0.110431, 0.504662 }, + { 0.420791, 0.112920, 0.505215 }, + { 0.426877, 0.115395, 0.505714 }, + { 0.432967, 0.117855, 0.506160 }, + { 0.439062, 0.120298, 0.506555 }, + { 0.445163, 0.122724, 0.506901 }, + { 0.451271, 0.125132, 0.507198 }, + { 0.457386, 0.127522, 0.507448 }, + { 0.463508, 0.129893, 0.507652 }, + { 0.469640, 0.132245, 0.507809 }, + { 0.475780, 0.134577, 0.507921 }, + { 0.481929, 0.136891, 0.507989 }, + { 0.488088, 0.139186, 0.508011 }, + { 0.494258, 0.141462, 0.507988 }, + { 0.500438, 0.143719, 0.507920 }, + { 0.506629, 0.145958, 0.507806 }, + { 0.512831, 0.148179, 0.507648 }, + { 0.519045, 0.150383, 0.507443 }, + { 0.525270, 0.152569, 0.507192 }, + { 0.531507, 0.154739, 0.506895 }, + { 0.537755, 0.156894, 0.506551 }, + { 0.544015, 0.159033, 0.506159 }, + { 0.550287, 0.161158, 0.505719 }, + { 0.556571, 0.163269, 0.505230 }, + { 0.562866, 0.165368, 0.504692 }, + { 0.569172, 0.167454, 0.504105 }, + { 0.575490, 0.169530, 0.503466 }, + { 0.581819, 0.171596, 0.502777 }, + { 0.588158, 0.173652, 0.502035 }, + { 0.594508, 0.175701, 0.501241 }, + { 0.600868, 0.177743, 0.500394 }, + { 0.607238, 0.179779, 0.499492 }, + { 0.613617, 0.181811, 0.498536 }, + { 0.620005, 0.183840, 0.497524 }, + { 0.626401, 0.185867, 0.496456 }, + { 0.632805, 0.187893, 0.495332 }, + { 0.639216, 0.189921, 0.494150 }, + { 0.645633, 0.191952, 0.492910 }, + { 0.652056, 0.193986, 0.491611 }, + { 0.658483, 0.196027, 0.490253 }, + { 0.664915, 0.198075, 0.488836 }, + { 0.671349, 0.200133, 0.487358 }, + { 0.677786, 0.202203, 0.485819 }, + { 0.684224, 0.204286, 0.484219 }, + { 0.690661, 0.206384, 0.482558 }, + { 0.697098, 0.208501, 0.480835 }, + { 0.703532, 0.210638, 0.479049 }, + { 0.709962, 0.212797, 0.477201 }, + { 0.716387, 0.214982, 0.475290 }, + { 0.722805, 0.217194, 0.473316 }, + { 0.729216, 0.219437, 0.471279 }, + { 0.735616, 0.221713, 0.469180 }, + { 0.742004, 0.224025, 0.467018 }, + { 0.748378, 0.226377, 0.464794 }, + { 0.754737, 0.228772, 0.462509 }, + { 0.761077, 0.231214, 0.460162 }, + { 0.767398, 0.233705, 0.457755 }, + { 0.773695, 0.236249, 0.455289 }, + { 0.779968, 0.238851, 0.452765 }, + { 0.786212, 0.241514, 0.450184 }, + { 0.792427, 0.244242, 0.447543 }, + { 0.798608, 0.247040, 0.444848 }, + { 0.804752, 0.249911, 0.442102 }, + { 0.810855, 0.252861, 0.439305 }, + { 0.816914, 0.255895, 0.436461 }, + { 0.822926, 0.259016, 0.433573 }, + { 0.828886, 0.262229, 0.430644 }, + { 0.834791, 0.265540, 0.427671 }, + { 0.840636, 0.268953, 0.424666 }, + { 0.846416, 0.272473, 0.421631 }, + { 0.852126, 0.276106, 0.418573 }, + { 0.857763, 0.279857, 0.415496 }, + { 0.863320, 0.283729, 0.412403 }, + { 0.868793, 0.287728, 0.409303 }, + { 0.874176, 0.291859, 0.406205 }, + { 0.879464, 0.296125, 0.403118 }, + { 0.884651, 0.300530, 0.400047 }, + { 0.889731, 0.305079, 0.397002 }, + { 0.894700, 0.309773, 0.393995 }, + { 0.899552, 0.314616, 0.391037 }, + { 0.904281, 0.319610, 0.388137 }, + { 0.908884, 0.324755, 0.385308 }, + { 0.913354, 0.330052, 0.382563 }, + { 0.917689, 0.335500, 0.379915 }, + { 0.921884, 0.341098, 0.377376 }, + { 0.925937, 0.346844, 0.374959 }, + { 0.929845, 0.352734, 0.372677 }, + { 0.933606, 0.358764, 0.370541 }, + { 0.937221, 0.364929, 0.368567 }, + { 0.940687, 0.371224, 0.366762 }, + { 0.944006, 0.377643, 0.365136 }, + { 0.947180, 0.384178, 0.363701 }, + { 0.950210, 0.390820, 0.362468 }, + { 0.953099, 0.397563, 0.361438 }, + { 0.955849, 0.404400, 0.360619 }, + { 0.958464, 0.411324, 0.360014 }, + { 0.960949, 0.418323, 0.359630 }, + { 0.963310, 0.425390, 0.359469 }, + { 0.965549, 0.432519, 0.359529 }, + { 0.967671, 0.439703, 0.359810 }, + { 0.969680, 0.446936, 0.360311 }, + { 0.971582, 0.454210, 0.361030 }, + { 0.973381, 0.461520, 0.361965 }, + { 0.975082, 0.468861, 0.363111 }, + { 0.976690, 0.476226, 0.364466 }, + { 0.978210, 0.483612, 0.366025 }, + { 0.979645, 0.491014, 0.367783 }, + { 0.981000, 0.498428, 0.369734 }, + { 0.982279, 0.505851, 0.371874 }, + { 0.983485, 0.513280, 0.374198 }, + { 0.984622, 0.520713, 0.376698 }, + { 0.985693, 0.528148, 0.379371 }, + { 0.986700, 0.535582, 0.382210 }, + { 0.987646, 0.543015, 0.385210 }, + { 0.988533, 0.550446, 0.388365 }, + { 0.989363, 0.557873, 0.391671 }, + { 0.990138, 0.565296, 0.395122 }, + { 0.990871, 0.572706, 0.398714 }, + { 0.991558, 0.580107, 0.402441 }, + { 0.992196, 0.587502, 0.406299 }, + { 0.992785, 0.594891, 0.410283 }, + { 0.993326, 0.602275, 0.414390 }, + { 0.993834, 0.609644, 0.418613 }, + { 0.994309, 0.616999, 0.422950 }, + { 0.994738, 0.624350, 0.427397 }, + { 0.995122, 0.631696, 0.431951 }, + { 0.995480, 0.639027, 0.436607 }, + { 0.995810, 0.646344, 0.441361 }, + { 0.996096, 0.653659, 0.446213 }, + { 0.996341, 0.660969, 0.451160 }, + { 0.996580, 0.668256, 0.456192 }, + { 0.996775, 0.675541, 0.461314 }, + { 0.996925, 0.682828, 0.466526 }, + { 0.997077, 0.690088, 0.471811 }, + { 0.997186, 0.697349, 0.477182 }, + { 0.997254, 0.704611, 0.482635 }, + { 0.997325, 0.711848, 0.488154 }, + { 0.997351, 0.719089, 0.493755 }, + { 0.997351, 0.726324, 0.499428 }, + { 0.997341, 0.733545, 0.505167 }, + { 0.997285, 0.740772, 0.510983 }, + { 0.997228, 0.747981, 0.516859 }, + { 0.997138, 0.755190, 0.522806 }, + { 0.997019, 0.762398, 0.528821 }, + { 0.996898, 0.769591, 0.534892 }, + { 0.996727, 0.776795, 0.541039 }, + { 0.996571, 0.783977, 0.547233 }, + { 0.996369, 0.791167, 0.553499 }, + { 0.996162, 0.798348, 0.559820 }, + { 0.995932, 0.805527, 0.566202 }, + { 0.995680, 0.812706, 0.572645 }, + { 0.995424, 0.819875, 0.579140 }, + { 0.995131, 0.827052, 0.585701 }, + { 0.994851, 0.834213, 0.592307 }, + { 0.994524, 0.841387, 0.598983 }, + { 0.994222, 0.848540, 0.605696 }, + { 0.993866, 0.855711, 0.612482 }, + { 0.993545, 0.862859, 0.619299 }, + { 0.993170, 0.870024, 0.626189 }, + { 0.992831, 0.877168, 0.633109 }, + { 0.992440, 0.884330, 0.640099 }, + { 0.992089, 0.891470, 0.647116 }, + { 0.991688, 0.898627, 0.654202 }, + { 0.991332, 0.905763, 0.661309 }, + { 0.990930, 0.912915, 0.668481 }, + { 0.990570, 0.920049, 0.675675 }, + { 0.990175, 0.927196, 0.682926 }, + { 0.989815, 0.934329, 0.690198 }, + { 0.989434, 0.941470, 0.697519 }, + { 0.989077, 0.948604, 0.704863 }, + { 0.988717, 0.955742, 0.712242 }, + { 0.988367, 0.962878, 0.719649 }, + { 0.988033, 0.970012, 0.727077 }, + { 0.987691, 0.977154, 0.734536 }, + { 0.987387, 0.984288, 0.742002 }, + { 0.987053, 0.991438, 0.749504 } +}; + +static double plasma_cm[256][3] = { + { 0.050383, 0.029803, 0.527975 }, + { 0.063536, 0.028426, 0.533124 }, + { 0.075353, 0.027206, 0.538007 }, + { 0.086222, 0.026125, 0.542658 }, + { 0.096379, 0.025165, 0.547103 }, + { 0.105980, 0.024309, 0.551368 }, + { 0.115124, 0.023556, 0.555468 }, + { 0.123903, 0.022878, 0.559423 }, + { 0.132381, 0.022258, 0.563250 }, + { 0.140603, 0.021687, 0.566959 }, + { 0.148607, 0.021154, 0.570562 }, + { 0.156421, 0.020651, 0.574065 }, + { 0.164070, 0.020171, 0.577478 }, + { 0.171574, 0.019706, 0.580806 }, + { 0.178950, 0.019252, 0.584054 }, + { 0.186213, 0.018803, 0.587228 }, + { 0.193374, 0.018354, 0.590330 }, + { 0.200445, 0.017902, 0.593364 }, + { 0.207435, 0.017442, 0.596333 }, + { 0.214350, 0.016973, 0.599239 }, + { 0.221197, 0.016497, 0.602083 }, + { 0.227983, 0.016007, 0.604867 }, + { 0.234715, 0.015502, 0.607592 }, + { 0.241396, 0.014979, 0.610259 }, + { 0.248032, 0.014439, 0.612868 }, + { 0.254627, 0.013882, 0.615419 }, + { 0.261183, 0.013308, 0.617911 }, + { 0.267703, 0.012716, 0.620346 }, + { 0.274191, 0.012109, 0.622722 }, + { 0.280648, 0.011488, 0.625038 }, + { 0.287076, 0.010855, 0.627295 }, + { 0.293478, 0.010213, 0.629490 }, + { 0.299855, 0.009561, 0.631624 }, + { 0.306210, 0.008902, 0.633694 }, + { 0.312543, 0.008239, 0.635700 }, + { 0.318856, 0.007576, 0.637640 }, + { 0.325150, 0.006915, 0.639512 }, + { 0.331426, 0.006261, 0.641316 }, + { 0.337683, 0.005618, 0.643049 }, + { 0.343925, 0.004991, 0.644710 }, + { 0.350150, 0.004382, 0.646298 }, + { 0.356359, 0.003798, 0.647810 }, + { 0.362553, 0.003243, 0.649245 }, + { 0.368733, 0.002724, 0.650601 }, + { 0.374897, 0.002245, 0.651876 }, + { 0.381047, 0.001814, 0.653068 }, + { 0.387183, 0.001434, 0.654177 }, + { 0.393304, 0.001114, 0.655199 }, + { 0.399411, 0.000859, 0.656133 }, + { 0.405503, 0.000678, 0.656977 }, + { 0.411580, 0.000577, 0.657730 }, + { 0.417642, 0.000564, 0.658390 }, + { 0.423689, 0.000646, 0.658956 }, + { 0.429719, 0.000831, 0.659425 }, + { 0.435734, 0.001127, 0.659797 }, + { 0.441732, 0.001540, 0.660069 }, + { 0.447714, 0.002080, 0.660240 }, + { 0.453677, 0.002755, 0.660310 }, + { 0.459623, 0.003574, 0.660277 }, + { 0.465550, 0.004545, 0.660139 }, + { 0.471457, 0.005678, 0.659897 }, + { 0.477344, 0.006980, 0.659549 }, + { 0.483210, 0.008460, 0.659095 }, + { 0.489055, 0.010127, 0.658534 }, + { 0.494877, 0.011990, 0.657865 }, + { 0.500678, 0.014055, 0.657088 }, + { 0.506454, 0.016333, 0.656202 }, + { 0.512206, 0.018833, 0.655209 }, + { 0.517933, 0.021563, 0.654109 }, + { 0.523633, 0.024532, 0.652901 }, + { 0.529306, 0.027747, 0.651586 }, + { 0.534952, 0.031217, 0.650165 }, + { 0.540570, 0.034950, 0.648640 }, + { 0.546157, 0.038954, 0.647010 }, + { 0.551715, 0.043136, 0.645277 }, + { 0.557243, 0.047331, 0.643443 }, + { 0.562738, 0.051545, 0.641509 }, + { 0.568201, 0.055778, 0.639477 }, + { 0.573632, 0.060028, 0.637349 }, + { 0.579029, 0.064296, 0.635126 }, + { 0.584391, 0.068579, 0.632812 }, + { 0.589719, 0.072878, 0.630408 }, + { 0.595011, 0.077190, 0.627917 }, + { 0.600266, 0.081516, 0.625342 }, + { 0.605485, 0.085854, 0.622686 }, + { 0.610667, 0.090204, 0.619951 }, + { 0.615812, 0.094564, 0.617140 }, + { 0.620919, 0.098934, 0.614257 }, + { 0.625987, 0.103312, 0.611305 }, + { 0.631017, 0.107699, 0.608287 }, + { 0.636008, 0.112092, 0.605205 }, + { 0.640959, 0.116492, 0.602065 }, + { 0.645872, 0.120898, 0.598867 }, + { 0.650746, 0.125309, 0.595617 }, + { 0.655580, 0.129725, 0.592317 }, + { 0.660374, 0.134144, 0.588971 }, + { 0.665129, 0.138566, 0.585582 }, + { 0.669845, 0.142992, 0.582154 }, + { 0.674522, 0.147419, 0.578688 }, + { 0.679160, 0.151848, 0.575189 }, + { 0.683758, 0.156278, 0.571660 }, + { 0.688318, 0.160709, 0.568103 }, + { 0.692840, 0.165141, 0.564522 }, + { 0.697324, 0.169573, 0.560919 }, + { 0.701769, 0.174005, 0.557296 }, + { 0.706178, 0.178437, 0.553657 }, + { 0.710549, 0.182868, 0.550004 }, + { 0.714883, 0.187299, 0.546338 }, + { 0.719181, 0.191729, 0.542663 }, + { 0.723444, 0.196158, 0.538981 }, + { 0.727670, 0.200586, 0.535293 }, + { 0.731862, 0.205013, 0.531601 }, + { 0.736019, 0.209439, 0.527908 }, + { 0.740143, 0.213864, 0.524216 }, + { 0.744232, 0.218288, 0.520524 }, + { 0.748289, 0.222711, 0.516834 }, + { 0.752312, 0.227133, 0.513149 }, + { 0.756304, 0.231555, 0.509468 }, + { 0.760264, 0.235976, 0.505794 }, + { 0.764193, 0.240396, 0.502126 }, + { 0.768090, 0.244817, 0.498465 }, + { 0.771958, 0.249237, 0.494813 }, + { 0.775796, 0.253658, 0.491171 }, + { 0.779604, 0.258078, 0.487539 }, + { 0.783383, 0.262500, 0.483918 }, + { 0.787133, 0.266922, 0.480307 }, + { 0.790855, 0.271345, 0.476706 }, + { 0.794549, 0.275770, 0.473117 }, + { 0.798216, 0.280197, 0.469538 }, + { 0.801855, 0.284626, 0.465971 }, + { 0.805467, 0.289057, 0.462415 }, + { 0.809052, 0.293491, 0.458870 }, + { 0.812612, 0.297928, 0.455338 }, + { 0.816144, 0.302368, 0.451816 }, + { 0.819651, 0.306812, 0.448306 }, + { 0.823132, 0.311261, 0.444806 }, + { 0.826588, 0.315714, 0.441316 }, + { 0.830018, 0.320172, 0.437836 }, + { 0.833422, 0.324635, 0.434366 }, + { 0.836801, 0.329105, 0.430905 }, + { 0.840155, 0.333580, 0.427455 }, + { 0.843484, 0.338062, 0.424013 }, + { 0.846788, 0.342551, 0.420579 }, + { 0.850066, 0.347048, 0.417153 }, + { 0.853319, 0.351553, 0.413734 }, + { 0.856547, 0.356066, 0.410322 }, + { 0.859750, 0.360588, 0.406917 }, + { 0.862927, 0.365119, 0.403519 }, + { 0.866078, 0.369660, 0.400126 }, + { 0.869203, 0.374212, 0.396738 }, + { 0.872303, 0.378774, 0.393355 }, + { 0.875376, 0.383347, 0.389976 }, + { 0.878423, 0.387932, 0.386600 }, + { 0.881443, 0.392529, 0.383229 }, + { 0.884436, 0.397139, 0.379860 }, + { 0.887402, 0.401762, 0.376494 }, + { 0.890340, 0.406398, 0.373130 }, + { 0.893250, 0.411048, 0.369768 }, + { 0.896131, 0.415712, 0.366407 }, + { 0.898984, 0.420392, 0.363047 }, + { 0.901807, 0.425087, 0.359688 }, + { 0.904601, 0.429797, 0.356329 }, + { 0.907365, 0.434524, 0.352970 }, + { 0.910098, 0.439268, 0.349610 }, + { 0.912800, 0.444029, 0.346251 }, + { 0.915471, 0.448807, 0.342890 }, + { 0.918109, 0.453603, 0.339529 }, + { 0.920714, 0.458417, 0.336166 }, + { 0.923287, 0.463251, 0.332801 }, + { 0.925825, 0.468103, 0.329435 }, + { 0.928329, 0.472975, 0.326067 }, + { 0.930798, 0.477867, 0.322697 }, + { 0.933232, 0.482780, 0.319325 }, + { 0.935630, 0.487712, 0.315952 }, + { 0.937990, 0.492667, 0.312575 }, + { 0.940313, 0.497642, 0.309197 }, + { 0.942598, 0.502639, 0.305816 }, + { 0.944844, 0.507658, 0.302433 }, + { 0.947051, 0.512699, 0.299049 }, + { 0.949217, 0.517763, 0.295662 }, + { 0.951344, 0.522850, 0.292275 }, + { 0.953428, 0.527960, 0.288883 }, + { 0.955470, 0.533093, 0.285490 }, + { 0.957469, 0.538250, 0.282096 }, + { 0.959424, 0.543431, 0.278701 }, + { 0.961336, 0.548636, 0.275305 }, + { 0.963203, 0.553865, 0.271909 }, + { 0.965024, 0.559118, 0.268513 }, + { 0.966798, 0.564396, 0.265118 }, + { 0.968526, 0.569700, 0.261721 }, + { 0.970205, 0.575028, 0.258325 }, + { 0.971835, 0.580382, 0.254931 }, + { 0.973416, 0.585761, 0.251540 }, + { 0.974947, 0.591165, 0.248151 }, + { 0.976428, 0.596595, 0.244767 }, + { 0.977856, 0.602051, 0.241387 }, + { 0.979233, 0.607532, 0.238013 }, + { 0.980556, 0.613039, 0.234646 }, + { 0.981826, 0.618572, 0.231287 }, + { 0.983041, 0.624131, 0.227937 }, + { 0.984199, 0.629718, 0.224595 }, + { 0.985301, 0.635330, 0.221265 }, + { 0.986345, 0.640969, 0.217948 }, + { 0.987332, 0.646633, 0.214648 }, + { 0.988260, 0.652325, 0.211364 }, + { 0.989128, 0.658043, 0.208100 }, + { 0.989935, 0.663787, 0.204859 }, + { 0.990681, 0.669558, 0.201642 }, + { 0.991365, 0.675355, 0.198453 }, + { 0.991985, 0.681179, 0.195295 }, + { 0.992541, 0.687030, 0.192170 }, + { 0.993032, 0.692907, 0.189084 }, + { 0.993456, 0.698810, 0.186041 }, + { 0.993814, 0.704741, 0.183043 }, + { 0.994103, 0.710698, 0.180097 }, + { 0.994324, 0.716681, 0.177208 }, + { 0.994474, 0.722691, 0.174381 }, + { 0.994553, 0.728728, 0.171622 }, + { 0.994561, 0.734791, 0.168938 }, + { 0.994495, 0.740880, 0.166335 }, + { 0.994355, 0.746995, 0.163821 }, + { 0.994141, 0.753137, 0.161404 }, + { 0.993851, 0.759304, 0.159092 }, + { 0.993482, 0.765499, 0.156891 }, + { 0.993033, 0.771720, 0.154808 }, + { 0.992505, 0.777967, 0.152855 }, + { 0.991897, 0.784239, 0.151042 }, + { 0.991209, 0.790537, 0.149377 }, + { 0.990439, 0.796859, 0.147870 }, + { 0.989587, 0.803205, 0.146529 }, + { 0.988648, 0.809579, 0.145357 }, + { 0.987621, 0.815978, 0.144363 }, + { 0.986509, 0.822401, 0.143557 }, + { 0.985314, 0.828846, 0.142945 }, + { 0.984031, 0.835315, 0.142528 }, + { 0.982653, 0.841812, 0.142303 }, + { 0.981190, 0.848329, 0.142279 }, + { 0.979644, 0.854866, 0.142453 }, + { 0.977995, 0.861432, 0.142808 }, + { 0.976265, 0.868016, 0.143351 }, + { 0.974443, 0.874622, 0.144061 }, + { 0.972530, 0.881250, 0.144923 }, + { 0.970533, 0.887896, 0.145919 }, + { 0.968443, 0.894564, 0.147014 }, + { 0.966271, 0.901249, 0.148180 }, + { 0.964021, 0.907950, 0.149370 }, + { 0.961681, 0.914672, 0.150520 }, + { 0.959276, 0.921407, 0.151566 }, + { 0.956808, 0.928152, 0.152409 }, + { 0.954287, 0.934908, 0.152921 }, + { 0.951726, 0.941671, 0.152925 }, + { 0.949151, 0.948435, 0.152178 }, + { 0.946602, 0.955190, 0.150328 }, + { 0.944152, 0.961916, 0.146861 }, + { 0.941896, 0.968590, 0.140956 }, + { 0.940015, 0.975158, 0.131326 } +}; + +static double viridis_cm[256][3] = { + { 0.267004, 0.004874, 0.329415 }, + { 0.268510, 0.009605, 0.335427 }, + { 0.269944, 0.014625, 0.341379 }, + { 0.271305, 0.019942, 0.347269 }, + { 0.272594, 0.025563, 0.353093 }, + { 0.273809, 0.031497, 0.358853 }, + { 0.274952, 0.037752, 0.364543 }, + { 0.276022, 0.044167, 0.370164 }, + { 0.277018, 0.050344, 0.375715 }, + { 0.277941, 0.056324, 0.381191 }, + { 0.278791, 0.062145, 0.386592 }, + { 0.279566, 0.067836, 0.391917 }, + { 0.280267, 0.073417, 0.397163 }, + { 0.280894, 0.078907, 0.402329 }, + { 0.281446, 0.084320, 0.407414 }, + { 0.281924, 0.089666, 0.412415 }, + { 0.282327, 0.094955, 0.417331 }, + { 0.282656, 0.100196, 0.422160 }, + { 0.282910, 0.105393, 0.426902 }, + { 0.283091, 0.110553, 0.431554 }, + { 0.283197, 0.115680, 0.436115 }, + { 0.283229, 0.120777, 0.440584 }, + { 0.283187, 0.125848, 0.444960 }, + { 0.283072, 0.130895, 0.449241 }, + { 0.282884, 0.135920, 0.453427 }, + { 0.282623, 0.140926, 0.457517 }, + { 0.282290, 0.145912, 0.461510 }, + { 0.281887, 0.150881, 0.465405 }, + { 0.281412, 0.155834, 0.469201 }, + { 0.280868, 0.160771, 0.472899 }, + { 0.280255, 0.165693, 0.476498 }, + { 0.279574, 0.170599, 0.479997 }, + { 0.278826, 0.175490, 0.483397 }, + { 0.278012, 0.180367, 0.486697 }, + { 0.277134, 0.185228, 0.489898 }, + { 0.276194, 0.190074, 0.493001 }, + { 0.275191, 0.194905, 0.496005 }, + { 0.274128, 0.199721, 0.498911 }, + { 0.273006, 0.204520, 0.501721 }, + { 0.271828, 0.209303, 0.504434 }, + { 0.270595, 0.214069, 0.507052 }, + { 0.269308, 0.218818, 0.509577 }, + { 0.267968, 0.223549, 0.512008 }, + { 0.266580, 0.228262, 0.514349 }, + { 0.265145, 0.232956, 0.516599 }, + { 0.263663, 0.237631, 0.518762 }, + { 0.262138, 0.242286, 0.520837 }, + { 0.260571, 0.246922, 0.522828 }, + { 0.258965, 0.251537, 0.524736 }, + { 0.257322, 0.256130, 0.526563 }, + { 0.255645, 0.260703, 0.528312 }, + { 0.253935, 0.265254, 0.529983 }, + { 0.252194, 0.269783, 0.531579 }, + { 0.250425, 0.274290, 0.533103 }, + { 0.248629, 0.278775, 0.534556 }, + { 0.246811, 0.283237, 0.535941 }, + { 0.244972, 0.287675, 0.537260 }, + { 0.243113, 0.292092, 0.538516 }, + { 0.241237, 0.296485, 0.539709 }, + { 0.239346, 0.300855, 0.540844 }, + { 0.237441, 0.305202, 0.541921 }, + { 0.235526, 0.309527, 0.542944 }, + { 0.233603, 0.313828, 0.543914 }, + { 0.231674, 0.318106, 0.544834 }, + { 0.229739, 0.322361, 0.545706 }, + { 0.227802, 0.326594, 0.546532 }, + { 0.225863, 0.330805, 0.547314 }, + { 0.223925, 0.334994, 0.548053 }, + { 0.221989, 0.339161, 0.548752 }, + { 0.220057, 0.343307, 0.549413 }, + { 0.218130, 0.347432, 0.550038 }, + { 0.216210, 0.351535, 0.550627 }, + { 0.214298, 0.355619, 0.551184 }, + { 0.212395, 0.359683, 0.551710 }, + { 0.210503, 0.363727, 0.552206 }, + { 0.208623, 0.367752, 0.552675 }, + { 0.206756, 0.371758, 0.553117 }, + { 0.204903, 0.375746, 0.553533 }, + { 0.203063, 0.379716, 0.553925 }, + { 0.201239, 0.383670, 0.554294 }, + { 0.199430, 0.387607, 0.554642 }, + { 0.197636, 0.391528, 0.554969 }, + { 0.195860, 0.395433, 0.555276 }, + { 0.194100, 0.399323, 0.555565 }, + { 0.192357, 0.403199, 0.555836 }, + { 0.190631, 0.407061, 0.556089 }, + { 0.188923, 0.410910, 0.556326 }, + { 0.187231, 0.414746, 0.556547 }, + { 0.185556, 0.418570, 0.556753 }, + { 0.183898, 0.422383, 0.556944 }, + { 0.182256, 0.426184, 0.557120 }, + { 0.180629, 0.429975, 0.557282 }, + { 0.179019, 0.433756, 0.557430 }, + { 0.177423, 0.437527, 0.557565 }, + { 0.175841, 0.441290, 0.557685 }, + { 0.174274, 0.445044, 0.557792 }, + { 0.172719, 0.448791, 0.557885 }, + { 0.171176, 0.452530, 0.557965 }, + { 0.169646, 0.456262, 0.558030 }, + { 0.168126, 0.459988, 0.558082 }, + { 0.166617, 0.463708, 0.558119 }, + { 0.165117, 0.467423, 0.558141 }, + { 0.163625, 0.471133, 0.558148 }, + { 0.162142, 0.474838, 0.558140 }, + { 0.160665, 0.478540, 0.558115 }, + { 0.159194, 0.482237, 0.558073 }, + { 0.157729, 0.485932, 0.558013 }, + { 0.156270, 0.489624, 0.557936 }, + { 0.154815, 0.493313, 0.557840 }, + { 0.153364, 0.497000, 0.557724 }, + { 0.151918, 0.500685, 0.557587 }, + { 0.150476, 0.504369, 0.557430 }, + { 0.149039, 0.508051, 0.557250 }, + { 0.147607, 0.511733, 0.557049 }, + { 0.146180, 0.515413, 0.556823 }, + { 0.144759, 0.519093, 0.556572 }, + { 0.143343, 0.522773, 0.556295 }, + { 0.141935, 0.526453, 0.555991 }, + { 0.140536, 0.530132, 0.555659 }, + { 0.139147, 0.533812, 0.555298 }, + { 0.137770, 0.537492, 0.554906 }, + { 0.136408, 0.541173, 0.554483 }, + { 0.135066, 0.544853, 0.554029 }, + { 0.133743, 0.548535, 0.553541 }, + { 0.132444, 0.552216, 0.553018 }, + { 0.131172, 0.555899, 0.552459 }, + { 0.129933, 0.559582, 0.551864 }, + { 0.128729, 0.563265, 0.551229 }, + { 0.127568, 0.566949, 0.550556 }, + { 0.126453, 0.570633, 0.549841 }, + { 0.125394, 0.574318, 0.549086 }, + { 0.124395, 0.578002, 0.548287 }, + { 0.123463, 0.581687, 0.547445 }, + { 0.122606, 0.585371, 0.546557 }, + { 0.121831, 0.589055, 0.545623 }, + { 0.121148, 0.592739, 0.544641 }, + { 0.120565, 0.596422, 0.543611 }, + { 0.120092, 0.600104, 0.542530 }, + { 0.119738, 0.603785, 0.541400 }, + { 0.119512, 0.607464, 0.540218 }, + { 0.119423, 0.611141, 0.538982 }, + { 0.119483, 0.614817, 0.537692 }, + { 0.119699, 0.618490, 0.536347 }, + { 0.120081, 0.622161, 0.534946 }, + { 0.120638, 0.625828, 0.533488 }, + { 0.121380, 0.629492, 0.531973 }, + { 0.122312, 0.633153, 0.530398 }, + { 0.123444, 0.636809, 0.528763 }, + { 0.124780, 0.640461, 0.527068 }, + { 0.126326, 0.644107, 0.525311 }, + { 0.128087, 0.647749, 0.523491 }, + { 0.130067, 0.651384, 0.521608 }, + { 0.132268, 0.655014, 0.519661 }, + { 0.134692, 0.658636, 0.517649 }, + { 0.137339, 0.662252, 0.515571 }, + { 0.140210, 0.665859, 0.513427 }, + { 0.143303, 0.669459, 0.511215 }, + { 0.146616, 0.673050, 0.508936 }, + { 0.150148, 0.676631, 0.506589 }, + { 0.153894, 0.680203, 0.504172 }, + { 0.157851, 0.683765, 0.501686 }, + { 0.162016, 0.687316, 0.499129 }, + { 0.166383, 0.690856, 0.496502 }, + { 0.170948, 0.694384, 0.493803 }, + { 0.175707, 0.697900, 0.491033 }, + { 0.180653, 0.701402, 0.488189 }, + { 0.185783, 0.704891, 0.485273 }, + { 0.191090, 0.708366, 0.482284 }, + { 0.196571, 0.711827, 0.479221 }, + { 0.202219, 0.715272, 0.476084 }, + { 0.208030, 0.718701, 0.472873 }, + { 0.214000, 0.722114, 0.469588 }, + { 0.220124, 0.725509, 0.466226 }, + { 0.226397, 0.728888, 0.462789 }, + { 0.232815, 0.732247, 0.459277 }, + { 0.239374, 0.735588, 0.455688 }, + { 0.246070, 0.738910, 0.452024 }, + { 0.252899, 0.742211, 0.448284 }, + { 0.259857, 0.745492, 0.444467 }, + { 0.266941, 0.748751, 0.440573 }, + { 0.274149, 0.751988, 0.436601 }, + { 0.281477, 0.755203, 0.432552 }, + { 0.288921, 0.758394, 0.428426 }, + { 0.296479, 0.761561, 0.424223 }, + { 0.304148, 0.764704, 0.419943 }, + { 0.311925, 0.767822, 0.415586 }, + { 0.319809, 0.770914, 0.411152 }, + { 0.327796, 0.773980, 0.406640 }, + { 0.335885, 0.777018, 0.402049 }, + { 0.344074, 0.780029, 0.397381 }, + { 0.352360, 0.783011, 0.392636 }, + { 0.360741, 0.785964, 0.387814 }, + { 0.369214, 0.788888, 0.382914 }, + { 0.377779, 0.791781, 0.377939 }, + { 0.386433, 0.794644, 0.372886 }, + { 0.395174, 0.797475, 0.367757 }, + { 0.404001, 0.800275, 0.362552 }, + { 0.412913, 0.803041, 0.357269 }, + { 0.421908, 0.805774, 0.351910 }, + { 0.430983, 0.808473, 0.346476 }, + { 0.440137, 0.811138, 0.340967 }, + { 0.449368, 0.813768, 0.335384 }, + { 0.458674, 0.816363, 0.329727 }, + { 0.468053, 0.818921, 0.323998 }, + { 0.477504, 0.821444, 0.318195 }, + { 0.487026, 0.823929, 0.312321 }, + { 0.496615, 0.826376, 0.306377 }, + { 0.506271, 0.828786, 0.300362 }, + { 0.515992, 0.831158, 0.294279 }, + { 0.525776, 0.833491, 0.288127 }, + { 0.535621, 0.835785, 0.281908 }, + { 0.545524, 0.838039, 0.275626 }, + { 0.555484, 0.840254, 0.269281 }, + { 0.565498, 0.842430, 0.262877 }, + { 0.575563, 0.844566, 0.256415 }, + { 0.585678, 0.846661, 0.249897 }, + { 0.595839, 0.848717, 0.243329 }, + { 0.606045, 0.850733, 0.236712 }, + { 0.616293, 0.852709, 0.230052 }, + { 0.626579, 0.854645, 0.223353 }, + { 0.636902, 0.856542, 0.216620 }, + { 0.647257, 0.858400, 0.209861 }, + { 0.657642, 0.860219, 0.203082 }, + { 0.668054, 0.861999, 0.196293 }, + { 0.678489, 0.863742, 0.189503 }, + { 0.688944, 0.865448, 0.182725 }, + { 0.699415, 0.867117, 0.175971 }, + { 0.709898, 0.868751, 0.169257 }, + { 0.720391, 0.870350, 0.162603 }, + { 0.730889, 0.871916, 0.156029 }, + { 0.741388, 0.873449, 0.149561 }, + { 0.751884, 0.874951, 0.143228 }, + { 0.762373, 0.876424, 0.137064 }, + { 0.772852, 0.877868, 0.131109 }, + { 0.783315, 0.879285, 0.125405 }, + { 0.793760, 0.880678, 0.120005 }, + { 0.804182, 0.882046, 0.114965 }, + { 0.814576, 0.883393, 0.110347 }, + { 0.824940, 0.884720, 0.106217 }, + { 0.835270, 0.886029, 0.102646 }, + { 0.845561, 0.887322, 0.099702 }, + { 0.855810, 0.888601, 0.097452 }, + { 0.866013, 0.889868, 0.095953 }, + { 0.876168, 0.891125, 0.095250 }, + { 0.886271, 0.892374, 0.095374 }, + { 0.896320, 0.893616, 0.096335 }, + { 0.906311, 0.894855, 0.098125 }, + { 0.916242, 0.896091, 0.100717 }, + { 0.926106, 0.897330, 0.104071 }, + { 0.935904, 0.898570, 0.108131 }, + { 0.945636, 0.899815, 0.112838 }, + { 0.955300, 0.901065, 0.118128 }, + { 0.964894, 0.902323, 0.123941 }, + { 0.974417, 0.903590, 0.130215 }, + { 0.983868, 0.904867, 0.136897 }, + { 0.993248, 0.906157, 0.143936 } +}; + +static double parula_cm[256][3] = { + { 0.2081, 0.1663, 0.5292 }, + { 0.2091, 0.1721, 0.5411 }, + { 0.2101, 0.1779, 0.553 }, + { 0.2109, 0.1837, 0.565 }, + { 0.2116, 0.1895, 0.5771 }, + { 0.2121, 0.1954, 0.5892 }, + { 0.2124, 0.2013, 0.6013 }, + { 0.2125, 0.2072, 0.6135 }, + { 0.2123, 0.2132, 0.6258 }, + { 0.2118, 0.2192, 0.6381 }, + { 0.2111, 0.2253, 0.6505 }, + { 0.2099, 0.2315, 0.6629 }, + { 0.2084, 0.2377, 0.6753 }, + { 0.2063, 0.244, 0.6878 }, + { 0.2038, 0.2503, 0.7003 }, + { 0.2006, 0.2568, 0.7129 }, + { 0.1968, 0.2632, 0.7255 }, + { 0.1921, 0.2698, 0.7381 }, + { 0.1867, 0.2764, 0.7507 }, + { 0.1802, 0.2832, 0.7634 }, + { 0.1728, 0.2902, 0.7762 }, + { 0.1641, 0.2975, 0.789 }, + { 0.1541, 0.3052, 0.8017 }, + { 0.1427, 0.3132, 0.8145 }, + { 0.1295, 0.3217, 0.8269 }, + { 0.1147, 0.3306, 0.8387 }, + { 0.0986, 0.3397, 0.8495 }, + { 0.0816, 0.3486, 0.8588 }, + { 0.0646, 0.3572, 0.8664 }, + { 0.0482, 0.3651, 0.8722 }, + { 0.0329, 0.3724, 0.8765 }, + { 0.0213, 0.3792, 0.8796 }, + { 0.0136, 0.3853, 0.8815 }, + { 0.0086, 0.3911, 0.8827 }, + { 0.006, 0.3965, 0.8833 }, + { 0.0051, 0.4017, 0.8834 }, + { 0.0054, 0.4066, 0.8831 }, + { 0.0067, 0.4113, 0.8825 }, + { 0.0089, 0.4159, 0.8816 }, + { 0.0116, 0.4203, 0.8805 }, + { 0.0148, 0.4246, 0.8793 }, + { 0.0184, 0.4288, 0.8779 }, + { 0.0223, 0.4329, 0.8763 }, + { 0.0264, 0.437, 0.8747 }, + { 0.0306, 0.441, 0.8729 }, + { 0.0349, 0.4449, 0.8711 }, + { 0.0394, 0.4488, 0.8692 }, + { 0.0437, 0.4526, 0.8672 }, + { 0.0477, 0.4564, 0.8652 }, + { 0.0514, 0.4602, 0.8632 }, + { 0.0549, 0.464, 0.8611 }, + { 0.0582, 0.4677, 0.8589 }, + { 0.0612, 0.4714, 0.8568 }, + { 0.064, 0.4751, 0.8546 }, + { 0.0666, 0.4788, 0.8525 }, + { 0.0689, 0.4825, 0.8503 }, + { 0.071, 0.4862, 0.8481 }, + { 0.0729, 0.4899, 0.846 }, + { 0.0746, 0.4937, 0.8439 }, + { 0.0761, 0.4974, 0.8418 }, + { 0.0773, 0.5012, 0.8398 }, + { 0.0782, 0.5051, 0.8378 }, + { 0.0789, 0.5089, 0.8359 }, + { 0.0794, 0.5129, 0.8341 }, + { 0.0795, 0.5169, 0.8324 }, + { 0.0793, 0.521, 0.8308 }, + { 0.0788, 0.5251, 0.8293 }, + { 0.0778, 0.5295, 0.828 }, + { 0.0764, 0.5339, 0.827 }, + { 0.0746, 0.5384, 0.8261 }, + { 0.0724, 0.5431, 0.8253 }, + { 0.0698, 0.5479, 0.8247 }, + { 0.0668, 0.5527, 0.8243 }, + { 0.0636, 0.5577, 0.8239 }, + { 0.06, 0.5627, 0.8237 }, + { 0.0562, 0.5677, 0.8234 }, + { 0.0523, 0.5727, 0.8231 }, + { 0.0484, 0.5777, 0.8228 }, + { 0.0445, 0.5826, 0.8223 }, + { 0.0408, 0.5874, 0.8217 }, + { 0.0372, 0.5922, 0.8209 }, + { 0.0342, 0.5968, 0.8198 }, + { 0.0317, 0.6012, 0.8186 }, + { 0.0296, 0.6055, 0.8171 }, + { 0.0279, 0.6097, 0.8154 }, + { 0.0265, 0.6137, 0.8135 }, + { 0.0255, 0.6176, 0.8114 }, + { 0.0248, 0.6214, 0.8091 }, + { 0.0243, 0.625, 0.8066 }, + { 0.0239, 0.6285, 0.8039 }, + { 0.0237, 0.6319, 0.801 }, + { 0.0235, 0.6352, 0.798 }, + { 0.0233, 0.6384, 0.7948 }, + { 0.0231, 0.6415, 0.7916 }, + { 0.023, 0.6445, 0.7881 }, + { 0.0229, 0.6474, 0.7846 }, + { 0.0227, 0.6503, 0.781, }, + { 0.0227, 0.6531, 0.7773 }, + { 0.0232, 0.6558, 0.7735 }, + { 0.0238, 0.6585, 0.7696 }, + { 0.0246, 0.6611, 0.7656 }, + { 0.0263, 0.6637, 0.7615 }, + { 0.0282, 0.6663, 0.7574 }, + { 0.0306, 0.6688, 0.7532 }, + { 0.0338, 0.6712, 0.749 }, + { 0.0373, 0.6737, 0.7446 }, + { 0.0418, 0.6761, 0.7402 }, + { 0.0467, 0.6784, 0.7358 }, + { 0.0516, 0.6808, 0.7313 }, + { 0.0574, 0.6831, 0.7267 }, + { 0.0629, 0.6854, 0.7221 }, + { 0.0692, 0.6877, 0.7173 }, + { 0.0755, 0.6899, 0.7126 }, + { 0.082, 0.6921, 0.7078 }, + { 0.0889, 0.6943, 0.7029 }, + { 0.0956, 0.6965, 0.6979 }, + { 0.1031, 0.6986, 0.6929 }, + { 0.1104, 0.7007, 0.6878 }, + { 0.118, 0.7028, 0.6827 }, + { 0.1258, 0.7049, 0.6775 }, + { 0.1335, 0.7069, 0.6723 }, + { 0.1418, 0.7089, 0.6669 }, + { 0.1499, 0.7109, 0.6616 }, + { 0.1585, 0.7129, 0.6561 }, + { 0.1671, 0.7148, 0.6507 }, + { 0.1758, 0.7168, 0.6451 }, + { 0.1849, 0.7186, 0.6395 }, + { 0.1938, 0.7205, 0.6338 }, + { 0.2033, 0.7223, 0.6281 }, + { 0.2128, 0.7241, 0.6223 }, + { 0.2224, 0.7259, 0.6165 }, + { 0.2324, 0.7275, 0.6107 }, + { 0.2423, 0.7292, 0.6048 }, + { 0.2527, 0.7308, 0.5988 }, + { 0.2631, 0.7324, 0.5929 }, + { 0.2735, 0.7339, 0.5869 }, + { 0.2845, 0.7354, 0.5809 }, + { 0.2953, 0.7368, 0.5749 }, + { 0.3064, 0.7381, 0.5689 }, + { 0.3177, 0.7394, 0.563 }, + { 0.3289, 0.7406, 0.557 }, + { 0.3405, 0.7417, 0.5512 }, + { 0.352, 0.7428, 0.5453 }, + { 0.3635, 0.7438, 0.5396 }, + { 0.3753, 0.7446, 0.5339 }, + { 0.3869, 0.7454, 0.5283 }, + { 0.3986, 0.7461, 0.5229 }, + { 0.4103, 0.7467, 0.5175 }, + { 0.4218, 0.7473, 0.5123 }, + { 0.4334, 0.7477, 0.5072 }, + { 0.4447, 0.7482, 0.5021 }, + { 0.4561, 0.7485, 0.4972 }, + { 0.4672, 0.7487, 0.4924 }, + { 0.4783, 0.7489, 0.4877 }, + { 0.4892, 0.7491, 0.4831 }, + { 0.5, 0.7491, 0.4786 }, + { 0.5106, 0.7492, 0.4741 }, + { 0.5212, 0.7492, 0.4698 }, + { 0.5315, 0.7491, 0.4655 }, + { 0.5418, 0.749, 0.4613 }, + { 0.5519, 0.7489, 0.4571 }, + { 0.5619, 0.7487, 0.4531 }, + { 0.5718, 0.7485, 0.449 }, + { 0.5816, 0.7482, 0.4451 }, + { 0.5913, 0.7479, 0.4412 }, + { 0.6009, 0.7476, 0.4374 }, + { 0.6103, 0.7473, 0.4335 }, + { 0.6197, 0.7469, 0.4298 }, + { 0.629, 0.7465, 0.4261 }, + { 0.6382, 0.746, 0.4224 }, + { 0.6473, 0.7456, 0.4188 }, + { 0.6564, 0.7451, 0.4152 }, + { 0.6653, 0.7446, 0.4116 }, + { 0.6742, 0.7441, 0.4081 }, + { 0.683, 0.7435, 0.4046 }, + { 0.6918, 0.743, 0.4011 }, + { 0.7004, 0.7424, 0.3976 }, + { 0.7091, 0.7418, 0.3942 }, + { 0.7176, 0.7412, 0.3908 }, + { 0.7261, 0.7405, 0.3874 }, + { 0.7346, 0.7399, 0.384 }, + { 0.743, 0.7392, 0.3806 }, + { 0.7513, 0.7385, 0.3773 }, + { 0.7596, 0.7378, 0.3739 }, + { 0.7679, 0.7372, 0.3706 }, + { 0.7761, 0.7364, 0.3673 }, + { 0.7843, 0.7357, 0.3639 }, + { 0.7924, 0.735, 0.3606 }, + { 0.8005, 0.7343, 0.3573 }, + { 0.8085, 0.7336, 0.3539 }, + { 0.8166, 0.7329, 0.3506 }, + { 0.8246, 0.7322, 0.3472 }, + { 0.8325, 0.7315, 0.3438 }, + { 0.8405, 0.7308, 0.3404 }, + { 0.8484, 0.7301, 0.337 }, + { 0.8563, 0.7294, 0.3336 }, + { 0.8642, 0.7288, 0.33 }, + { 0.872, 0.7282, 0.3265 }, + { 0.8798, 0.7276, 0.3229 }, + { 0.8877, 0.7271, 0.3193 }, + { 0.8954, 0.7266, 0.3156 }, + { 0.9032, 0.7262, 0.3117 }, + { 0.911, 0.7259, 0.3078 }, + { 0.9187, 0.7256, 0.3038 }, + { 0.9264, 0.7256, 0.2996 }, + { 0.9341, 0.7256, 0.2953 }, + { 0.9417, 0.7259, 0.2907 }, + { 0.9493, 0.7264, 0.2859 }, + { 0.9567, 0.7273, 0.2808 }, + { 0.9639, 0.7285, 0.2754 }, + { 0.9708, 0.7303, 0.2696 }, + { 0.9773, 0.7326, 0.2634 }, + { 0.9831, 0.7355, 0.257 }, + { 0.9882, 0.739, 0.2504 }, + { 0.9922, 0.7431, 0.2437 }, + { 0.9952, 0.7476, 0.2373 }, + { 0.9973, 0.7524, 0.231 }, + { 0.9986, 0.7573, 0.2251 }, + { 0.9991, 0.7624, 0.2195 }, + { 0.999, 0.7675, 0.2141 }, + { 0.9985, 0.7726, 0.209 }, + { 0.9976, 0.7778, 0.2042 }, + { 0.9964, 0.7829, 0.1995 }, + { 0.995, 0.788, 0.1949 }, + { 0.9933, 0.7931, 0.1905 }, + { 0.9914, 0.7981, 0.1863 }, + { 0.9894, 0.8032, 0.1821 }, + { 0.9873, 0.8083, 0.178 }, + { 0.9851, 0.8133, 0.174 }, + { 0.9828, 0.8184, 0.17 }, + { 0.9805, 0.8235, 0.1661 }, + { 0.9782, 0.8286, 0.1622 }, + { 0.9759, 0.8337, 0.1583 }, + { 0.9736, 0.8389, 0.1544 }, + { 0.9713, 0.8441, 0.1505 }, + { 0.9692, 0.8494, 0.1465 }, + { 0.9672, 0.8548, 0.1425 }, + { 0.9654, 0.8603, 0.1385 }, + { 0.9638, 0.8659, 0.1343 }, + { 0.9623, 0.8716, 0.1301 }, + { 0.9611, 0.8774, 0.1258 }, + { 0.96, 0.8834, 0.1215 }, + { 0.9593, 0.8895, 0.1171 }, + { 0.9588, 0.8958, 0.1126 }, + { 0.9586, 0.9022, 0.1082 }, + { 0.9587, 0.9088, 0.1036 }, + { 0.9591, 0.9155, 0.099 }, + { 0.9599, 0.9225, 0.0944 }, + { 0.961, 0.9296, 0.0897 }, + { 0.9624, 0.9368, 0.085 }, + { 0.9641, 0.9443, 0.0802 }, + { 0.9662, 0.9518, 0.0753 }, + { 0.9685, 0.9595, 0.0703 }, + { 0.971, 0.9673, 0.0651 }, + { 0.9736, 0.9752, 0.0597 }, + { 0.9763, 0.9831, 0.0538 } +}; +} + +template +IGL_INLINE void igl::colormap(const ColorMapType cm, const T x, T * rgb) +{ + return colormap(cm,x,rgb[0],rgb[1],rgb[2]); +} + +template +IGL_INLINE void igl::colormap( + const ColorMapType cm, const T x_in, T & r, T & g, T & b) +{ + switch (cm) + { + case COLOR_MAP_TYPE_INFERNO: + colormap(inferno_cm, x_in, r, g, b); + break; + case COLOR_MAP_TYPE_JET: + jet(x_in, r, g, b); + break; + case COLOR_MAP_TYPE_MAGMA: + colormap(magma_cm, x_in, r, g, b); + break; + case COLOR_MAP_TYPE_PARULA: + colormap(parula_cm, x_in, r, g, b); + break; + case COLOR_MAP_TYPE_PLASMA: + colormap(plasma_cm, x_in, r, g, b); + break; + case COLOR_MAP_TYPE_VIRIDIS: + colormap(viridis_cm, x_in, r, g, b); + break; + default: + throw std::invalid_argument("igl::colormap(): Selected colormap is unsupported!"); + break; + } +} + +template +IGL_INLINE void igl::colormap( + const double palette[256][3], const T x_in, T & r, T & g, T & b) +{ + static const unsigned int pal = 256; + const T zero = 0.0; + const T one = 1.0; + T x_in_clamped = static_cast(std::max(zero, std::min(one, x_in))); + + // simple rgb lerp from palette + unsigned int least = std::floor(x_in_clamped * static_cast(pal - 1)); + unsigned int most = std::ceil(x_in_clamped * static_cast(pal - 1)); + + T _r[2] = { static_cast(palette[least][0]), static_cast(palette[most][0]) }; + T _g[2] = { static_cast(palette[least][1]), static_cast(palette[most][1]) }; + T _b[2] = { static_cast(palette[least][2]), static_cast(palette[most][2]) }; + + T t = std::max(zero, std::min(one, static_cast(fmod(x_in_clamped * static_cast(pal), one)))); + + r = std::max(zero, std::min(one, (one - t) * _r[0] + t * _r[1])); + g = std::max(zero, std::min(one, (one - t) * _g[0] + t * _g[1])); + b = std::max(zero, std::min(one, (one - t) * _b[0] + t * _b[1])); +} + +template +IGL_INLINE void igl::colormap( + const ColorMapType cm, + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C) +{ + const double min_z = normalize ? Z.minCoeff() : 0; + const double max_z = normalize ? Z.maxCoeff() : 1; + return colormap(cm, Z, min_z, max_z, C); +} + +template +IGL_INLINE void igl::colormap( + const ColorMapType cm, + const Eigen::MatrixBase & Z, + const double min_z, + const double max_z, + Eigen::PlainObjectBase & C) +{ + C.resize(Z.rows(),3); + double denom = (max_z - min_z); + denom = (denom == 0) ? 1 : denom; + for(int r = 0; r < Z.rows(); ++r) { + colormap( + cm, + (typename DerivedC::Scalar)((-min_z + Z(r,0)) / denom), + C(r,0), + C(r,1), + C(r,2)); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::colormap(igl::ColorMapType, float, float*); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap(igl::ColorMapType, double, double&, double&, double&); +// generated by autoexplicit.sh +template void igl::colormap(igl::ColorMapType, double, double*); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); + +template void igl::colormap, Eigen::Matrix >(igl::ColorMapType, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/colormap.h b/src/igl/colormap.h new file mode 100644 index 000000000..1d93d73b1 --- /dev/null +++ b/src/igl/colormap.h @@ -0,0 +1,76 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Joe Graus , Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COLORMAP_H +#define IGL_COLORMAP_H +#include "igl_inline.h" + +#include + +namespace igl { + + enum ColorMapType + { + COLOR_MAP_TYPE_INFERNO = 0, + COLOR_MAP_TYPE_JET = 1, + COLOR_MAP_TYPE_MAGMA = 2, + COLOR_MAP_TYPE_PARULA = 3, + COLOR_MAP_TYPE_PLASMA = 4, + COLOR_MAP_TYPE_VIRIDIS = 5, + NUM_COLOR_MAP_TYPES = 6 + }; + // Comput [r,g,b] values of the selected colormap for + // a given factor f between 0 and 1 + // + // Inputs: + // c colormap enum + // f factor determining color value as if 0 was min and 1 was max + // Outputs: + // rgb red, green, blue value + template + IGL_INLINE void colormap(const ColorMapType cm, const T f, T * rgb); + // Outputs: + // r red value + // g green value + // b blue value + template + IGL_INLINE void colormap(const ColorMapType cm, const T f, T & r, T & g, T & b); + // Inputs: + // palette 256 by 3 array of color values + template + IGL_INLINE void colormap( + const double palette[256][3], const T x_in, T & r, T & g, T & b); + // Inputs: + // cm selected colormap palette to interpolate from + // Z #Z list of factors + // normalize whether to normalize Z to be tightly between [0,1] + // Outputs: + // C #C by 3 list of rgb colors + template + IGL_INLINE void colormap( + const ColorMapType cm, + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C); + // Inputs: + // min_z value at "0" + // max_z value at "1" + template + IGL_INLINE void colormap( + const ColorMapType cm, + const Eigen::MatrixBase & Z, + const double min_Z, + const double max_Z, + Eigen::PlainObjectBase & C); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "colormap.cpp" +#endif + +#endif diff --git a/src/igl/column_to_quats.cpp b/src/igl/column_to_quats.cpp new file mode 100644 index 000000000..f87adab06 --- /dev/null +++ b/src/igl/column_to_quats.cpp @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "column_to_quats.h" +IGL_INLINE bool igl::column_to_quats( + const Eigen::VectorXd & Q, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ) +{ + using namespace Eigen; + if(Q.size() % 4 != 0) + { + return false; + } + const int nQ = Q.size()/4; + vQ.resize(nQ); + for(int q=0;q +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COLUMN_TO_QUATS_H +#define IGL_COLUMN_TO_QUATS_H +#include "igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + // "Columnize" a list of quaternions (q1x,q1y,q1z,q1w,q2x,q2y,q2z,q2w,...) + // + // Inputs: + // Q n*4-long list of coefficients + // Outputs: + // vQ n-long list of quaternions + // Returns false if n%4!=0 + IGL_INLINE bool column_to_quats( + const Eigen::VectorXd & Q, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ); +} + +#ifndef IGL_STATIC_LIBRARY +# include "column_to_quats.cpp" +#endif + +#endif diff --git a/src/igl/columnize.cpp b/src/igl/columnize.cpp new file mode 100644 index 000000000..29e91c669 --- /dev/null +++ b/src/igl/columnize.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "columnize.h" +#include + +template +IGL_INLINE void igl::columnize( + const Eigen::PlainObjectBase & A, + const int k, + const int dim, + Eigen::PlainObjectBase & B) +{ + // Eigen matrices must be 2d so dim must be only 1 or 2 + assert(dim == 1 || dim == 2); + + // block height, width, and number of blocks + int m,n; + if(dim == 1) + { + m = A.rows()/k; + assert(m*(int)k == (int)A.rows()); + n = A.cols(); + }else// dim == 2 + { + m = A.rows(); + n = A.cols()/k; + assert(n*(int)k == (int)A.cols()); + } + + // resize output + B.resize(A.rows()*A.cols(),1); + + for(int b = 0;b<(int)k;b++) + { + for(int i = 0;i, Eigen::Matrix >(Eigen::PlainObjectBase > const&, int, int, Eigen::PlainObjectBase >&); +template void igl::columnize, Eigen::Matrix >(Eigen::PlainObjectBase > const&, int, int, Eigen::PlainObjectBase >&); +template void igl::columnize, Eigen::Matrix >(Eigen::PlainObjectBase > const&, int, int, Eigen::PlainObjectBase >&); +template void igl::columnize, Eigen::Matrix >(Eigen::PlainObjectBase > const&, int, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/columnize.h b/src/igl/columnize.h new file mode 100644 index 000000000..1bb453af1 --- /dev/null +++ b/src/igl/columnize.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COLUMNIZE_H +#define IGL_COLUMNIZE_H +#include "igl_inline.h" + +#include +namespace igl +{ + // "Columnize" a stack of block matrices. If A = [A1,A2,A3,...,Ak] with each A* + // an m by n block then this produces the column vector whose entries are + // B(j*m*k+i*k+b) = A(i,b*n+j); + // or if A = [A1;A2;...;Ak] then + // B(j*m*k+i*k+b) = A(i+b*m,j); + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // A m*k by n (dim: 1) or m by n*k (dim: 2) eigen Matrix of type T values + // k number of blocks + // dim dimension in which blocks are stacked + // Output + // B m*n*k eigen vector of type T values, + // + // See also: transpose_blocks + template + IGL_INLINE void columnize( + const Eigen::PlainObjectBase & A, + const int k, + const int dim, + Eigen::PlainObjectBase & B); +} +#ifndef IGL_STATIC_LIBRARY +# include "columnize.cpp" +#endif +#endif diff --git a/src/igl/comb_cross_field.cpp b/src/igl/comb_cross_field.cpp new file mode 100644 index 000000000..c277b056d --- /dev/null +++ b/src/igl/comb_cross_field.cpp @@ -0,0 +1,148 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "comb_cross_field.h" + +#include +#include +#include +#include "per_face_normals.h" +#include "is_border_vertex.h" +#include "rotation_matrix_from_directions.h" + +#include "triangle_triangle_adjacency.h" + +namespace igl { + template + class Comb + { + public: + + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &PD1; + const Eigen::PlainObjectBase &PD2; + DerivedV N; + + private: + // internal + DerivedF TT; + DerivedF TTi; + + + private: + + + static inline double Sign(double a){return (double)((a>0)?+1:-1);} + + + private: + + // returns the 90 deg rotation of a (around n) most similar to target b + /// a and b should be in the same plane orthogonal to N + static inline Eigen::Matrix K_PI_new(const Eigen::Matrix& a, + const Eigen::Matrix& b, + const Eigen::Matrix& n) + { + Eigen::Matrix c = (a.cross(n)).normalized(); + typename DerivedV::Scalar scorea = a.dot(b); + typename DerivedV::Scalar scorec = c.dot(b); + if (fabs(scorea)>=fabs(scorec)) + return a*Sign(scorea); + else + return c*Sign(scorec); + } + + + + public: + inline Comb(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_PD1, + const Eigen::PlainObjectBase &_PD2 + ): + V(_V), + F(_F), + PD1(_PD1), + PD2(_PD2) + { + igl::per_face_normals(V,F,N); + igl::triangle_triangle_adjacency(F,TT,TTi); + } + inline void comb(Eigen::PlainObjectBase &PD1out, + Eigen::PlainObjectBase &PD2out) + { +// PD1out = PD1; +// PD2out = PD2; + PD1out.setZero(F.rows(),3);PD1out< d; + + d.push_back(0); + mark(0) = true; + + while (!d.empty()) + { + int f0 = d.at(0); + d.pop_front(); + for (int k=0; k<3; k++) + { + int f1 = TT(f0,k); + if (f1==-1) continue; + if (mark(f1)) continue; + + Eigen::Matrix dir0 = PD1out.row(f0); + Eigen::Matrix dir1 = PD1out.row(f1); + Eigen::Matrix n0 = N.row(f0); + Eigen::Matrix n1 = N.row(f1); + + + Eigen::Matrix dir0Rot = igl::rotation_matrix_from_directions(n0, n1)*dir0; + dir0Rot.normalize(); + Eigen::Matrix targD = K_PI_new(dir1,dir0Rot,n1); + + PD1out.row(f1) = targD; + PD2out.row(f1) = n1.cross(targD).normalized(); + + mark(f1) = true; + d.push_back(f1); + + } + } + + // everything should be marked + for (int i=0; i +IGL_INLINE void igl::comb_cross_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + Eigen::PlainObjectBase &PD1out, + Eigen::PlainObjectBase &PD2out) +{ + igl::Comb cmb(V, F, PD1, PD2); + cmb.comb(PD1out, PD2out); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::comb_cross_field, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::comb_cross_field, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/comb_cross_field.h b/src/igl/comb_cross_field.h new file mode 100644 index 000000000..fa3b57648 --- /dev/null +++ b/src/igl/comb_cross_field.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COMB_CROSS_FIELD_H +#define IGL_COMB_CROSS_FIELD_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes principal matchings of the vectors of a cross field across face edges, + // and generates a combed cross field defined on the mesh faces + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 4 eigen Matrix of face (quad) indices + // PD1in #F by 3 eigen Matrix of the first per face cross field vector + // PD2in #F by 3 eigen Matrix of the second per face cross field vector + // Output: + // PD1out #F by 3 eigen Matrix of the first combed cross field vector + // PD2out #F by 3 eigen Matrix of the second combed cross field vector + // + + + template + IGL_INLINE void comb_cross_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1in, + const Eigen::PlainObjectBase &PD2in, + Eigen::PlainObjectBase &PD1out, + Eigen::PlainObjectBase &PD2out); +} +#ifndef IGL_STATIC_LIBRARY +#include "comb_cross_field.cpp" +#endif + +#endif diff --git a/src/igl/comb_frame_field.cpp b/src/igl/comb_frame_field.cpp new file mode 100644 index 000000000..f8afe56e9 --- /dev/null +++ b/src/igl/comb_frame_field.cpp @@ -0,0 +1,82 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifdef WIN32 + #define _USE_MATH_DEFINES +#endif +#include + +#include "comb_frame_field.h" +#include "local_basis.h" +#include "PI.h" + +template +IGL_INLINE void igl::comb_frame_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + const Eigen::PlainObjectBase &BIS1_combed, + const Eigen::PlainObjectBase &BIS2_combed, + Eigen::PlainObjectBase &PD1_combed, + Eigen::PlainObjectBase &PD2_combed) +{ + DerivedV B1, B2, B3; + igl::local_basis(V,F,B1,B2,B3); + + PD1_combed.resize(BIS1_combed.rows(),3); + PD2_combed.resize(BIS2_combed.rows(),3); + + for (unsigned i=0; i DIRs; + DIRs << + PD1.row(i), + -PD1.row(i), + PD2.row(i), + -PD2.row(i); + + std::vector a(4); + + + double a_combed = atan2(B2.row(i).dot(BIS1_combed.row(i)),B1.row(i).dot(BIS1_combed.row(i))); + + // center on the combed sector center + for (unsigned j=0;j<4;++j) + { + a[j] = atan2(B2.row(i).dot(DIRs.row(j)),B1.row(i).dot(DIRs.row(j))) - a_combed; + //make it positive by adding some multiple of 2pi + a[j] += std::ceil (std::max(0., -a[j]) / (igl::PI*2.)) * (igl::PI*2.); + //take modulo 2pi + a[j] = fmod(a[j], (igl::PI*2.)); + } + // now the max is u and the min is v + + int m = std::min_element(a.begin(),a.end())-a.begin(); + int M = std::max_element(a.begin(),a.end())-a.begin(); + + assert( + ((m>=0 && m<=1) && (M>=2 && M<=3)) + || + ((m>=2 && m<=3) && (M>=0 && M<=1)) + ); + + PD1_combed.row(i) = DIRs.row(m); + PD2_combed.row(i) = DIRs.row(M); + + } + + + // PD1_combed = BIS1_combed; + // PD2_combed = BIS2_combed; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::comb_frame_field, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::comb_frame_field, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/comb_frame_field.h b/src/igl/comb_frame_field.h new file mode 100644 index 000000000..4691dc19d --- /dev/null +++ b/src/igl/comb_frame_field.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COMB_FRAME_FIELD_H +#define IGL_COMB_FRAME_FIELD_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes principal matchings of the vectors of a frame field across face edges, + // and generates a combed frame field defined on the mesh faces. This makes use of a + // combed cross field generated by combing the field created by the bisectors of the + // frame field. + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 4 eigen Matrix of face (quad) indices + // PD1 #F by 3 eigen Matrix of the first per face cross field vector + // PD2 #F by 3 eigen Matrix of the second per face cross field vector + // BIS1_combed #F by 3 eigen Matrix of the first combed bisector field vector + // BIS2_combed #F by 3 eigen Matrix of the second combed bisector field vector + // Output: + // PD1_combed #F by 3 eigen Matrix of the first combed cross field vector + // PD2_combed #F by 3 eigen Matrix of the second combed cross field vector + // + + + template + IGL_INLINE void comb_frame_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + const Eigen::PlainObjectBase &BIS1_combed, + const Eigen::PlainObjectBase &BIS2_combed, + Eigen::PlainObjectBase &PD1_combed, + Eigen::PlainObjectBase &PD2_combed); +} +#ifndef IGL_STATIC_LIBRARY +#include "comb_frame_field.cpp" +#endif + +#endif diff --git a/src/igl/comb_line_field.cpp b/src/igl/comb_line_field.cpp new file mode 100644 index 000000000..66899319a --- /dev/null +++ b/src/igl/comb_line_field.cpp @@ -0,0 +1,132 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Nico Pietroni +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "comb_line_field.h" + +#include +#include +#include "per_face_normals.h" +#include "is_border_vertex.h" +#include "rotation_matrix_from_directions.h" + +#include "triangle_triangle_adjacency.h" + +namespace igl { +template +class CombLine +{ +public: + + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &PD1; + DerivedV N; + +private: + // internal + DerivedF TT; + DerivedF TTi; + + +private: + + + static inline double Sign(double a){return (double)((a>0)?+1:-1);} + + +private: + + // returns the 180 deg rotation of a (around n) most similar to target b + // a and b should be in the same plane orthogonal to N + static inline Eigen::Matrix K_PI_line(const Eigen::Matrix& a, + const Eigen::Matrix& b) + { + typename DerivedV::Scalar scorea = a.dot(b); + if (scorea<0) + return -a; + else + return a; + } + + + +public: + + inline CombLine(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_PD1): + V(_V), + F(_F), + PD1(_PD1) + { + igl::per_face_normals(V,F,N); + igl::triangle_triangle_adjacency(F,TT,TTi); + } + + inline void comb(Eigen::PlainObjectBase &PD1out) + { + PD1out.setZero(F.rows(),3);PD1out< d; + + d.push_back(0); + mark(0) = true; + + while (!d.empty()) + { + int f0 = d.at(0); + d.pop_front(); + for (int k=0; k<3; k++) + { + int f1 = TT(f0,k); + if (f1==-1) continue; + if (mark(f1)) continue; + + Eigen::Matrix dir0 = PD1out.row(f0); + Eigen::Matrix dir1 = PD1out.row(f1); + Eigen::Matrix n0 = N.row(f0); + Eigen::Matrix n1 = N.row(f1); + + Eigen::Matrix dir0Rot = igl::rotation_matrix_from_directions(n0, n1)*dir0; + dir0Rot.normalize(); + Eigen::Matrix targD = K_PI_line(dir1,dir0Rot); + + PD1out.row(f1) = targD; + //PD2out.row(f1) = n1.cross(targD).normalized(); + + mark(f1) = true; + d.push_back(f1); + + } + } + + // everything should be marked + for (int i=0; i +IGL_INLINE void igl::comb_line_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + Eigen::PlainObjectBase &PD1out) +{ + igl::CombLine cmb(V, F, PD1); + cmb.comb(PD1out); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/comb_line_field.h b/src/igl/comb_line_field.h new file mode 100644 index 000000000..e2f22b37f --- /dev/null +++ b/src/igl/comb_line_field.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Nico Pietroni +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COMB_LINE_FIELD_H +#define IGL_COMB_LINE_FIELD_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes principal matchings of the vectors of a cross field across face edges, + // and generates a combed cross field defined on the mesh faces + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 4 eigen Matrix of face (quad) indices + // PD1in #F by 3 eigen Matrix of the first per face cross field vector + // PD2in #F by 3 eigen Matrix of the second per face cross field vector + // Output: + // PD1out #F by 3 eigen Matrix of the first combed cross field vector + // PD2out #F by 3 eigen Matrix of the second combed cross field vector + // + + + template + IGL_INLINE void comb_line_field(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1in, + Eigen::PlainObjectBase &PD1out); +} +#ifndef IGL_STATIC_LIBRARY +#include "comb_line_field.cpp" +#endif + +#endif diff --git a/src/igl/combine.cpp b/src/igl/combine.cpp new file mode 100644 index 000000000..7e0af151a --- /dev/null +++ b/src/igl/combine.cpp @@ -0,0 +1,97 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "combine.h" +#include + +template < + typename DerivedVV, + typename DerivedFF, + typename DerivedV, + typename DerivedF, + typename DerivedVsizes, + typename DerivedFsizes> +IGL_INLINE void igl::combine( + const std::vector & VV, + const std::vector & FF, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & Vsizes, + Eigen::PlainObjectBase & Fsizes) +{ + assert(VV.size() == FF.size() && + "Lists of verex lists and face lists should be same size"); + Vsizes.resize(VV.size()); + Fsizes.resize(FF.size()); + // Dimension of vertex positions + const int dim = VV.size() > 0 ? VV[0].cols() : 0; + // Simplex/element size + const int ss = FF.size() > 0 ? FF[0].cols() : 0; + int n = 0; + int m = 0; + for(int i = 0;i0) + { + F.block(kf,0,mi,ss) = Fi.array()+kv; + } + kf+=mi; + if(Vi.size() >0) + { + V.block(kv,0,ni,dim) = Vi; + } + kv+=ni; + } + assert(kv == V.rows()); + assert(kf == F.rows()); + } +} + +template < + typename DerivedVV, + typename DerivedFF, + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::combine( + const std::vector & VV, + const std::vector & FF, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + Eigen::VectorXi Vsizes,Fsizes; + return igl::combine(VV,FF,V,F,Vsizes,Fsizes); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::combine, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::vector, std::allocator > > const&, std::vector, std::allocator > > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::combine, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::vector, std::allocator > > const&, std::vector, std::allocator > > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::combine, Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(class std::vector,class std::allocator > > const &,class std::vector,class std::allocator > > const &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &); +template void igl::combine, Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(class std::vector,class std::allocator > > const &,class std::vector,class std::allocator > > const &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/combine.h b/src/igl/combine.h new file mode 100644 index 000000000..ff5e1ae34 --- /dev/null +++ b/src/igl/combine.h @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COMBINE_H +#define IGL_COMBINE_H + +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Concatenate k meshes into a single >=k connected component mesh with a + // single vertex list and face list. Similar to Maya's Combine operation. + // + // Inputs: + // VV k-long list of lists of mesh vertex positions + // FF k-long list of lists of mesh face indices so that FF[i] indexes + // VV[i] + // Outputs: + // V VV[0].rows()+...+VV[k-1].rows() by VV[0].cols() list of mesh + // vertex positions + // F FF[0].rows()+...+FF[k-1].rows() by FF[0].cols() list of mesh faces + // indices into V + // Vsizes k list so that Vsizes(i) is the #vertices in the ith input + // Fsizes k list so that Fsizes(i) is the #faces in the ith input + // Example: + // // Suppose you have mesh A (VA,FA) and mesh B (VB,FB) + // igl::combine({VA,VB},{FA,FB},V,F); + // + // + template < + typename DerivedVV, + typename DerivedFF, + typename DerivedV, + typename DerivedF, + typename DerivedVsizes, + typename DerivedFsizes> + IGL_INLINE void combine( + const std::vector & VV, + const std::vector & FF, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & Vsizes, + Eigen::PlainObjectBase & Fsizes); + template < + typename DerivedVV, + typename DerivedFF, + typename DerivedV, + typename DerivedF> + IGL_INLINE void combine( + const std::vector & VV, + const std::vector & FF, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "combine.cpp" +#endif +#endif diff --git a/src/igl/components.cpp b/src/igl/components.cpp new file mode 100644 index 000000000..253186889 --- /dev/null +++ b/src/igl/components.cpp @@ -0,0 +1,98 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "components.h" +#include "adjacency_matrix.h" +#include +#include + +template +IGL_INLINE void igl::components( + const Eigen::SparseMatrix & A, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & counts) +{ + using namespace Eigen; + using namespace std; + assert(A.rows() == A.cols() && "A should be square."); + const size_t n = A.rows(); + Array seen = Array::Zero(n,1); + C.resize(n,1); + typename DerivedC::Scalar id = 0; + vector vcounts; + // breadth first search + for(int k=0; k Q; + Q.push(k); + vcounts.push_back(0); + while(!Q.empty()) + { + const int f = Q.front(); + Q.pop(); + if(seen(f)) + { + continue; + } + seen(f) = true; + C(f,0) = id; + vcounts[id]++; + // Iterate over inside + for(typename SparseMatrix::InnerIterator it (A,f); it; ++it) + { + const int g = it.index(); + if(!seen(g) && it.value()) + { + Q.push(g); + } + } + } + id++; + } + assert((size_t) id == vcounts.size()); + const size_t ncc = vcounts.size(); + assert((size_t)C.maxCoeff()+1 == ncc); + counts.resize(ncc,1); + for(size_t i = 0;i +IGL_INLINE void igl::components( + const Eigen::SparseMatrix & A, + Eigen::PlainObjectBase & C) +{ + Eigen::VectorXi counts; + return components(A,C,counts); +} + +template +IGL_INLINE void igl::components( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & C) +{ + Eigen::SparseMatrix A; + adjacency_matrix(F,A); + return components(A,C); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::components >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase >&); +template void igl::components, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::components >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase >&); +template void igl::components >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase >&); +template void igl::components, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::components, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/components.h b/src/igl/components.h new file mode 100644 index 000000000..d43a64c89 --- /dev/null +++ b/src/igl/components.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COMPONENTS_H +#define IGL_COMPONENTS_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Compute connected components of a graph represented by an adjacency + // matrix. This version is faster than the previous version using boost. + // + // Inputs: + // A n by n adjacency matrix + // Outputs: + // C n list of component ids (starting with 0) + // counts #components list of counts for each component + // + template + IGL_INLINE void components( + const Eigen::SparseMatrix & A, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & counts); + template + IGL_INLINE void components( + const Eigen::SparseMatrix & A, + Eigen::PlainObjectBase & C); + // Ditto but for mesh faces as input. This computes connected components of + // **vertices** where **edges** establish connectivity. + // + // Inputs: + // F n by 3 list of triangle indices + // Outputs: + // C max(F) list of component ids + template + IGL_INLINE void components( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & C); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "components.cpp" +#endif + +#endif + diff --git a/src/igl/compute_frame_field_bisectors.cpp b/src/igl/compute_frame_field_bisectors.cpp new file mode 100644 index 000000000..e3c47c608 --- /dev/null +++ b/src/igl/compute_frame_field_bisectors.cpp @@ -0,0 +1,85 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifdef WIN32 + #define _USE_MATH_DEFINES +#endif +#include + +#include "compute_frame_field_bisectors.h" +#include "igl/local_basis.h" +#include "PI.h" + +template +IGL_INLINE void igl::compute_frame_field_bisectors( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& B1, + const Eigen::PlainObjectBase& B2, + const Eigen::PlainObjectBase& PD1, + const Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& BIS1, + Eigen::PlainObjectBase& BIS2) +{ + BIS1.resize(PD1.rows(),3); + BIS2.resize(PD1.rows(),3); + + for (unsigned i=0; i +IGL_INLINE void igl::compute_frame_field_bisectors( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& PD1, + const Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& BIS1, + Eigen::PlainObjectBase& BIS2) +{ + DerivedV B1, B2, B3; + igl::local_basis(V,F,B1,B2,B3); + + compute_frame_field_bisectors( V, F, B1, B2, PD1, PD2, BIS1, BIS2); + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::compute_frame_field_bisectors, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::compute_frame_field_bisectors, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/compute_frame_field_bisectors.h b/src/igl/compute_frame_field_bisectors.h new file mode 100644 index 000000000..4c853febf --- /dev/null +++ b/src/igl/compute_frame_field_bisectors.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COMPUTE_FRAME_FIELD_BISECTORS_H +#define IGL_COMPUTE_FRAME_FIELD_BISECTORS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute bisectors of a frame field defined on mesh faces + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // B1 #F by 3 eigen Matrix of face (triangle) base vector 1 + // B2 #F by 3 eigen Matrix of face (triangle) base vector 2 + // PD1 #F by 3 eigen Matrix of the first per face frame field vector + // PD2 #F by 3 eigen Matrix of the second per face frame field vector + // Output: + // BIS1 #F by 3 eigen Matrix of the first per face frame field bisector + // BIS2 #F by 3 eigen Matrix of the second per face frame field bisector + // + template + IGL_INLINE void compute_frame_field_bisectors( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& B1, + const Eigen::PlainObjectBase& B2, + const Eigen::PlainObjectBase& PD1, + const Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& BIS1, + Eigen::PlainObjectBase& BIS2); + + // Wrapper without given basis vectors. + template + IGL_INLINE void compute_frame_field_bisectors( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& PD1, + const Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& BIS1, + Eigen::PlainObjectBase& BIS2); +} + +#ifndef IGL_STATIC_LIBRARY +# include "compute_frame_field_bisectors.cpp" +#endif + +#endif diff --git a/src/igl/connect_boundary_to_infinity.cpp b/src/igl/connect_boundary_to_infinity.cpp new file mode 100644 index 000000000..627b0b9b6 --- /dev/null +++ b/src/igl/connect_boundary_to_infinity.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "connect_boundary_to_infinity.h" +#include "boundary_facets.h" + +template +IGL_INLINE void igl::connect_boundary_to_infinity( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FO) +{ + return connect_boundary_to_infinity(F,F.maxCoeff(),FO); +} +template +IGL_INLINE void igl::connect_boundary_to_infinity( + const Eigen::PlainObjectBase & F, + const typename DerivedF::Scalar inf_index, + Eigen::PlainObjectBase & FO) +{ + // Determine boundary edges + Eigen::Matrix O; + boundary_facets(F,O); + FO.resize(F.rows()+O.rows(),F.cols()); + typedef Eigen::Matrix VectorXI; + FO.topLeftCorner(F.rows(),F.cols()) = F; + FO.bottomLeftCorner(O.rows(),O.cols()) = O.rowwise().reverse(); + FO.bottomRightCorner(O.rows(),1).setConstant(inf_index); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVO, + typename DerivedFO> +IGL_INLINE void igl::connect_boundary_to_infinity( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & VO, + Eigen::PlainObjectBase & FO) +{ + typename DerivedV::Index inf_index = V.rows(); + connect_boundary_to_infinity(F,inf_index,FO); + VO.resize(V.rows()+1,V.cols()); + VO.topLeftCorner(V.rows(),V.cols()) = V; + auto inf = std::numeric_limits::infinity(); + VO.row(V.rows()).setConstant(inf); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::connect_boundary_to_infinity, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/connect_boundary_to_infinity.h b/src/igl/connect_boundary_to_infinity.h new file mode 100644 index 000000000..2fa2783d6 --- /dev/null +++ b/src/igl/connect_boundary_to_infinity.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CONNECT_BOUNDARY_TO_INFINITY_H +#define IGL_CONNECT_BOUNDARY_TO_INFINITY_H +#include "igl_inline.h" +#include +namespace igl +{ + // Connect all boundary edges to a fictitious point at infinity. + // + // Inputs: + // F #F by 3 list of face indices into some V + // Outputs: + // FO #F+#O by 3 list of face indices into [V;inf inf inf], original F are + // guaranteed to come first. If (V,F) was a manifold mesh, now it is + // closed with a possibly non-manifold vertex at infinity (but it will be + // edge-manifold). + template + IGL_INLINE void connect_boundary_to_infinity( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FO); + // Inputs: + // inf_index index of point at infinity (usually V.rows() or F.maxCoeff()) + template + IGL_INLINE void connect_boundary_to_infinity( + const Eigen::PlainObjectBase & F, + const typename DerivedF::Scalar inf_index, + Eigen::PlainObjectBase & FO); + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of face indices into some V + // Outputs: + // VO #V+1 by 3 list of vertex positions, original V are guaranteed to + // come first. Last point is inf, inf, inf + // FO #F+#O by 3 list of face indices into VO + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedVO, + typename DerivedFO> + IGL_INLINE void connect_boundary_to_infinity( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & VO, + Eigen::PlainObjectBase & FO); +} +#ifndef IGL_STATIC_LIBRARY +# include "connect_boundary_to_infinity.cpp" +#endif +#endif diff --git a/src/igl/copyleft/README.md b/src/igl/copyleft/README.md new file mode 100644 index 000000000..78454affc --- /dev/null +++ b/src/igl/copyleft/README.md @@ -0,0 +1,14 @@ +## IGL copyleft subdirectory + +Functions in the `include/igl/copyleft/` subdirectory are in the +`igl::copyleft::` namespace to indicate that they are under a more aggressive +[copyleft](https://en.wikipedia.org/wiki/Copyleft) than +[MPL2](https://en.wikipedia.org/wiki/Mozilla_Public_License) used for the main +`include/igl` directory and `igl::` namespace. Most notably, this subdirectory +includes code that is under +[GPL](https://en.wikipedia.org/wiki/GNU_General_Public_License). + +Typically a company planning on developing software without releasing its +source code will avoid or purchase licenses for such dependencies. If you do +obtain such a license for the dependencies employed here, you are free to use +the libigl functions here as per their MPL2 license. diff --git a/src/igl/copyleft/cgal/BinaryWindingNumberOperations.h b/src/igl/copyleft/cgal/BinaryWindingNumberOperations.h new file mode 100644 index 000000000..9bae0b7bb --- /dev/null +++ b/src/igl/copyleft/cgal/BinaryWindingNumberOperations.h @@ -0,0 +1,167 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_BINARY_WINDING_NUMBER_OPERATIONS_H +#define IGL_COPYLEFT_CGAL_BINARY_WINDING_NUMBER_OPERATIONS_H + +#include +#include "../../igl_inline.h" +#include "../../MeshBooleanType.h" +#include + +// TODO: This is not written according to libigl style. These should be +// function handles. +// +// Why is this templated on DerivedW +// +// These are all generalized to n-ary operations +namespace igl +{ + namespace copyleft + { + namespace cgal + { + template + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& /*win_nums*/) const { + throw (std::runtime_error("not implemented!")); + } + }; + + // A ∪ B ∪ ... ∪ Z + template <> + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& win_nums) const + { + for(int i = 0;i 0) return true; + } + return false; + } + }; + + // A ∩ B ∩ ... ∩ Z + template <> + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& win_nums) const + { + for(int i = 0;i + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& win_nums) const + { + assert(win_nums.size()>1); + // Union of objects 1 through n-1 + bool union_rest = false; + for(int i = 1;i 0; + if(union_rest) break; + } + // Must be in object 0 and not in union of objects 1 through n-1 + return win_nums(0) > 0 && !union_rest; + } + }; + + // A ∆ B ∆ ... ∆ Z (equivalent to set inside odd number of objects) + template <> + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& win_nums) const + { + // If inside an odd number of objects + int count = 0; + for(int i = 0;i 0) count++; + } + return count % 2 == 1; + } + }; + + template <> + class BinaryWindingNumberOperations { + public: + template + typename DerivedW::Scalar operator()( + const Eigen::PlainObjectBase& /*win_nums*/) const { + return true; + } + }; + + typedef BinaryWindingNumberOperations BinaryUnion; + typedef BinaryWindingNumberOperations BinaryIntersect; + typedef BinaryWindingNumberOperations BinaryMinus; + typedef BinaryWindingNumberOperations BinaryXor; + typedef BinaryWindingNumberOperations BinaryResolve; + + enum KeeperType { + KEEP_INSIDE, + KEEP_ALL + }; + + template + class WindingNumberFilter { + public: + template + short operator()( + const Eigen::PlainObjectBase& /*win_nums*/) const { + throw std::runtime_error("Not implemented"); + } + }; + + template<> + class WindingNumberFilter { + public: + template + short operator()(T out_w, T in_w) const { + if (in_w > 0 && out_w <= 0) return 1; + else if (in_w <= 0 && out_w > 0) return -1; + else return 0; + } + }; + + template<> + class WindingNumberFilter { + public: + template + short operator()(T /*out_w*/, T /*in_w*/) const { + return 1; + } + }; + + typedef WindingNumberFilter KeepInside; + typedef WindingNumberFilter KeepAll; + } + } +} + +#endif diff --git a/src/igl/copyleft/cgal/CGAL_includes.hpp b/src/igl/copyleft/cgal/CGAL_includes.hpp new file mode 100644 index 000000000..38d2fc749 --- /dev/null +++ b/src/igl/copyleft/cgal/CGAL_includes.hpp @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CGAL_INCLUDES_H +#define IGL_CGAL_INCLUDES_H + +// This causes unknown bugs during intersection meshing: +//// http://www.alecjacobson.com/weblog/?p=4291 +//#define CGAL_INTERSECTION_VERSION 1 +// Use this instead to mute errors resulting from bad CGAL assertions +#define CGAL_KERNEL_NO_ASSERTIONS +// Triangle triangle intersection +#include +// THIS CANNOT BE INCLUDED IN THE SAME FILE AS +// #include + +// Constrained Delaunay Triangulation types +#include +#include + +// Axis-align boxes for all-pairs self-intersection detection +#include +#include +#include +#include +#include +#include +#include + +// Axis-aligned bounding box tree for tet tri intersection +#include +#include +#include + +// Boolean operations +#include +// Is this actually used? +//#include + +// Delaunay Triangulation in 3D +#include + +#include +#include + +#endif diff --git a/src/igl/copyleft/cgal/CSGTree.h b/src/igl/copyleft/cgal/CSGTree.h new file mode 100644 index 000000000..599572d3f --- /dev/null +++ b/src/igl/copyleft/cgal/CSGTree.h @@ -0,0 +1,189 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_CSG_TREE_H +#define IGL_COPYLEFT_CGAL_CSG_TREE_H + +#include "../../MeshBooleanType.h" +#include "string_to_mesh_boolean_type.h" +#include "mesh_boolean.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Class for defining and computing a constructive solid geometry result + // out of a tree of boolean operations on "solid" triangle meshes. + // + //template + class CSGTree + { + public: + typedef CGAL::Epeck::FT ExactScalar; + //typedef Eigen::PlainObjectBase POBF; + typedef Eigen::MatrixXi POBF; + typedef POBF::Index FIndex; + typedef Eigen::Matrix MatrixX3E; + typedef Eigen::Matrix VectorJ; + private: + // Resulting mesh + MatrixX3E m_V; + POBF m_F; + VectorJ m_J; + // Number of birth faces in A + those in B. I.e. sum of original "leaf" + // faces involved in result. + size_t m_number_of_birth_faces; + public: + CSGTree() + { + } + //typedef Eigen::MatrixXd MatrixX3E; + //typedef Eigen::MatrixXi POBF; + // http://stackoverflow.com/a/3279550/148668 + CSGTree(const CSGTree & other) + : + // copy things + m_V(other.m_V), + // This is an issue if m_F is templated + // https://forum.kde.org/viewtopic.php?f=74&t=128414 + m_F(other.m_F), + m_J(other.m_J), + m_number_of_birth_faces(other.m_number_of_birth_faces) + { + } + // copy-swap idiom + friend void swap(CSGTree& first, CSGTree& second) + { + using std::swap; + // swap things + swap(first.m_V,second.m_V); + // This is an issue if m_F is templated, similar to + // https://forum.kde.org/viewtopic.php?f=74&t=128414 + swap(first.m_F,second.m_F); + swap(first.m_J,second.m_J); + swap(first.m_number_of_birth_faces,second.m_number_of_birth_faces); + } + // Pass-by-value (aka copy) + CSGTree& operator=(CSGTree other) + { + swap(*this,other); + return *this; + } + CSGTree(CSGTree&& other): + // initialize via default constructor + CSGTree() + { + swap(*this,other); + } + // Construct and compute a boolean operation on existing CSGTree nodes. + // + // Inputs: + // A Solid result of previous CSG operation (or identity, see below) + // B Solid result of previous CSG operation (or identity, see below) + // type type of mesh boolean to compute + CSGTree( + const CSGTree & A, + const CSGTree & B, + const MeshBooleanType & type) + { + // conduct boolean operation + mesh_boolean(A.V(),A.F(),B.V(),B.F(),type,m_V,m_F,m_J); + // reindex m_J + std::for_each(m_J.data(),m_J.data()+m_J.size(), + [&](typename VectorJ::Scalar & j) -> void + { + if(j < A.F().rows()) + { + j = A.J()(j); + }else + { + assert(j<(A.F().rows()+B.F().rows())); + j = A.number_of_birth_faces()+(B.J()(j-A.F().rows())); + } + }); + m_number_of_birth_faces = + A.number_of_birth_faces() + B.number_of_birth_faces(); + } + // Overload using string for type + CSGTree( + const CSGTree & A, + const CSGTree & B, + const std::string & s): + CSGTree(A,B,string_to_mesh_boolean_type(s)) + { + // do nothing (all done in constructor). + } + // "Leaf" node with identity operation on assumed "solid" mesh (V,F) + // + // Inputs: + // V #V by 3 list of mesh vertices (in any precision, will be + // converted to exact) + // F #F by 3 list of mesh face indices into V + template + CSGTree(const Eigen::PlainObjectBase & V, const POBF & F)//: + // Possible Eigen bug: + // https://forum.kde.org/viewtopic.php?f=74&t=128414 + //m_V(V.template cast()),m_F(F) + { + m_V = V.template cast(); + m_F = F; + // number of faces + m_number_of_birth_faces = m_F.rows(); + // identity birth index + m_J = VectorJ::LinSpaced( + m_number_of_birth_faces,0,m_number_of_birth_faces-1); + } + // Returns reference to resulting mesh vertices m_V in exact scalar + // representation + const MatrixX3E & V() const + { + return m_V; + } + // Returns mesh vertices in the desired output type, casting when + // appropriate to floating precision. + template + DerivedV cast_V() const + { + DerivedV dV; + dV.resize(m_V.rows(),m_V.cols()); + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_REMESH_SELF_INTERSECTIONS_PARAM_H +#define IGL_COPYLEFT_CGAL_REMESH_SELF_INTERSECTIONS_PARAM_H + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Optional Parameters + // DetectOnly Only compute IF, leave VV and FF alone + struct RemeshSelfIntersectionsParam + { + bool detect_only; + bool first_only; + bool stitch_all; + inline RemeshSelfIntersectionsParam( + bool _detect_only=false, + bool _first_only=false, + bool _stitch_all=false): + detect_only(_detect_only), + first_only(_first_only), + stitch_all(_stitch_all){}; + }; + } + } +} + +#endif diff --git a/src/igl/copyleft/cgal/SelfIntersectMesh.h b/src/igl/copyleft/cgal/SelfIntersectMesh.h new file mode 100644 index 000000000..5a70fc39e --- /dev/null +++ b/src/igl/copyleft/cgal/SelfIntersectMesh.h @@ -0,0 +1,939 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_SELFINTERSECTMESH_H +#define IGL_COPYLEFT_CGAL_SELFINTERSECTMESH_H + +#include "CGAL_includes.hpp" +#include "RemeshSelfIntersectionsParam.h" +#include "../../unique.h" + +#include +#include +#include +#include +#include +#include + +//#define IGL_SELFINTERSECTMESH_DEBUG +#ifndef IGL_FIRST_HIT_EXCEPTION +#define IGL_FIRST_HIT_EXCEPTION 10 +#endif + +// The easiest way to keep track of everything is to use a class + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Kernel is a CGAL kernel like: + // CGAL::Exact_predicates_inexact_constructions_kernel + // or + // CGAL::Exact_predicates_exact_constructions_kernel + + template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> + class SelfIntersectMesh + { + typedef + SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM> Self; + public: + // 3D Primitives + typedef CGAL::Point_3 Point_3; + typedef CGAL::Segment_3 Segment_3; + typedef CGAL::Triangle_3 Triangle_3; + typedef CGAL::Plane_3 Plane_3; + typedef CGAL::Tetrahedron_3 Tetrahedron_3; + // 2D Primitives + typedef CGAL::Point_2 Point_2; + typedef CGAL::Segment_2 Segment_2; + typedef CGAL::Triangle_2 Triangle_2; + // 2D Constrained Delaunay Triangulation types + typedef CGAL::Exact_intersections_tag Itag; + // Axis-align boxes for all-pairs self-intersection detection + typedef std::vector Triangles; + typedef typename Triangles::iterator TrianglesIterator; + typedef typename Triangles::const_iterator TrianglesConstIterator; + typedef + CGAL::Box_intersection_d::Box_with_handle_d + Box; + + // Input mesh + const Eigen::MatrixBase & V; + const Eigen::MatrixBase & F; + // Number of self-intersecting triangle pairs + typedef typename DerivedF::Index Index; + Index count; + typedef std::vector> ObjectList; + // Using a vector here makes this **not** output sensitive + Triangles T; + typedef std::vector IndexList; + IndexList lIF; + // #F-long list of faces with intersections mapping to the order in + // which they were first found + std::map offending; + // Make a short name for the edge map's key + typedef std::pair EMK; + // Make a short name for the type stored at each edge, the edge map's + // value + typedef std::vector EMV; + // Make a short name for the edge map + typedef std::map EdgeMap; + // Maps edges of offending faces to all incident offending faces + std::vector > + candidate_triangle_pairs; + + public: + RemeshSelfIntersectionsParam params; + public: + // Constructs (VV,FF) a new mesh with self-intersections of (V,F) + // subdivided + // + // See also: remesh_self_intersections.h + inline SelfIntersectMesh( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + private: + // Helper function to mark a face as offensive + // + // Inputs: + // f index of face in F + inline void mark_offensive(const Index f); + // Helper function to count intersections between faces + // + // Input: + // fa index of face A in F + // fb index of face B in F + inline void count_intersection( const Index fa, const Index fb); + // Helper function for box_intersect. Intersect two triangles A and B, + // append the intersection object (point,segment,triangle) to a running + // list for A and B + // + // Inputs: + // A triangle in 3D + // B triangle in 3D + // fa index of A in F (and key into offending) + // fb index of B in F (and key into offending) + // Returns true only if A intersects B + // + inline bool intersect( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb); + // Helper function for box_intersect. In the case where A and B have + // already been identified to share a vertex, then we only want to + // add possible segment intersections. Assumes truly duplicate + // triangles are not given as input + // + // Inputs: + // A triangle in 3D + // B triangle in 3D + // fa index of A in F (and key into offending) + // fb index of B in F (and key into offending) + // va index of shared vertex in A (and key into offending) + // vb index of shared vertex in B (and key into offending) + // Returns true if intersection (besides shared point) + // + inline bool single_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const Index va, + const Index vb); + // Helper handling one direction + inline bool single_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const Index va); + // Helper function for box_intersect. In the case where A and B have + // already been identified to share two vertices, then we only want + // to add a possible coplanar (Triangle) intersection. Assumes truly + // degenerate facets are not givin as input. + inline bool double_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const std::vector > shared); + + public: + // Callback function called during box self intersections test. Means + // boxes a and b intersect. This method then checks if the triangles + // in each box intersect and if so, then processes the intersections + // + // Inputs: + // a box containing a triangle + // b box containing a triangle + inline void box_intersect(const Box& a, const Box& b); + inline void process_intersecting_boxes(); + public: + // Getters: + //const IndexList& get_lIF() const{ return lIF;} + static inline void box_intersect_static( + SelfIntersectMesh * SIM, + const Box &a, + const Box &b); + private: + std::mutex m_offending_lock; + }; + } + } +} + +// Implementation + +#include "mesh_to_cgal_triangle_list.h" +#include "remesh_intersections.h" + +#include "../../REDRUM.h" +#include "../../get_seconds.h" +#include "../../C_STR.h" + + +#include +#include +#include +#include +#include + +// References: +// http://minregret.googlecode.com/svn/trunk/skyline/src/extern/CGAL-3.3.1/examples/Polyhedron/polyhedron_self_intersection.cpp +// http://www.cgal.org/Manual/3.9/examples/Boolean_set_operations_2/do_intersect.cpp + +// Q: Should we be using CGAL::Polyhedron_3? +// A: No! Input is just a list of unoriented triangles. Polyhedron_3 requires +// a 2-manifold. +// A: But! It seems we could use CGAL::Triangulation_3. Though it won't be easy +// to take advantage of functions like insert_in_facet because we want to +// constrain segments. Hmmm. Actually Triangulation_3 doesn't look right... + +// CGAL's box_self_intersection_d uses C-style function callbacks without +// userdata. This is a leapfrog method for calling a member function. It should +// be bound as if the prototype was: +// static void box_intersect(const Box &a, const Box &b) +// using boost: +// boost::function cb +// = boost::bind(&::box_intersect, this, _1,_2); +// +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline void igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::box_intersect_static( + Self * SIM, + const typename Self::Box &a, + const typename Self::Box &b) +{ + SIM->box_intersect(a,b); +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::SelfIntersectMesh( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM): + V(V), + F(F), + count(0), + T(), + lIF(), + offending(), + params(params) +{ + using namespace std; + using namespace Eigen; + +#ifdef IGL_SELFINTERSECTMESH_DEBUG + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void{ + std::cout << "SelfIntersectMesh." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#endif + + // Compute and process self intersections + mesh_to_cgal_triangle_list(V,F,T); +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("convert_to_triangle_list"); +#endif + // http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Box_intersection_d/Chapter_main.html#Section_63.5 + // Create the corresponding vector of bounding boxes + std::vector boxes; + boxes.reserve(T.size()); + for ( + TrianglesIterator tit = T.begin(); + tit != T.end(); + ++tit) + { + if (!tit->is_degenerate()) + { + boxes.push_back(Box(tit->bbox(), tit)); + } + } + // Leapfrog callback + std::function cb = + std::bind(&box_intersect_static, this, + // Explicitly use std namespace to avoid confusion with boost (who puts + // _1 etc. in global namespace) + std::placeholders::_1, + std::placeholders::_2); +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("box_and_bind"); +#endif + // Run the self intersection algorithm with all defaults + CGAL::box_self_intersection_d(boxes.begin(), boxes.end(),cb); +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("box_intersection_d"); +#endif + try{ + process_intersecting_boxes(); + }catch(int e) + { + // Rethrow if not IGL_FIRST_HIT_EXCEPTION + if(e != IGL_FIRST_HIT_EXCEPTION) + { + throw e; + } + // Otherwise just fall through + } +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("resolve_intersection"); +#endif + + // Convert lIF to Eigen matrix + assert(lIF.size()%2 == 0); + IF.resize(lIF.size()/2,2); + { + Index i=0; + for( + typename IndexList::const_iterator ifit = lIF.begin(); + ifit!=lIF.end(); + ) + { + IF(i,0) = (*ifit); + ifit++; + IF(i,1) = (*ifit); + ifit++; + i++; + } + } +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("store_intersecting_face_pairs"); +#endif + + if(params.detect_only) + { + return; + } + + remesh_intersections( + V,F,T,offending,params.stitch_all,VV,FF,J,IM); + +#ifdef IGL_SELFINTERSECTMESH_DEBUG + log_time("remesh_intersection"); +#endif +} + + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline void igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::mark_offensive(const Index f) +{ + using namespace std; + lIF.push_back(f); + if(offending.count(f) == 0) + { + // first time marking, initialize with new id and empty list + offending[f] = {}; + } +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline void igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::count_intersection( + const Index fa, + const Index fb) +{ + std::lock_guard guard(m_offending_lock); + mark_offensive(fa); + mark_offensive(fb); + this->count++; + // We found the first intersection + if(params.first_only && this->count >= 1) + { + throw IGL_FIRST_HIT_EXCEPTION; + } + +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline bool igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::intersect( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb) +{ + // Determine whether there is an intersection + if(!CGAL::do_intersect(A,B)) + { + return false; + } + count_intersection(fa,fb); + if(!params.detect_only) + { + // Construct intersection + CGAL::Object result = CGAL::intersection(A,B); + std::lock_guard guard(m_offending_lock); + offending[fa].push_back({fb, result}); + offending[fb].push_back({fa, result}); + } + return true; +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline bool igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::single_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const Index va, + const Index vb) +{ + if(single_shared_vertex(A,B,fa,fb,va)) + { + return true; + } + return single_shared_vertex(B,A,fb,fa,vb); +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline bool igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::single_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const Index va) +{ + // This was not a good idea. It will not handle coplanar triangles well. + using namespace std; + Segment_3 sa( + A.vertex((va+1)%3), + A.vertex((va+2)%3)); + + if(CGAL::do_intersect(sa,B)) + { + // can't put count_intersection(fa,fb) here since we use intersect below + // and then it will be counted twice. + if(params.detect_only) + { + count_intersection(fa,fb); + return true; + } + CGAL::Object result = CGAL::intersection(sa,B); + if(const Point_3 * p = CGAL::object_cast(&result)) + { + // Single intersection --> segment from shared point to intersection + CGAL::Object seg = CGAL::make_object(Segment_3( + A.vertex(va), + *p)); + count_intersection(fa,fb); + std::lock_guard guard(m_offending_lock); + offending[fa].push_back({fb, seg}); + offending[fb].push_back({fa, seg}); + return true; + }else if(CGAL::object_cast(&result)) + { + // Need to do full test. Intersection could be a general poly. + bool test = intersect(A,B,fa,fb); + ((void)test); + assert(test && "intersect should agree with do_intersect"); + return true; + }else + { + cerr< +inline bool igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::double_shared_vertex( + const Triangle_3 & A, + const Triangle_3 & B, + const Index fa, + const Index fb, + const std::vector > shared) +{ + using namespace std; + + // must be co-planar + if( + A.supporting_plane() != B.supporting_plane() && + A.supporting_plane() != B.supporting_plane().opposite()) + { + return false; + } + // Since A and B are non-degenerate the intersection must be a polygon + // (triangle). Either + // - the vertex of A (B) opposite the shared edge of lies on B (A), or + // - an edge of A intersects and edge of B without sharing a vertex + // + // Determine if the vertex opposite edge (a0,a1) in triangle A lies in + // (intersects) triangle B + const auto & opposite_point_inside = []( + const Triangle_3 & A, const Index a0, const Index a1, const Triangle_3 & B) + -> bool + { + // get opposite index + Index a2 = -1; + for(int c = 0;c<3;c++) + { + if(c != a0 && c != a1) + { + a2 = c; + break; + } + } + assert(a2 != -1); + bool ret = CGAL::do_intersect(A.vertex(a2),B); + return ret; + }; + + // Determine if edge opposite vertex va in triangle A intersects edge + // opposite vertex vb in triangle B. + const auto & opposite_edges_intersect = []( + const Triangle_3 & A, const Index va, + const Triangle_3 & B, const Index vb) -> bool + { + Segment_3 sa( A.vertex((va+1)%3), A.vertex((va+2)%3)); + Segment_3 sb( B.vertex((vb+1)%3), B.vertex((vb+2)%3)); + bool ret = CGAL::do_intersect(sa,sb); + return ret; + }; + + if( + !opposite_point_inside(A,shared[0].first,shared[1].first,B) && + !opposite_point_inside(B,shared[0].second,shared[1].second,A) && + !opposite_edges_intersect(A,shared[0].first,B,shared[1].second) && + !opposite_edges_intersect(A,shared[1].first,B,shared[0].second)) + { + return false; + } + + // there is an intersection indeed + count_intersection(fa,fb); + if(params.detect_only) + { + return true; + } + // Construct intersection + try + { + // This can fail for Epick but not Epeck + CGAL::Object result = CGAL::intersection(A,B); + if(!result.empty()) + { + if(CGAL::object_cast(&result)) + { + // not coplanar + assert(false && + "Co-planar non-degenerate triangles should intersect over triangle"); + return false; + } else if(CGAL::object_cast(&result)) + { + // this "shouldn't" happen but does for inexact + assert(false && + "Co-planar non-degenerate triangles should intersect over triangle"); + return false; + } else + { + // Triangle object + std::lock_guard guard(m_offending_lock); + offending[fa].push_back({fb, result}); + offending[fb].push_back({fa, result}); + return true; + } + }else + { + // CGAL::intersection is disagreeing with do_intersect + assert(false && "CGAL::intersection should agree with predicate tests"); + return false; + } + }catch(...) + { + // This catches some cgal assertion: + // CGAL error: assertion violation! + // Expression : is_finite(d) + // File : /opt/local/include/CGAL/GMP/Gmpq_type.h + // Line : 132 + // Explanation: + // But only if NDEBUG is not defined, otherwise there's an uncaught + // "Floating point exception: 8" SIGFPE + return false; + } + // No intersection. + return false; +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline void igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::box_intersect( + const Box& a, + const Box& b) +{ + candidate_triangle_pairs.push_back({a.handle(), b.handle()}); +} + +template < + typename Kernel, + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +inline void igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM>::process_intersecting_boxes() +{ + std::vector triangle_locks(T.size()); + std::vector vertex_locks(V.rows()); + std::mutex index_lock; + std::mutex exception_mutex; + bool exception_fired = false; + int exception = -1; + auto process_chunk = + [&]( + const size_t first, + const size_t last) -> void + { + try + { + assert(last >= first); + + for (size_t i=first; i guard(index_lock); + const auto& tri_pair = candidate_triangle_pairs[i]; + fa = tri_pair.first - T.begin(); + fb = tri_pair.second - T.begin(); + } + assert(fa < T.size()); + assert(fb < T.size()); + + // Lock triangles + std::lock_guard guard_A(triangle_locks[fa]); + std::lock_guard guard_B(triangle_locks[fb]); + + // Lock vertices + std::list > guard_vertices; + { + std::vector unique_vertices; + std::vector tmp1, tmp2; + igl::unique({F(fa,0), F(fa,1), F(fa,2), F(fb,0), F(fb,1), F(fb,2)}, + unique_vertices, tmp1, tmp2); + std::for_each(unique_vertices.begin(), unique_vertices.end(), + [&](const typename DerivedF::Scalar& vi) { + guard_vertices.emplace_back(vertex_locks[vi]); + }); + } + if(exception_fired) return; + + const Triangle_3& A = T[fa]; + const Triangle_3& B = T[fb]; + + // Number of combinatorially shared vertices + Index comb_shared_vertices = 0; + // Number of geometrically shared vertices (*not* including + // combinatorially shared) + Index geo_shared_vertices = 0; + // Keep track of shared vertex indices + std::vector > shared; + Index ea,eb; + for(ea=0;ea<3;ea++) + { + for(eb=0;eb<3;eb++) + { + if(F(fa,ea) == F(fb,eb)) + { + comb_shared_vertices++; + shared.emplace_back(ea,eb); + }else if(A.vertex(ea) == B.vertex(eb)) + { + geo_shared_vertices++; + shared.emplace_back(ea,eb); + } + } + } + const Index total_shared_vertices = + comb_shared_vertices + geo_shared_vertices; + if(exception_fired) return; + + if(comb_shared_vertices== 3) + { + assert(shared.size() == 3); + // Combinatorially duplicate face, these should be removed by + // preprocessing + continue; + } + if(total_shared_vertices== 3) + { + assert(shared.size() == 3); + // Geometrically duplicate face, these should be removed by + // preprocessing + continue; + } + if(total_shared_vertices == 2) + { + assert(shared.size() == 2); + // Q: What about coplanar? + // + // o o + // |\ /| + // | \/ | + // | /\ | + // |/ \| + // o----o + double_shared_vertex(A,B,fa,fb,shared); + continue; + } + assert(total_shared_vertices<=1); + if(total_shared_vertices==1) + { + single_shared_vertex(A,B,fa,fb,shared[0].first,shared[0].second); + }else + { + intersect(A,B,fa,fb); + } + } + }catch(int e) + { + std::lock_guard exception_lock(exception_mutex); + exception_fired = true; + exception = e; + } + }; + size_t num_threads=0; + const size_t hardware_limit = std::thread::hardware_concurrency(); + if (const char* igl_num_threads = std::getenv("LIBIGL_NUM_THREADS")) { + num_threads = atoi(igl_num_threads); + } + if (num_threads == 0 || num_threads > hardware_limit) { + num_threads = hardware_limit; + } + assert(num_threads > 0); + const size_t num_pairs = candidate_triangle_pairs.size(); + const size_t chunk_size = num_pairs / num_threads; + std::vector threads; + for (size_t i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "assign.h" +#include "assign_scalar.h" + +template +IGL_INLINE void igl::copyleft::cgal::assign( + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & D) +{ + D.resizeLike(C); + for(int i = 0;i +IGL_INLINE +Eigen::Matrix< + ReturnScalar, + DerivedC::RowsAtCompileTime, + DerivedC::ColsAtCompileTime, + 1, + DerivedC::MaxRowsAtCompileTime, + DerivedC::MaxColsAtCompileTime> +igl::copyleft::cgal::assign( + const Eigen::MatrixBase & C) +{ + Eigen::Matrix< + ReturnScalar, + DerivedC::RowsAtCompileTime, + DerivedC::ColsAtCompileTime, + 1, + DerivedC::MaxRowsAtCompileTime, + DerivedC::MaxColsAtCompileTime> D; + assign(C,D); + return D; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, -1, 1, -1, -1>, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, Eigen::Matrix, -1, -1, 1, -1, -1> >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, Eigen::Matrix, -1, -1, 1, -1, -1> >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, 3, 0, -1, 3>, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, -1, 1, -1, -1>, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, -1, 1, -1, -1>, Eigen::Matrix, -1, -1, 0, -1, -1> >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, -1, 1, -1, -1>, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::assign, -1, -1, 1, -1, -1>, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, -1, -1, 0, -1, -1>, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, -1, -1, 0, -1, -1>, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix, -1, -1, 0, -1, -1> >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix, -1, -1, 0, -1, -1> >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +template void igl::copyleft::cgal::assign, -1, -1, 0, -1, -1>, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, -1, 3, 0, -1, 3>, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +template void igl::copyleft::cgal::assign, -1, 3, 0, -1, 3>, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::assign, 8, 3, 0, 8, 3>, Eigen::Matrix, 8, 3, 0, 8, 3> >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::PlainObjectBase, 8, 3, 0, 8, 3> >&); +template void igl::copyleft::cgal::assign, 8, 3, 0, 8, 3>, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/assign.h b/src/igl/copyleft/cgal/assign.h new file mode 100644 index 000000000..db9081f23 --- /dev/null +++ b/src/igl/copyleft/cgal/assign.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_ASSIGN_H +#define IGL_COPYLEFT_CGAL_ASSIGN_H +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + template + IGL_INLINE void assign( + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & D); + template + IGL_INLINE + Eigen::Matrix< + ReturnScalar, + DerivedC::RowsAtCompileTime, + DerivedC::ColsAtCompileTime, + 1, + DerivedC::MaxRowsAtCompileTime, + DerivedC::MaxColsAtCompileTime> + assign( + const Eigen::MatrixBase & C); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "assign.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/assign_scalar.cpp b/src/igl/copyleft/cgal/assign_scalar.cpp new file mode 100644 index 000000000..c38ee03a1 --- /dev/null +++ b/src/igl/copyleft/cgal/assign_scalar.cpp @@ -0,0 +1,136 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "assign_scalar.h" + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Epeck::FT & cgal, + CGAL::Epeck::FT & d) +{ + d = cgal; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Epeck::FT & _cgal, + double & d) +{ + // FORCE evaluation of the exact type otherwise interval might be huge. + const CGAL::Epeck::FT cgal = _cgal.exact(); + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const double next = nextafter(d, interval.second); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < interval.second); +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Epeck::FT & _cgal, + float& d) +{ + // FORCE evaluation of the exact type otherwise interval might be huge. + const CGAL::Epeck::FT cgal = _cgal.exact(); + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const float next = nextafter(d, float(interval.second)); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < float(interval.second)); +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const double & c, + double & d) +{ + d = c; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const float& c, + float& d) +{ + d = c; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const float& c, + double& d) +{ + d = c; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & d) +{ + d = cgal; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + double & d) +{ + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const double next = nextafter(d, interval.second); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < interval.second); +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + float& d) +{ + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const float next = nextafter(d, float(interval.second)); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < float(interval.second)); +} + +#ifndef WIN32 + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + CGAL::Simple_cartesian::FT & d) +{ + d = cgal; +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + double & d) +{ + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const double next = nextafter(d, interval.second); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < interval.second); +} + +IGL_INLINE void igl::copyleft::cgal::assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + float& d) +{ + const auto interval = CGAL::to_interval(cgal); + d = interval.first; + do { + const float next = nextafter(d, float(interval.second)); + if (CGAL::abs(cgal-d) < CGAL::abs(cgal-next)) break; + d = next; + } while (d < float(interval.second)); +} + +#endif // WIN32 diff --git a/src/igl/copyleft/cgal/assign_scalar.h b/src/igl/copyleft/cgal/assign_scalar.h new file mode 100644 index 000000000..feec928d5 --- /dev/null +++ b/src/igl/copyleft/cgal/assign_scalar.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_ASSIGN_SCALAR_H +#define IGL_COPYLEFT_CGAL_ASSIGN_SCALAR_H +#include "../../igl_inline.h" +#include +#include +#ifndef WIN32 +#include +#endif + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // cgal cgal scalar + // Outputs: + // d output scalar + IGL_INLINE void assign_scalar( + const CGAL::Epeck::FT & cgal, + CGAL::Epeck::FT & d); + IGL_INLINE void assign_scalar( + const CGAL::Epeck::FT & cgal, + double & d); + IGL_INLINE void assign_scalar( + const CGAL::Epeck::FT & cgal, + float& d); + IGL_INLINE void assign_scalar( + const double & c, + double & d); + IGL_INLINE void assign_scalar( + const float& c, + float & d); + IGL_INLINE void assign_scalar( + const float& c, + double& d); + + IGL_INLINE void assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & d); + IGL_INLINE void assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + double & d); + IGL_INLINE void assign_scalar( + const CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt::FT & cgal, + float& d); + +#ifndef WIN32 + IGL_INLINE void assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + CGAL::Simple_cartesian::FT & d); + IGL_INLINE void assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + double & d); + IGL_INLINE void assign_scalar( + const CGAL::Simple_cartesian::FT & cgal, + float& d); +#endif // WIN32 + + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "assign_scalar.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/barycenter.cpp b/src/igl/copyleft/cgal/barycenter.cpp new file mode 100644 index 000000000..b3ed8aeaf --- /dev/null +++ b/src/igl/copyleft/cgal/barycenter.cpp @@ -0,0 +1,15 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../barycenter.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../barycenter.cpp" +template void igl::barycenter, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1> >(Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&); +#endif diff --git a/src/igl/copyleft/cgal/cell_adjacency.cpp b/src/igl/copyleft/cgal/cell_adjacency.cpp new file mode 100644 index 000000000..1824a37c8 --- /dev/null +++ b/src/igl/copyleft/cgal/cell_adjacency.cpp @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// + +#include "cell_adjacency.h" + +template +IGL_INLINE void igl::copyleft::cgal::cell_adjacency( + const Eigen::PlainObjectBase& per_patch_cells, + const size_t num_cells, + std::vector > >& + adjacency_list) { + + const size_t num_patches = per_patch_cells.rows(); + adjacency_list.resize(num_cells); + for (size_t i=0; i >(Eigen::PlainObjectBase > const&, unsigned long, std::vector::Scalar, bool, unsigned long>, std::less::Scalar, bool, unsigned long> >, std::allocator::Scalar, bool, unsigned long> > >, std::allocator::Scalar, bool, unsigned long>, std::less::Scalar, bool, unsigned long> >, std::allocator::Scalar, bool, unsigned long> > > > >&); +#ifdef WIN32 +template void igl::copyleft::cgal::cell_adjacency>(class Eigen::PlainObjectBase> const &, unsigned __int64, class std::vector, struct std::less>, class std::allocator>>, class std::allocator, struct std::less>, class std::allocator>>>> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/cell_adjacency.h b/src/igl/copyleft/cgal/cell_adjacency.h new file mode 100644 index 000000000..2244d3c4a --- /dev/null +++ b/src/igl/copyleft/cgal/cell_adjacency.h @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_CELL_ADJACENCY_H +#define IGL_COPYLEFT_CGAL_CELL_ADJACENCY_H +#include "../../igl_inline.h" +#include +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // per_patch_cells #P by 2 list of cell labels on each side of each + // patch. Cell labels are assumed to be continuous + // from 0 to #C. + // num_cells number of cells. + // + // Outputs: + // adjacency_list #C array of list of adjcent cell information. If + // cell i and cell j are adjacent via patch x, where i + // is on the positive side of x, and j is on the + // negative side. Then, + // adjacency_list[i] will contain the entry {j, false, x} + // and + // adjacency_list[j] will contain the entry {i, true, x} + template < typename DerivedC > + IGL_INLINE void cell_adjacency( + const Eigen::PlainObjectBase& per_patch_cells, + const size_t num_cells, + std::vector > >& + adjacency_list); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "cell_adjacency.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/closest_facet.cpp b/src/igl/copyleft/cgal/closest_facet.cpp new file mode 100644 index 000000000..0b08dd4bc --- /dev/null +++ b/src/igl/copyleft/cgal/closest_facet.cpp @@ -0,0 +1,504 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "closest_facet.h" + +#include +#include +#include + +#include "order_facets_around_edge.h" +#include "submesh_aabb_tree.h" +#include "../../vertex_triangle_adjacency.h" +#include "../../LinSpaced.h" +//#include "../../writePLY.h" + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename DerivedR, + typename DerivedS > +IGL_INLINE void igl::copyleft::cgal::closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S) +{ + + typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; + typedef Kernel::Point_3 Point_3; + typedef Kernel::Plane_3 Plane_3; + typedef Kernel::Segment_3 Segment_3; + typedef Kernel::Triangle_3 Triangle; + typedef std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + + if (F.rows() <= 0 || I.rows() <= 0) { + throw std::runtime_error( + "Closest facet cannot be computed on empty mesh."); + } + + std::vector > VF, VFi; + igl::vertex_triangle_adjacency(V.rows(), F, VF, VFi); + std::vector in_I; + std::vector triangles; + Tree tree; + submesh_aabb_tree(V,F,I,tree,triangles,in_I); + + return closest_facet( + V,F,I,P,uE2E,EMAP,VF,VFi,tree,triangles,in_I,R,S); +} + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename Kernel, + typename DerivedR, + typename DerivedS > +IGL_INLINE void igl::copyleft::cgal::closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + const std::vector > & VF, + const std::vector > & VFi, + const CGAL::AABB_tree< + CGAL::AABB_traits< + Kernel, + CGAL::AABB_triangle_primitive< + Kernel, typename std::vector< + typename Kernel::Triangle_3 >::iterator > > > & tree, + const std::vector & triangles, + const std::vector & in_I, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S) +{ + typedef typename Kernel::Point_3 Point_3; + typedef typename Kernel::Plane_3 Plane_3; + typedef typename Kernel::Segment_3 Segment_3; + typedef typename Kernel::Triangle_3 Triangle; + typedef typename std::vector::iterator Iterator; + typedef typename CGAL::AABB_triangle_primitive Primitive; + typedef typename CGAL::AABB_traits AABB_triangle_traits; + typedef typename CGAL::AABB_tree Tree; + + const size_t num_faces = I.rows(); + if (F.rows() <= 0 || I.rows() <= 0) { + throw std::runtime_error( + "Closest facet cannot be computed on empty mesh."); + } + + auto on_the_positive_side = [&](size_t fid, const Point_3& p) -> bool + { + const auto& f = F.row(fid).eval(); + Point_3 v0(V(f[0], 0), V(f[0], 1), V(f[0], 2)); + Point_3 v1(V(f[1], 0), V(f[1], 1), V(f[1], 2)); + Point_3 v2(V(f[2], 0), V(f[2], 1), V(f[2], 2)); + auto ori = CGAL::orientation(v0, v1, v2, p); + switch (ori) { + case CGAL::POSITIVE: + return true; + case CGAL::NEGATIVE: + return false; + case CGAL::COPLANAR: + // Warning: + // This can only happen if fid contains a boundary edge. + // Categorized this ambiguous case as negative side. + return false; + default: + throw std::runtime_error("Unknown CGAL state."); + } + return false; + }; + + auto get_orientation = [&](size_t fid, size_t s, size_t d) -> bool + { + const auto& f = F.row(fid); + if ((size_t)f[0] == s && (size_t)f[1] == d) return false; + else if ((size_t)f[1] == s && (size_t)f[2] == d) return false; + else if ((size_t)f[2] == s && (size_t)f[0] == d) return false; + else if ((size_t)f[0] == d && (size_t)f[1] == s) return true; + else if ((size_t)f[1] == d && (size_t)f[2] == s) return true; + else if ((size_t)f[2] == d && (size_t)f[0] == s) return true; + else { + throw std::runtime_error( + "Cannot compute orientation due to incorrect connectivity"); + return false; + } + }; + auto index_to_signed_index = [&](size_t index, bool ori) -> int{ + return (index+1) * (ori? 1:-1); + }; + //auto signed_index_to_index = [&](int signed_index) -> size_t { + // return abs(signed_index) - 1; + //}; + + enum ElementType { VERTEX, EDGE, FACE }; + auto determine_element_type = [&](const Point_3& p, const size_t fid, + size_t& element_index) -> ElementType { + const auto& tri = triangles[fid]; + const Point_3 p0 = tri[0]; + const Point_3 p1 = tri[1]; + const Point_3 p2 = tri[2]; + + if (p == p0) { element_index = 0; return VERTEX; } + if (p == p1) { element_index = 1; return VERTEX; } + if (p == p2) { element_index = 2; return VERTEX; } + if (CGAL::collinear(p0, p1, p)) { element_index = 2; return EDGE; } + if (CGAL::collinear(p1, p2, p)) { element_index = 0; return EDGE; } + if (CGAL::collinear(p2, p0, p)) { element_index = 1; return EDGE; } + + element_index = 0; + return FACE; + }; + + auto process_edge_case = [&]( + size_t query_idx, + const size_t s, const size_t d, + size_t preferred_facet, + bool& orientation) -> size_t + { + Point_3 query_point( + P(query_idx, 0), + P(query_idx, 1), + P(query_idx, 2)); + + size_t corner_idx = std::numeric_limits::max(); + if ((s == F(preferred_facet, 0) && d == F(preferred_facet, 1)) || + (s == F(preferred_facet, 1) && d == F(preferred_facet, 0))) + { + corner_idx = 2; + } else if ((s == F(preferred_facet, 0) && d == F(preferred_facet, 2)) || + (s == F(preferred_facet, 2) && d == F(preferred_facet, 0))) + { + corner_idx = 1; + } else if ((s == F(preferred_facet, 1) && d == F(preferred_facet, 2)) || + (s == F(preferred_facet, 2) && d == F(preferred_facet, 1))) + { + corner_idx = 0; + } else + { + std::cerr << "s: " << s << "\t d:" << d << std::endl; + std::cerr << F.row(preferred_facet) << std::endl; + throw std::runtime_error( + "Invalid connectivity, edge does not belong to facet"); + } + + auto ueid = EMAP(preferred_facet + corner_idx * F.rows()); + auto eids = uE2E[ueid]; + std::vector intersected_face_indices; + for (auto eid : eids) + { + const size_t fid = eid % F.rows(); + if (in_I[fid]) + { + intersected_face_indices.push_back(fid); + } + } + + const size_t num_intersected_faces = intersected_face_indices.size(); + std::vector intersected_face_signed_indices(num_intersected_faces); + std::transform( + intersected_face_indices.begin(), + intersected_face_indices.end(), + intersected_face_signed_indices.begin(), + [&](size_t index) { + return index_to_signed_index( + index, get_orientation(index, s,d)); + }); + + assert(num_intersected_faces >= 1); + if (num_intersected_faces == 1) + { + // The edge must be a boundary edge. Thus, the orientation can be + // simply determined by checking if the query point is on the + // positive side of the facet. + const size_t fid = intersected_face_indices[0]; + orientation = on_the_positive_side(fid, query_point); + return fid; + } + + Eigen::VectorXi order; + DerivedP pivot = P.row(query_idx).eval(); + igl::copyleft::cgal::order_facets_around_edge(V, F, s, d, + intersected_face_signed_indices, + pivot, order); + + // Although first and last are equivalent, make the choice based on + // preferred_facet. + const size_t first = order[0]; + const size_t last = order[num_intersected_faces-1]; + if (intersected_face_indices[first] == preferred_facet) { + orientation = intersected_face_signed_indices[first] < 0; + return intersected_face_indices[first]; + } else if (intersected_face_indices[last] == preferred_facet) { + orientation = intersected_face_signed_indices[last] > 0; + return intersected_face_indices[last]; + } else { + orientation = intersected_face_signed_indices[order[0]] < 0; + return intersected_face_indices[order[0]]; + } + }; + + auto process_face_case = [&]( + const size_t query_idx, const Point_3& closest_point, + const size_t fid, bool& orientation) -> size_t { + const auto& f = F.row(I(fid, 0)); + return process_edge_case(query_idx, f[0], f[1], I(fid, 0), orientation); + }; + + // Given that the closest point to query point P(query_idx,:) on (V,F(I,:)) + // is the vertex at V(s,:) which is incident at least on triangle + // F(preferred_facet,:), determine a facet incident on V(s,:) that is + // _exposed_ to the query point and determine whether that facet is facing + // _toward_ or _away_ from the query point. + // + // Inputs: + // query_idx index into P of query point + // s index into V of closest point at vertex + // preferred_facet facet incident on s + // Outputs: + // orientation whether returned face is facing toward or away from + // query (parity unclear) + // Returns face guaranteed to be "exposed" to P(query_idx,:) + auto process_vertex_case = [&]( + const size_t query_idx, + size_t s, + size_t preferred_facet, + bool& orientation) -> size_t + { + const Point_3 query_point( + P(query_idx, 0), P(query_idx, 1), P(query_idx, 2)); + const Point_3 closest_point(V(s, 0), V(s, 1), V(s, 2)); + std::vector adj_faces; + std::vector adj_face_corners; + { + // Gather adj faces to s within I. + const auto& all_adj_faces = VF[s]; + const auto& all_adj_face_corners = VFi[s]; + const size_t num_all_adj_faces = all_adj_faces.size(); + for (size_t i=0; i 0); + + std::set adj_vertices_set; + std::unordered_multimap v2f; + for (size_t i=0; i adj_vertices(num_adj_vertices); + std::copy(adj_vertices_set.begin(), adj_vertices_set.end(), + adj_vertices.begin()); + + std::vector adj_points; + for (size_t vid : adj_vertices) + { + adj_points.emplace_back(V(vid,0), V(vid,1), V(vid,2)); + } + + // A plane is on the exterior if all adj_points lies on or to + // one side of the plane. + auto is_on_exterior = [&](const Plane_3& separator) -> bool{ + size_t positive=0; + size_t negative=0; + size_t coplanar=0; + for (const auto& point : adj_points) { + switch(separator.oriented_side(point)) { + case CGAL::ON_POSITIVE_SIDE: + positive++; + break; + case CGAL::ON_NEGATIVE_SIDE: + negative++; + break; + case CGAL::ON_ORIENTED_BOUNDARY: + coplanar++; + break; + default: + throw "Unknown plane-point orientation"; + } + } + auto query_orientation = separator.oriented_side(query_point); + if (query_orientation == CGAL::ON_ORIENTED_BOUNDARY && + (positive == 0 && negative == 0)) { + // All adj vertices and query point are coplanar. + // In this case, all separators are equally valid. + return true; + } else { + bool r = (positive == 0 && query_orientation == CGAL::POSITIVE) + || (negative == 0 && query_orientation == CGAL::NEGATIVE); + return r; + } + }; + + size_t d = std::numeric_limits::max(); + for (size_t i=0; i::max()) { + Eigen::MatrixXd tmp_vertices(V.rows(), V.cols()); + for (size_t i=0; isecond, orientation); + }; + + const size_t num_queries = P.rows(); + R.resize(num_queries, 1); + S.resize(num_queries, 1); + for (size_t i=0; i intersected_faces; + tree.all_intersected_primitives(Segment_3(closest_point, query), + std::back_inserter(intersected_faces)); + const size_t num_intersected_faces = intersected_faces.size(); + std::vector intersected_face_indices(num_intersected_faces); + std::transform(intersected_faces.begin(), + intersected_faces.end(), + intersected_face_indices.begin(), + [&](const typename Tree::Primitive_id& itr) -> size_t + { return I(itr-triangles.begin(), 0); }); + + size_t element_index; + auto element_type = determine_element_type(closest_point, fid, + element_index); + switch(element_type) { + case VERTEX: + { + const auto& f = F.row(I(fid, 0)); + const size_t s = f[element_index]; + fid = process_vertex_case(i, s, I(fid, 0), fid_ori); + } + break; + case EDGE: + { + const auto& f = F.row(I(fid, 0)); + const size_t s = f[(element_index+1)%3]; + const size_t d = f[(element_index+2)%3]; + fid = process_edge_case(i, s, d, I(fid, 0), fid_ori); + } + break; + case FACE: + { + fid = process_face_case(i, closest_point, fid, fid_ori); + } + break; + default: + throw std::runtime_error("Unknown element type."); + } + + + R(i,0) = fid; + S(i,0) = fid_ori; + } +} + +template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename DerivedR, + typename DerivedS > +IGL_INLINE void igl::copyleft::cgal::closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S) { + const size_t num_faces = F.rows(); + Eigen::VectorXi I = igl::LinSpaced(num_faces, 0, num_faces-1); + igl::copyleft::cgal::closest_facet(V, F, I, P, uE2E, EMAP, R, S); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::closest_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, unsigned long, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, CGAL::AABB_tree >::iterator, CGAL::Boolean_tag > > > const&, std::vector > const&, std::vector > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::closest_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, unsigned long, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, CGAL::AABB_tree >::iterator, CGAL::Boolean_tag > > > const&, std::vector > const&, std::vector > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::closest_facet, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, unsigned long, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::copyleft::cgal::closest_facet, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, -1, -1, 0, -1, -1>, unsigned __int64, class Eigen::Matrix, class CGAL::Epeck, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class std::vector>, class std::allocator>>> const &, class CGAL::AABB_tree>>>, struct CGAL::Boolean_tag<0>>>> const &, class std::vector, class std::allocator>> const &, class std::vector> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::closest_facet, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, -1, -1, 1, -1, -1>, unsigned __int64, class Eigen::Matrix, class CGAL::Epeck, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class std::vector>, class std::allocator>>> const &, class CGAL::AABB_tree>>>, struct CGAL::Boolean_tag<0>>>> const &, class std::vector, class std::allocator>> const &, class std::vector> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::closest_facet, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, -1, -1, 1, -1, -1>, unsigned __int64, class Eigen::Matrix, class CGAL::Epeck, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class std::vector>, class std::allocator>>> const &, class CGAL::AABB_tree>>>, struct CGAL::Boolean_tag<0>>>> const &, class std::vector, class std::allocator>> const &, class std::vector> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/closest_facet.h b/src/igl/copyleft/cgal/closest_facet.h new file mode 100644 index 000000000..737ca2e22 --- /dev/null +++ b/src/igl/copyleft/cgal/closest_facet.h @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLET_CGAL_CLOSEST_FACET_H +#define IGL_COPYLET_CGAL_CLOSEST_FACET_H + +#include "../../igl_inline.h" +#include +#include + +#include +#include +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Determine the closest facet for each of the input points. + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // I #I list of triangle indices to consider. + // P #P by 3 array of query points. + // + // Outputs: + // R #P list of closest facet indices. + // S #P list of bools indicating on which side of the closest facet + // each query point lies. + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename DerivedR, + typename DerivedS > + IGL_INLINE void closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S); + template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename DerivedR, + typename DerivedS > + IGL_INLINE void closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S); + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedP, + typename uE2EType, + typename DerivedEMAP, + typename Kernel, + typename DerivedR, + typename DerivedS > + IGL_INLINE void closest_facet( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + const std::vector > & VF, + const std::vector > & VFi, + const CGAL::AABB_tree< + CGAL::AABB_traits< + Kernel, + CGAL::AABB_triangle_primitive< + Kernel, typename std::vector< + typename Kernel::Triangle_3 >::iterator > > > & tree, + const std::vector & triangles, + const std::vector & in_I, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& S); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "closest_facet.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/complex_to_mesh.cpp b/src/igl/copyleft/cgal/complex_to_mesh.cpp new file mode 100644 index 000000000..74bf085ab --- /dev/null +++ b/src/igl/copyleft/cgal/complex_to_mesh.cpp @@ -0,0 +1,152 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "complex_to_mesh.h" + +#include "../../centroid.h" +#include "../../remove_unreferenced.h" + +#include +#include +#include +#include + +template +IGL_INLINE bool igl::copyleft::cgal::complex_to_mesh( + const CGAL::Complex_2_in_triangulation_3 & c2t3, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + using namespace Eigen; + // CGAL/IO/Complex_2_in_triangulation_3_file_writer.h + using CGAL::Surface_mesher::number_of_facets_on_surface; + + typedef typename CGAL::Complex_2_in_triangulation_3 C2t3; + typedef typename Tr::Finite_facets_iterator Finite_facets_iterator; + typedef typename Tr::Finite_vertices_iterator Finite_vertices_iterator; + typedef typename Tr::Facet Facet; + typedef typename Tr::Edge Edge; + typedef typename Tr::Vertex_handle Vertex_handle; + + // Header. + const Tr& tr = c2t3.triangulation(); + + bool success = true; + + const int n = tr.number_of_vertices(); + const int m = c2t3.number_of_facets(); + + assert(m == number_of_facets_on_surface(tr)); + + // Finite vertices coordinates. + std::map v2i; + V.resize(n,3); + { + int v = 0; + for(Finite_vertices_iterator vit = tr.finite_vertices_begin(); + vit != tr.finite_vertices_end(); + ++vit) + { + V(v,0) = vit->point().x(); + V(v,1) = vit->point().y(); + V(v,2) = vit->point().z(); + v2i[vit] = v++; + } + } + + { + Finite_facets_iterator fit = tr.finite_facets_begin(); + std::set oriented_set; + std::stack stack; + + while ((int)oriented_set.size() != m) + { + 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)); + + const typename C2t3::Face_status face_status + = c2t3.face_status(Edge(f.first, i1, i2)); + if(face_status == C2t3::REGULAR) + { + Facet fn = c2t3.neighbor(f, ih); + if (oriented_set.find(fn) == oriented_set.end()) + { + if(oriented_set.find(c2t3.opposite_facet(fn)) == oriented_set.end()) + { + oriented_set.insert(fn); + stack.push(fn); + }else { + success = false; // non-orientable + } + } + }else if(face_status != C2t3::BOUNDARY) + { + success = false; // non manifold, thus non-orientable + } + } // end "for each neighbor of f" + } // end "stack non empty" + } // end "oriented_set not full" + + F.resize(m,3); + int f = 0; + for(typename std::set::const_iterator fit = + oriented_set.begin(); + fit != oriented_set.end(); + ++fit) + { + const typename Tr::Cell_handle cell = fit->first; + const int& index = fit->second; + const int index1 = v2i[cell->vertex(tr.vertex_triple_index(index, 0))]; + const int index2 = v2i[cell->vertex(tr.vertex_triple_index(index, 1))]; + const int index3 = v2i[cell->vertex(tr.vertex_triple_index(index, 2))]; + // This order is flipped + F(f,0) = index1; + F(f,1) = index2; + F(f,2) = index3; + f++; + } + assert(f == m); + } // end if(facets must be oriented) + + // This CGAL code seems to randomly assign the global orientation + // Flip based on the signed volume. + Eigen::Vector3d cen; + double vol; + igl::centroid(V,F,cen,vol); + if(vol < 0) + { + // Flip + F = F.rowwise().reverse().eval(); + } + + // CGAL code somehow can end up with unreferenced vertices + { + VectorXi _1; + remove_unreferenced( MatrixXd(V), MatrixXi(F), V,F,_1); + } + + return success; +} + +#ifdef IGL_STATIC_LIBRARY +template bool igl::copyleft::cgal::complex_to_mesh, CGAL::Triangulation_data_structure_3, CGAL::Triangulation_vertex_base_3, CGAL::Triangulation_ds_vertex_base_3 > >, CGAL::Delaunay_triangulation_cell_base_with_circumcenter_3, CGAL::Surface_mesh_cell_base_3, CGAL::Triangulation_cell_base_3, CGAL::Triangulation_ds_cell_base_3 > > >, CGAL::Sequential_tag>, CGAL::Default, CGAL::Default>, Eigen::Matrix, Eigen::Matrix >(CGAL::Complex_2_in_triangulation_3, CGAL::Triangulation_data_structure_3, CGAL::Triangulation_vertex_base_3, CGAL::Triangulation_ds_vertex_base_3 > >, CGAL::Delaunay_triangulation_cell_base_with_circumcenter_3, CGAL::Surface_mesh_cell_base_3, CGAL::Triangulation_cell_base_3, CGAL::Triangulation_ds_cell_base_3 > > >, CGAL::Sequential_tag>, CGAL::Default, CGAL::Default>, void> const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/complex_to_mesh.h b/src/igl/copyleft/cgal/complex_to_mesh.h new file mode 100644 index 000000000..ed66064b7 --- /dev/null +++ b/src/igl/copyleft/cgal/complex_to_mesh.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_COMPLEX_TO_MESH_H +#define IGL_COPYLEFT_CGAL_COMPLEX_TO_MESH_H +#include "../../igl_inline.h" + +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Templates: + // Tr CGAL triangulation type, e.g. + // CGAL::Surface_mesh_default_triangulation_3 + // Inputs + // c2t3 2-complex (surface) living in a 3d triangulation (e.g. result of + // CGAL::make_surface_mesh) + // Outputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // Returns true iff conversion was successful, failure can ok if CGAL code + // can't figure out ordering. + // + template + IGL_INLINE bool complex_to_mesh( + const CGAL::Complex_2_in_triangulation_3 & c2t3, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "complex_to_mesh.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/component_inside_component.cpp b/src/igl/copyleft/cgal/component_inside_component.cpp new file mode 100644 index 000000000..251e91949 --- /dev/null +++ b/src/igl/copyleft/cgal/component_inside_component.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "component_inside_component.h" + +#include "order_facets_around_edge.h" +#include "../../LinSpaced.h" +#include "points_inside_component.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +template +IGL_INLINE bool igl::copyleft::cgal::component_inside_component( + const Eigen::PlainObjectBase& V1, + const Eigen::PlainObjectBase& F1, + const Eigen::PlainObjectBase& I1, + const Eigen::PlainObjectBase& V2, + const Eigen::PlainObjectBase& F2, + const Eigen::PlainObjectBase& I2) { + if (F1.rows() <= 0 || I1.rows() <= 0 || F2.rows() <= 0 || I2.rows() <= 0) { + throw "Component inside test cannot be done on empty component!"; + } + + const Eigen::Vector3i& f = F1.row(I1(0, 0)); + const Eigen::Matrix query( + (V1(f[0],0) + V1(f[1],0) + V1(f[2],0))/3.0, + (V1(f[0],1) + V1(f[1],1) + V1(f[2],1))/3.0, + (V1(f[0],2) + V1(f[1],2) + V1(f[2],2))/3.0); + Eigen::VectorXi inside; + igl::copyleft::cgal::points_inside_component(V2, F2, I2, query, inside); + assert(inside.size() == 1); + return inside[0]; +} + +template +IGL_INLINE bool igl::copyleft::cgal::component_inside_component( + const Eigen::PlainObjectBase& V1, + const Eigen::PlainObjectBase& F1, + const Eigen::PlainObjectBase& V2, + const Eigen::PlainObjectBase& F2) { + if (F1.rows() <= 0 || F2.rows() <= 0) { + throw "Component inside test cannot be done on empty component!"; + } + Eigen::VectorXi I1(F1.rows()), I2(F2.rows()); + I1 = igl::LinSpaced(F1.rows(), 0, F1.rows()-1); + I2 = igl::LinSpaced(F2.rows(), 0, F2.rows()-1); + return igl::copyleft::cgal::component_inside_component(V1, F1, I1, V2, F2, I2); +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::copyleft::cgal::component_inside_component< +Eigen::Matrix, +Eigen::Matrix< int, -1, -1, 0, -1, -1>, +Eigen::Matrix< int, -1, -1, 0, -1, -1> > ( +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&); + +template bool igl::copyleft::cgal::component_inside_component< +Eigen::Matrix, +Eigen::Matrix< int, -1, -1, 0, -1, -1> > ( +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&, +Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/copyleft/cgal/component_inside_component.h b/src/igl/copyleft/cgal/component_inside_component.h new file mode 100644 index 000000000..17fdd6255 --- /dev/null +++ b/src/igl/copyleft/cgal/component_inside_component.h @@ -0,0 +1,75 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_COMONENT_INSIDE_COMPONENT +#define IGL_COPYLEFT_CGAL_COMONENT_INSIDE_COMPONENT + +#include "../../igl_inline.h" +#include +#include + +namespace igl { + namespace copyleft + { + namespace cgal { + + // Determine if connected facet component (V1, F1, I1) is inside of + // connected facet component (V2, F2, I2). + // + // Precondition: + // Both components must represent closed, self-intersection free, + // non-degenerated surfaces that are the boundary of 3D volumes. In + // addition, (V1, F1, I1) must not intersect with (V2, F2, I2). + // + // Inputs: + // V1 #V1 by 3 list of vertex position of mesh 1 + // F1 #F1 by 3 list of triangles indices into V1 + // I1 #I1 list of indices into F1, indicate the facets of component + // V2 #V2 by 3 list of vertex position of mesh 2 + // F2 #F2 by 3 list of triangles indices into V2 + // I2 #I2 list of indices into F2, indicate the facets of component + // + // Outputs: + // return true iff (V1, F1, I1) is entirely inside of (V2, F2, I2). + template + IGL_INLINE bool component_inside_component( + const Eigen::PlainObjectBase& V1, + const Eigen::PlainObjectBase& F1, + const Eigen::PlainObjectBase& I1, + const Eigen::PlainObjectBase& V2, + const Eigen::PlainObjectBase& F2, + const Eigen::PlainObjectBase& I2); + + // Determine if mesh (V1, F1) is inside of mesh (V2, F2). + // + // Precondition: + // Both meshes must be closed, self-intersection free, non-degenerated + // surfaces that are the boundary of 3D volumes. They should not + // intersect each other. + // + // Inputs: + // V1 #V1 by 3 list of vertex position of mesh 1 + // F1 #F1 by 3 list of triangles indices into V1 + // V2 #V2 by 3 list of vertex position of mesh 2 + // F2 #F2 by 3 list of triangles indices into V2 + // + // Outputs: + // return true iff (V1, F1) is entirely inside of (V2, F2). + template + IGL_INLINE bool component_inside_component( + const Eigen::PlainObjectBase& V1, + const Eigen::PlainObjectBase& F1, + const Eigen::PlainObjectBase& V2, + const Eigen::PlainObjectBase& F2); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "component_inside_component.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/convex_hull.cpp b/src/igl/copyleft/cgal/convex_hull.cpp new file mode 100644 index 000000000..8f89d5593 --- /dev/null +++ b/src/igl/copyleft/cgal/convex_hull.cpp @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "convex_hull.h" +#include "../../ismember.h" +#include "polyhedron_to_mesh.h" +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedW, + typename DerivedG> +IGL_INLINE void igl::copyleft::cgal::convex_hull( + const Eigen::MatrixBase & V, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G) +{ + typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + typedef K::Point_3 Point_3; + //typedef CGAL::Delaunay_triangulation_3 Delaunay; + //typedef Delaunay::Vertex_handle Vertex_handle; + //typedef CGAL::Surface_mesh Surface_mesh; + typedef CGAL::Polyhedron_3 Polyhedron_3; + std::vector points(V.rows()); + for(int i = 0;i +IGL_INLINE void igl::copyleft::cgal::convex_hull( + const Eigen::MatrixBase & V, + Eigen::PlainObjectBase & F) +{ + Eigen::Matrix W; + Eigen::Matrix G; + convex_hull(V,W,G); + // This is a lazy way to reindex into the original mesh + Eigen::Matrix I; + Eigen::VectorXi J; + igl::ismember_rows(W,V,I,J); + assert(I.all() && "Should find all W in V"); + F.resizeLike(G); + for(int f = 0;f, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/convex_hull.h b/src/igl/copyleft/cgal/convex_hull.h new file mode 100644 index 000000000..ba04dd7cc --- /dev/null +++ b/src/igl/copyleft/cgal/convex_hull.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_CONVEX_HULL_H +#define IGL_COPYLEFT_CGAL_CONVEX_HULL_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a set of points (V), compute the convex hull as a triangle mesh (W,G) + // + // Inputs: + // V #V by 3 list of input points + // Outputs: + // W #W by 3 list of convex hull points + // G #G by 3 list of triangle indices into W + template < + typename DerivedV, + typename DerivedW, + typename DerivedG> + IGL_INLINE void convex_hull( + const Eigen::MatrixBase & V, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G); + // Given a set of points (V), compute the convex hull as a triangle mesh (F) + // over input vertex set (V) + // + // Inputs: + // V #V by 3 list of input points + // Outputs: + // F #F by 3 list of triangle indices into V + // + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE void convex_hull( + const Eigen::MatrixBase & V, + Eigen::PlainObjectBase & F); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "convex_hull.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.cpp b/src/igl/copyleft/cgal/delaunay_triangulation.cpp new file mode 100644 index 000000000..787f79f9e --- /dev/null +++ b/src/igl/copyleft/cgal/delaunay_triangulation.cpp @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "delaunay_triangulation.h" +#include "../../delaunay_triangulation.h" +#include "orient2D.h" +#include "incircle.h" + +template< + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::copyleft::cgal::delaunay_triangulation( + const Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + typedef typename DerivedV::Scalar Scalar; + igl::delaunay_triangulation(V, orient2D, incircle, F); + // This function really exists to test our igl::delaunay_triangulation + // + // It's currently much faster to call cgal's native Delaunay routine + // +//#include +//#include +//#include +//#include +// const auto delaunay = +// [&](const Eigen::MatrixXd & V,Eigen::MatrixXi & F) +// { +// typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +// typedef CGAL::Triangulation_vertex_base_with_info_2 Vb; +// typedef CGAL::Triangulation_data_structure_2 Tds; +// typedef CGAL::Delaunay_triangulation_2 Delaunay; +// typedef Kernel::Point_2 Point; +// std::vector< std::pair > points(V.rows()); +// for(int i = 0;ivertex(0)->info(); +// F(j,1) = face->vertex(1)->info(); +// F(j,2) = face->vertex(2)->info(); +// j++; +// } +// } +// }; +} + diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.h b/src/igl/copyleft/cgal/delaunay_triangulation.h new file mode 100644 index 000000000..88cbf9fe0 --- /dev/null +++ b/src/igl/copyleft/cgal/delaunay_triangulation.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_DELAUNAY_TRIANGULATION_H +#define IGL_COPYLEFT_CGAL_DELAUNAY_TRIANGULATION_H + +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + + // Given a set of points in 2D, return a Delaunay triangulation of these + // points. + // + // Inputs: + // V #V by 2 list of vertex positions + // + // Outputs: + // F #F by 3 of faces in Delaunay triangulation. + template< + typename DerivedV, + typename DerivedF + > + IGL_INLINE void delaunay_triangulation( + const Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); + } + } +} + + + + +#ifndef IGL_STATIC_LIBRARY +# include "delaunay_triangulation.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/extract_cells.cpp b/src/igl/copyleft/cgal/extract_cells.cpp new file mode 100644 index 000000000..a314edfae --- /dev/null +++ b/src/igl/copyleft/cgal/extract_cells.cpp @@ -0,0 +1,547 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "extract_cells.h" +#include "closest_facet.h" +#include "order_facets_around_edge.h" +#include "outer_facet.h" +#include "submesh_aabb_tree.h" +#include "../../extract_manifold_patches.h" +#include "../../facet_components.h" +#include "../../get_seconds.h" +#include "../../triangle_triangle_adjacency.h" +#include "../../unique_edge_map.h" +#include "../../vertex_triangle_adjacency.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//#define EXTRACT_CELLS_DEBUG + +template< + typename DerivedV, + typename DerivedF, + typename DerivedC > +IGL_INLINE size_t igl::copyleft::cgal::extract_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& cells) +{ + const size_t num_faces = F.rows(); + // Construct edge adjacency + Eigen::MatrixXi E, uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + // Cluster into manifold patches + Eigen::VectorXi P; + igl::extract_manifold_patches(F, EMAP, uE2E, P); + // Extract cells + DerivedC per_patch_cells; + const size_t num_cells = + igl::copyleft::cgal::extract_cells(V,F,P,E,uE,uE2E,EMAP,per_patch_cells); + // Distribute per-patch cell information to each face + cells.resize(num_faces, 2); + for (size_t i=0; i +IGL_INLINE size_t igl::copyleft::cgal::extract_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& cells) +{ + // Trivial base case + if(P.size() == 0) + { + assert(F.size() == 0); + cells.resize(0,2); + return 0; + } + + typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; + typedef Kernel::Point_3 Point_3; + typedef Kernel::Plane_3 Plane_3; + typedef Kernel::Segment_3 Segment_3; + typedef Kernel::Triangle_3 Triangle; + typedef std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + +#ifdef EXTRACT_CELLS_DEBUG + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void { + std::cout << "extract_cells." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#else + // no-op + const auto log_time = [](const std::string){}; +#endif + const size_t num_faces = F.rows(); + typedef typename DerivedF::Scalar Index; + assert(P.size() > 0); + const size_t num_patches = P.maxCoeff()+1; + + // Extract all cells... + DerivedC raw_cells; + const size_t num_raw_cells = + extract_cells_single_component(V,F,P,uE,uE2E,EMAP,raw_cells); + log_time("extract_single_component_cells"); + + // Compute triangle-triangle adjacency data-structure + std::vector > > TT,_1; + igl::triangle_triangle_adjacency(E, EMAP, uE2E, false, TT, _1); + log_time("compute_face_adjacency"); + + // Compute connected components of the mesh + Eigen::VectorXi C, counts; + igl::facet_components(TT, C, counts); + log_time("form_components"); + + const size_t num_components = counts.size(); + // components[c] --> list of face indices into F of faces in component c + std::vector > components(num_components); + // Loop over all faces + for (size_t i=0; i > VF,VFi; + igl::vertex_triangle_adjacency(V.rows(), F, VF, VFi); + std::vector Is(num_components); + std::vector< + CGAL::AABB_tree< + CGAL::AABB_traits< + Kernel, + CGAL::AABB_triangle_primitive< + Kernel, std::vector< + Kernel::Triangle_3 >::iterator > > > > trees(num_components); + std::vector< std::vector > + triangle_lists(num_components); + std::vector > in_Is(num_components); + + // Find outer facets, their orientations and cells for each component + Eigen::VectorXi outer_facets(num_components); + Eigen::VectorXi outer_facet_orientation(num_components); + Eigen::VectorXi outer_cells(num_components); + for (size_t i=0; i > nested_cells(num_raw_cells); + std::vector > ambient_cells(num_raw_cells); + std::vector > ambient_comps(num_components); + // Only bother if there's more than one component + if(num_components > 1) + { + // construct bounding boxes for each component + DerivedV bbox_min(num_components, 3); + DerivedV bbox_max(num_components, 3); + // Assuming our mesh (in exact numbers) fits in the range of double. + bbox_min.setConstant(std::numeric_limits::max()); + bbox_max.setConstant(std::numeric_limits::min()); + // Loop over faces + for (size_t i=0; i candidate_comps; + candidate_comps.reserve(num_components); + // Loop over components + for (size_t j=0; j component index inside component i, because the cell of the + // closest facet on i to component index is **not** the same as the + // "outer cell" of component i: component index is **not** outside of + // component i (therefore it's inside). + nested_cells[ambient_cell].push_back(outer_cells[index]); + ambient_cells[outer_cells[index]].push_back(ambient_cell); + ambient_comps[index].push_back(i); + } + } + } + } + +#ifdef EXTRACT_CELLS_DEBUG + log_time("nested_relationship"); +#endif + + const size_t INVALID = std::numeric_limits::max(); + const size_t INFINITE_CELL = num_raw_cells; + std::vector embedded_cells(num_raw_cells, INVALID); + for (size_t i=0; i 0) { + size_t embedded_comp = INVALID; + size_t embedded_cell = INVALID; + for (size_t j=0; j mapped_indices(num_raw_cells+1, INVALID); + // Always map infinite cell to index 0. + mapped_indices[INFINITE_CELL] = count; + count++; + + for (size_t i=0; i +IGL_INLINE size_t igl::copyleft::cgal::extract_cells_single_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& cells) +{ + const size_t num_faces = F.rows(); + // Input: + // index index into #F*3 list of undirect edges + // Returns index into face + const auto edge_index_to_face_index = [&num_faces](size_t index) + { + return index % num_faces; + }; + // Determine if a face (containing undirected edge {s,d} is consistently + // oriented with directed edge {s,d} (or otherwise it is with {d,s}) + // + // Inputs: + // fid face index into F + // s source index of edge + // d destination index of edge + // Returns true if face F(fid,:) is consistent with {s,d} + const auto is_consistent = + [&F](const size_t fid, const size_t s, const size_t d) -> bool + { + if ((size_t)F(fid, 0) == s && (size_t)F(fid, 1) == d) return false; + if ((size_t)F(fid, 1) == s && (size_t)F(fid, 2) == d) return false; + if ((size_t)F(fid, 2) == s && (size_t)F(fid, 0) == d) return false; + + if ((size_t)F(fid, 0) == d && (size_t)F(fid, 1) == s) return true; + if ((size_t)F(fid, 1) == d && (size_t)F(fid, 2) == s) return true; + if ((size_t)F(fid, 2) == d && (size_t)F(fid, 0) == s) return true; + throw "Invalid face!"; + return false; + }; + + const size_t num_unique_edges = uE.rows(); + const size_t num_patches = P.maxCoeff() + 1; + + // Build patch-patch adjacency list. + std::vector > patch_adj(num_patches); + for (size_t i=0; i 2) { + for (size_t j=0; j::max(); + std::vector cell_labels(num_patches * 2); + for (size_t i=0; i > equivalent_cells(num_patches*2); + std::vector processed(num_unique_edges, false); + + size_t label_count=0; + for (size_t i=0; i 2); + + const size_t s = uE(uei,0); + const size_t d = uE(uei,1); + + std::vector signed_adj_faces; + for (auto ej : adj_faces) + { + const size_t fid = edge_index_to_face_index(ej); + bool cons = is_consistent(fid, s, d); + signed_adj_faces.push_back((fid+1)*(cons ? 1:-1)); + } + { + // Sort adjacent faces cyclically around {s,d} + Eigen::VectorXi order; + // order[f] will reveal the order of face f in signed_adj_faces + order_facets_around_edge(V, F, s, d, signed_adj_faces, order); + for (size_t j=0; j 0; + const bool next_cons = signed_adj_faces[order[next_idx]] > 0; + const size_t curr_cell_idx = curr_patch_idx*2 + (curr_cons?0:1); + const size_t next_cell_idx = next_patch_idx*2 + (next_cons?1:0); + equivalent_cells[curr_cell_idx].insert(next_cell_idx); + equivalent_cells[next_cell_idx].insert(curr_cell_idx); + } + } + } + } + + size_t count=0; + cells.resize(num_patches, 2); + cells.setConstant(INVALID); + const auto extract_equivalent_cells = [&](size_t i) { + if (cells(i/2, i%2) != INVALID) return; + std::queue Q; + Q.push(i); + cells(i/2, i%2) = count; + while (!Q.empty()) { + const size_t index = Q.front(); + Q.pop(); + for (const auto j : equivalent_cells[index]) { + if (cells(j/2, j%2) == INVALID) { + cells(j/2, j%2) = count; + Q.push(j); + } + } + } + count++; + }; + for (size_t i=0; i, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template unsigned long igl::copyleft::cgal::extract_cells, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#include +template unsigned long igl::copyleft::cgal::extract_cells, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template unsigned long igl::copyleft::cgal::extract_cells, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template unsigned __int64 igl::copyleft::cgal::extract_cells, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +template unsigned __int64 igl::copyleft::cgal::extract_cells, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +template unsigned __int64 igl::copyleft::cgal::extract_cells, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/extract_cells.h b/src/igl/copyleft/cgal/extract_cells.h new file mode 100644 index 000000000..9575ffbae --- /dev/null +++ b/src/igl/copyleft/cgal/extract_cells.h @@ -0,0 +1,116 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_EXTRACT_CELLS +#define IGL_COPYLEFT_CGAL_EXTRACT_CELLS + +#include "../../igl_inline.h" +#include +#include + +namespace igl { + namespace copyleft + { + namespace cgal + { + // Extract connected 3D space partitioned by mesh (V, F). + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // + // Output: + // cells #F by 2 array of cell indices. cells(i,0) represents the + // cell index on the positive side of face i, and cells(i,1) + // represents cell index of the negqtive side. + // By convension cell with index 0 is the infinite cell. + // Returns the number of cells + template< + typename DerivedV, + typename DerivedF, + typename DerivedC > + IGL_INLINE size_t extract_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& cells); + + // Extract connected 3D space partitioned by mesh (V, F). + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // P #F list of patch indices. + // E #E by 2 array of vertex indices, one edge per row. + // uE #uE by 2 list of vertex_indices, represents undirected edges. + // uE2E #uE list of lists that maps uE to E. (a one-to-many map) + // EMAP #F*3 list of indices into uE. + // + // Output: + // cells #P by 2 array of cell indices. cells(i,0) represents the + // cell index on the positive side of patch i, and cells(i,1) + // represents cell index of the negqtive side. + // By convension cell with index 0 is the infinite cell. + template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedE, + typename DeriveduE, + typename uE2EType, + typename DerivedEMAP, + typename DerivedC > + IGL_INLINE size_t extract_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& cells); + + // Extract connected 3D space partitioned by mesh (V,F) composed of + // **possibly multiple components** (the name of this function is + // dubious). + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // P #F list of patch indices. + // E #E by 2 array of vertex indices, one edge per row. + // uE #uE by 2 list of vertex_indices, represents undirected edges. + // uE2E #uE list of lists that maps uE to E. (a one-to-many map) + // EMAP #F*3 list of indices into uE. + // Output: + // cells #P by 2 array of cell indices. cells(i,0) represents the + // cell index on the positive side of patch i, and cells(i,1) + // represents cell index of the negative side. + template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DeriveduE, + typename uE2EType, + typename DerivedEMAP, + typename DerivedC > + IGL_INLINE size_t extract_cells_single_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& cells); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "extract_cells.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/extract_feature.cpp b/src/igl/copyleft/cgal/extract_feature.cpp new file mode 100644 index 000000000..ff8205d35 --- /dev/null +++ b/src/igl/copyleft/cgal/extract_feature.cpp @@ -0,0 +1,123 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "extract_feature.h" +#include +#include +#include + +template< + typename DerivedV, + typename DerivedF, + typename DerivedE > +IGL_INLINE void igl::copyleft::cgal::extract_feature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double tol, + Eigen::PlainObjectBase& feature_edges) { + + using IndexType = typename DerivedE::Scalar; + DerivedE E, uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + + igl::copyleft::cgal::extract_feature(V, F, tol, E, uE, uE2E, feature_edges); +} + +template< + typename DerivedV, + typename DerivedF, + typename DerivedE > +IGL_INLINE void igl::copyleft::cgal::extract_feature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double tol, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + Eigen::PlainObjectBase& feature_edges) { + + assert(V.cols() == 3); + assert(F.cols() == 3); + using Scalar = typename DerivedV::Scalar; + using IndexType = typename DerivedE::Scalar; + using Vertex = Eigen::Matrix; + using Kernel = typename CGAL::Exact_predicates_exact_constructions_kernel; + using Point = typename Kernel::Point_3; + + const size_t num_unique_edges = uE.rows(); + const size_t num_faces = F.rows(); + // NOTE: CGAL's definition of dihedral angle measures the angle between two + // facets instead of facet normals. + const double cos_tol = cos(igl::PI - tol); + std::vector result; // Indices into uE + + auto is_non_manifold = [&uE2E](size_t ei) -> bool { + return uE2E[ei].size() > 2; + }; + + auto is_boundary = [&uE2E](size_t ei) -> bool { + return uE2E[ei].size() == 1; + }; + + auto opposite_vertex = [&uE, &F](size_t ei, size_t fi) -> IndexType { + const size_t v0 = uE(ei, 0); + const size_t v1 = uE(ei, 1); + for (size_t i=0; i<3; i++) { + const size_t v = F(fi, i); + if (v != v0 && v != v1) { return v; } + } + throw "Input face must be topologically degenerate!"; + }; + + auto is_feature = [&V, &F, &uE, &uE2E, &opposite_vertex, num_faces]( + size_t ei, double cos_tol) -> bool { + auto adj_faces = uE2E[ei]; + assert(adj_faces.size() == 2); + const Vertex v0 = V.row(uE(ei, 0)); + const Vertex v1 = V.row(uE(ei, 1)); + const Vertex v2 = V.row(opposite_vertex(ei, adj_faces[0] % num_faces)); + const Vertex v3 = V.row(opposite_vertex(ei, adj_faces[1] % num_faces)); + const Point p0(v0[0], v0[1], v0[2]); + const Point p1(v1[0], v1[1], v1[2]); + const Point p2(v2[0], v2[1], v2[2]); + const Point p3(v3[0], v3[1], v3[2]); + const auto ori = CGAL::orientation(p0, p1, p2, p3); + switch (ori) { + case CGAL::POSITIVE: + return CGAL::compare_dihedral_angle(p0, p1, p2, p3, cos_tol) == + CGAL::SMALLER; + case CGAL::NEGATIVE: + return CGAL::compare_dihedral_angle(p0, p1, p3, p2, cos_tol) == + CGAL::SMALLER; + case CGAL::COPLANAR: + if (!CGAL::collinear(p0, p1, p2) && !CGAL::collinear(p0, p1, p3)) { + return CGAL::compare_dihedral_angle(p0, p1, p2, p3, cos_tol) == + CGAL::SMALLER; + } else { + throw "Dihedral angle (and feature edge) is not well defined for" + " degenerate triangles!"; + } + default: + throw "Unknown CGAL orientation"; + } + }; + + for (size_t i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_EXTRACT_FEATURE_H +#define IGL_COPYLEFT_CGAL_EXTRACT_FEATURE_H +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Extract feature edges based on dihedral angle. + // Here, dihedral angle is defined as the angle between surface + // __normals__ as described in + // http://mathworld.wolfram.com/DihedralAngle.html + // + // Non-manifold and boundary edges are automatically considered as + // features. + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // tol Edges with dihedral angle larger than this are considered + // as features. Angle is measured in radian. + // + // Output: + // feature_edges: #E by 2 array of edges. Each edge satisfies at + // least one of the following criteria: + // + // * Edge has dihedral angle larger than tol. + // * Edge is boundary. + // * Edge is non-manifold (i.e. it has more than 2 adjacent + // faces). + template < + typename DerivedV, + typename DerivedF, + typename DerivedE> + IGL_INLINE void extract_feature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double tol, + Eigen::PlainObjectBase& feature_edges); + + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // tol Edges with dihedral angle larger than this are considered + // as features. Angle is measured in radian. + // E #E by 2 array of directed edges. + // uE #uE by 2 array of undirected edges. + // uE2E #uE list of lists mapping undirected edges to all corresponding + // directed edges. + // + // Output: + // feature_edges: #E by 2 array of edges. Each edge satisfies at + // least one of the following criteria: + // + // * Edge has dihedral angle larger than tol. + // * Edge is boundary. + // * Edge is non-manifold (i.e. it has more than 2 adjacent + // faces). + template < + typename DerivedV, + typename DerivedF, + typename DerivedE> + IGL_INLINE void extract_feature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double tol, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + Eigen::PlainObjectBase& feature_edges); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "extract_feature.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/fast_winding_number.cpp b/src/igl/copyleft/cgal/fast_winding_number.cpp new file mode 100644 index 000000000..682754a86 --- /dev/null +++ b/src/igl/copyleft/cgal/fast_winding_number.cpp @@ -0,0 +1,82 @@ +#include "../../fast_winding_number.h" +#include "../../octree.h" +#include "../../knn.h" +#include "../../parallel_for.h" +#include "point_areas.h" +#include + +namespace igl { + namespace copyleft{ + namespace cgal{ + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& Q, + const int expansion_order, + const BetaType beta, + Eigen::PlainObjectBase& WN + ){ + typedef typename DerivedWN::Scalar real; + typedef typename Eigen::Matrix + RealMatrix; + + std::vector > point_indices; + Eigen::Matrix CH; + Eigen::Matrix CN; + Eigen::Matrix W; + Eigen::MatrixXi I; + Eigen::Matrix A; + + octree(P,point_indices,CH,CN,W); + knn(P,21,point_indices,CH,CN,W,I); + point_areas(P,I,N,A); + + Eigen::Matrix EC; + Eigen::Matrix CM; + Eigen::Matrix R; + + igl::fast_winding_number(P,N,A,point_indices,CH, + expansion_order,CM,R,EC); + igl::fast_winding_number(P,N,A,point_indices,CH, + CM,R,EC,Q,beta,WN); + } + + template + IGL_INLINE void fast_winding_number( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN + ){ + fast_winding_number(P,N,Q,2,2.0,WN); + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/igl/copyleft/cgal/fast_winding_number.h b/src/igl/copyleft/cgal/fast_winding_number.h new file mode 100644 index 000000000..a4ab15316 --- /dev/null +++ b/src/igl/copyleft/cgal/fast_winding_number.h @@ -0,0 +1,66 @@ +#ifndef IGL_COPYLEFT_CGAL_FAST_WINDING_NUMBER +#define IGL_COPYLEFT_CGAL_FAST_WINDING_NUMBER +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + // Evaluate the fast winding number for point data, without known areas. The + // areas are calculated using igl::knn and igl::copyleft::cgal::point_areas. + // + // This function performes the precomputation and evaluation all in one. + // If you need to acess the precomuptation for repeated evaluations, use the + // two functions designed for exposed precomputation, which are the first two + // functions see in igl/fast_winding_number.h + // + // Inputs: + // P #P by 3 list of point locations + // N #P by 3 list of point normals + // Q #Q by 3 list of query points for the winding number + // beta This is a Barnes-Hut style accuracy term that separates near feild + // from far field. The higher the beta, the more accurate and slower + // the evaluation. We reccommend using a beta value of 2. + // expansion_order the order of the taylor expansion. We support 0,1,2. + // Outputs: + // WN #Q by 1 list of windinng number values at each query point + // + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& Q, + const int expansion_order, + const BetaType beta, + Eigen::PlainObjectBase& WN + ); + + // Evaluate the fast winding number for point data, without known areas. The + // areas are calculated using igl::knn and + // igl::point_areas. This function uses the default expansion + // order and beta (both are set to 2). + // + // This function performes the precomputation and evaluation all in one. + // If you need to acess the precomuptation for repeated evaluations, use the + // two functions designed for exposed precomputation (described above). + + // Inputs: + // P #P by 3 list of point locations + // N #P by 3 list of point normals + // Q #Q by 3 list of query points for the winding number + // Outputs: + // WN #Q by 1 list of windinng number values at each query point + // + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN + ); +} +#ifndef IGL_STATIC_LIBRARY +# include "fast_winding_number.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/half_space_box.cpp b/src/igl/copyleft/cgal/half_space_box.cpp new file mode 100644 index 000000000..1bfa8ea52 --- /dev/null +++ b/src/igl/copyleft/cgal/half_space_box.cpp @@ -0,0 +1,123 @@ +#include "half_space_box.h" +#include "assign_scalar.h" +#include +#include + +template +IGL_INLINE void igl::copyleft::cgal::half_space_box( + const CGAL::Plane_3 & P, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF) +{ + typedef CGAL::Plane_3 Plane; + typedef CGAL::Point_3 Point; + typedef CGAL::Vector_3 Vector; + typedef CGAL::Epeck::FT EScalar; + Eigen::Matrix avg(0,0,0); + for(int v = 0;v vBV;vBV.reserve(8); + Vector v = CGAL::cross_product(u,n); + // Scale u,v,n to be longer than bbd + const auto & longer_than = [](const EScalar min_sqr, Vector & x) + { + assert(x.squared_length() > 0); + while(x.squared_length() < min_sqr) + { + x = 2.*x; + } + }; + longer_than(bbd*bbd,u); + longer_than(bbd*bbd,v); + longer_than(bbd*bbd,n); + vBV.emplace_back( o2 + u + v); + vBV.emplace_back( o2 - u + v); + vBV.emplace_back( o2 - u - v); + vBV.emplace_back( o2 + u - v); + vBV.emplace_back( o2 + u + v - n); + vBV.emplace_back( o2 - u + v - n); + vBV.emplace_back( o2 - u - v - n); + vBV.emplace_back( o2 + u - v - n); + BV.resize(8,3); + for(int b = 0;b<8;b++) + { + igl::copyleft::cgal::assign_scalar(vBV[b].x(),BV(b,0)); + igl::copyleft::cgal::assign_scalar(vBV[b].y(),BV(b,1)); + igl::copyleft::cgal::assign_scalar(vBV[b].z(),BV(b,2)); + } + BF.resize(12,3); + BF<< + 1,0,2, + 2,0,3, + 4,5,6, + 4,6,7, + 0,1,4, + 4,1,5, + 1,2,5, + 5,2,6, + 2,3,6, + 6,3,7, + 3,0,7, + 7,0,4; +} + +template +IGL_INLINE void igl::copyleft::cgal::half_space_box( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & n, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF) +{ + typedef CGAL::Plane_3 Plane; + typedef CGAL::Point_3 Point; + typedef CGAL::Vector_3 Vector; + Plane P(Point(p(0),p(1),p(2)),Vector(n(0),n(1),n(2))); + return half_space_box(P,V,BV,BF); +} + +template +IGL_INLINE void igl::copyleft::cgal::half_space_box( + const Eigen::MatrixBase & equ, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF) +{ + typedef CGAL::Plane_3 Plane; + Plane P(equ(0),equ(1),equ(2),equ(3)); + return half_space_box(P,V,BV,BF); +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::half_space_box >(CGAL::Plane_3 const&, Eigen::MatrixBase > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::half_space_box >(CGAL::Plane_3 const&, Eigen::MatrixBase > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +template void igl::copyleft::cgal::half_space_box, 1, 4, 1, 1, 4>, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::MatrixBase, 1, 4, 1, 1, 4> > const&, Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +template void igl::copyleft::cgal::half_space_box, 1, 4, 1, 1, 4>, Eigen::Matrix, -1, 4, 0, -1, 4> >(Eigen::MatrixBase, 1, 4, 1, 1, 4> > const&, Eigen::MatrixBase, -1, 4, 0, -1, 4> > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +template void igl::copyleft::cgal::half_space_box, 1, 4, 1, 1, 4>, Eigen::Matrix >(Eigen::MatrixBase, 1, 4, 1, 1, 4> > const&, Eigen::MatrixBase > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +template void igl::copyleft::cgal::half_space_box, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix, 8, 3, 0, 8, 3>&, Eigen::Matrix&); +#endif diff --git a/src/igl/copyleft/cgal/half_space_box.h b/src/igl/copyleft/cgal/half_space_box.h new file mode 100644 index 000000000..275502235 --- /dev/null +++ b/src/igl/copyleft/cgal/half_space_box.h @@ -0,0 +1,63 @@ +#ifndef IGL_COPYLEFT_CGAL_HALF_SPACE_BOX_H +#define IGL_COPYLEFT_CGAL_HALF_SPACE_BOX_H +#include "../../igl_inline.h" +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Construct a mesh of box (BV,BF) so that it contains the intersection of + // the half-space under the plane (P) and the bounding box of V, and does not + // contain any of the half-space above (P). + // + // Inputs: + // P plane so that normal points away from half-space + // V #V by 3 list of vertex positions + // Outputs: + // BV #BV by 3 list of box vertex positions + // BF #BF b3 list of box triangle indices into BV + template + IGL_INLINE void half_space_box( + const CGAL::Plane_3 & P, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF); + // Inputs: + // p 3d point on plane + // n 3d vector of normal of plane pointing away from inside + // V #V by 3 list of vertex positions + // Outputs: + // BV #BV by 3 list of box vertex positions + // BF #BF b3 list of box triangle indices into BV + template + IGL_INLINE void half_space_box( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & n, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF); + // Inputs: + // equ plane equation: a*x+b*y+c*z + d = 0 + // V #V by 3 list of vertex positions + // Outputs: + // BV #BV by 3 list of box vertex positions + // BF #BF b3 list of box triangle indices into BV + template + IGL_INLINE void half_space_box( + const Eigen::MatrixBase & equ, + const Eigen::MatrixBase & V, + Eigen::Matrix & BV, + Eigen::Matrix & BF); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "half_space_box.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/hausdorff.cpp b/src/igl/copyleft/cgal/hausdorff.cpp new file mode 100644 index 000000000..382e9b476 --- /dev/null +++ b/src/igl/copyleft/cgal/hausdorff.cpp @@ -0,0 +1,39 @@ +#include "hausdorff.h" +#include "../../hausdorff.h" +#include + +template < + typename DerivedV, + typename Kernel, + typename Scalar> +IGL_INLINE void igl::copyleft::cgal::hausdorff( + const Eigen::MatrixBase& V, + const CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & treeB, + const std::vector > & /*TB*/, + Scalar & l, + Scalar & u) +{ + // Not sure why using `auto` here doesn't work with the `hausdorff` function + // parameter but explicitly naming the type does... + const std::function + dist_to_B = [&treeB]( + const double & x, const double & y, const double & z)->double + { + CGAL::Point_3 query(x,y,z); + typename CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + >::Point_and_primitive_id pp = treeB.closest_point_and_primitive(query); + return std::sqrt((query-pp.first).squared_length()); + }; + return igl::hausdorff(V,dist_to_B,l,u); +} diff --git a/src/igl/copyleft/cgal/hausdorff.h b/src/igl/copyleft/cgal/hausdorff.h new file mode 100644 index 000000000..c377d4576 --- /dev/null +++ b/src/igl/copyleft/cgal/hausdorff.h @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_HAUSDORFF_H +#define IGL_COPYLEFT_CGAL_HAUSDORFF_H +#include "../../igl_inline.h" + +#include +#include "CGAL_includes.hpp" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Compute lower and upper bounds (l,u) on the Hausdorff distance between a triangle + // (V) and a pointset (e.g., mesh, triangle soup) given by a distance function + // handle (dist_to_B). + // + // Inputs: + // V 3 by 3 list of corner positions so that V.row(i) is the position of the + // ith corner + // treeB CGAL's AABB tree containing triangle soup (VB,FB) + // TB list of CGAL triangles in order of FB (for determining which was found + // in computation) + // Outputs: + // l lower bound on Hausdorff distance + // u upper bound on Hausdorff distance + // + template < + typename DerivedV, + typename Kernel, + typename Scalar> + IGL_INLINE void hausdorff( + const Eigen::MatrixBase& V, + const CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & treeB, + const std::vector > & TB, + Scalar & l, + Scalar & u); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "hausdorff.cpp" +#endif + +#endif + + diff --git a/src/igl/copyleft/cgal/incircle.cpp b/src/igl/copyleft/cgal/incircle.cpp new file mode 100644 index 000000000..d129d3b14 --- /dev/null +++ b/src/igl/copyleft/cgal/incircle.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "incircle.h" +#include +#include + +template +IGL_INLINE short igl::copyleft::cgal::incircle( + const Scalar pa[2], + const Scalar pb[2], + const Scalar pc[2], + const Scalar pd[2]) +{ + typedef CGAL::Exact_predicates_exact_constructions_kernel Epeck; + typedef CGAL::Exact_predicates_inexact_constructions_kernel Epick; + typedef typename std::conditional::value, + Epeck, Epick>::type Kernel; + + switch(CGAL::side_of_oriented_circle( + typename Kernel::Point_2(pa[0], pa[1]), + typename Kernel::Point_2(pb[0], pb[1]), + typename Kernel::Point_2(pc[0], pc[1]), + typename Kernel::Point_2(pd[0], pd[1]))) { + case CGAL::ON_POSITIVE_SIDE: + return 1; + case CGAL::ON_NEGATIVE_SIDE: + return -1; + case CGAL::ON_ORIENTED_BOUNDARY: + return 0; + default: + throw "Invalid incircle result"; + } +} diff --git a/src/igl/copyleft/cgal/incircle.h b/src/igl/copyleft/cgal/incircle.h new file mode 100644 index 000000000..4088dc9b5 --- /dev/null +++ b/src/igl/copyleft/cgal/incircle.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_INCIRCLE_H +#define IGL_COPYLEFT_CGAL_INCIRCLE_H + +#include "../../igl_inline.h" + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // pa,pb,pc,pd 2D points. + // Output: + // 1 if pd is inside of the oriented circle formed by pa,pb,pc. + // 0 if pd is co-circular with pa,pb,pc. + // -1 if pd is outside of the oriented circle formed by pa,pb,pc. + template + IGL_INLINE short incircle( + const Scalar pa[2], + const Scalar pb[2], + const Scalar pc[2], + const Scalar pd[2]); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "incircle.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/insert_into_cdt.cpp b/src/igl/copyleft/cgal/insert_into_cdt.cpp new file mode 100644 index 000000000..2ea4e6de1 --- /dev/null +++ b/src/igl/copyleft/cgal/insert_into_cdt.cpp @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "insert_into_cdt.h" +#include +#include +#include + +template +IGL_INLINE void igl::copyleft::cgal::insert_into_cdt( + const CGAL::Object & obj, + const CGAL::Plane_3 & P, + CGAL::Constrained_triangulation_plus_2< + CGAL::Constrained_Delaunay_triangulation_2< + Kernel, + CGAL::Triangulation_data_structure_2< + CGAL::Triangulation_vertex_base_2, + CGAL::Constrained_triangulation_face_base_2< Kernel> + >, + CGAL::Exact_intersections_tag + > + > + & cdt) +{ + typedef CGAL::Point_3 Point_3; + typedef CGAL::Segment_3 Segment_3; + typedef CGAL::Triangle_3 Triangle_3; + + if(const Segment_3 *iseg = CGAL::object_cast(&obj)) + { + // Add segment constraint + cdt.insert_constraint( P.to_2d(iseg->vertex(0)),P.to_2d(iseg->vertex(1))); + }else if(const Point_3 *ipoint = CGAL::object_cast(&obj)) + { + // Add point + cdt.insert(P.to_2d(*ipoint)); + } else if(const Triangle_3 *itri = CGAL::object_cast(&obj)) + { + // Add 3 segment constraints + cdt.insert_constraint( P.to_2d(itri->vertex(0)),P.to_2d(itri->vertex(1))); + cdt.insert_constraint( P.to_2d(itri->vertex(1)),P.to_2d(itri->vertex(2))); + cdt.insert_constraint( P.to_2d(itri->vertex(2)),P.to_2d(itri->vertex(0))); + } else if(const std::vector *polyp = + CGAL::object_cast< std::vector >(&obj)) + { + const std::vector & poly = *polyp; + const size_t m = poly.size(); + assert(m>=2); + for(size_t p = 0;p +template void igl::copyleft::cgal::insert_into_cdt(CGAL::Object const&, CGAL::Plane_3 const&, CGAL::Constrained_triangulation_plus_2 >, CGAL::Constrained_triangulation_face_base_2 > > >, CGAL::Exact_intersections_tag> >&); +template void igl::copyleft::cgal::insert_into_cdt(CGAL::Object const&, CGAL::Plane_3 const&, CGAL::Constrained_triangulation_plus_2 >, CGAL::Constrained_triangulation_face_base_2 > > >, CGAL::Exact_intersections_tag> >&); +#endif diff --git a/src/igl/copyleft/cgal/insert_into_cdt.h b/src/igl/copyleft/cgal/insert_into_cdt.h new file mode 100644 index 000000000..afca0233f --- /dev/null +++ b/src/igl/copyleft/cgal/insert_into_cdt.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_INSERT_INTO_CDT_H +#define IGL_COPYLEFT_CGAL_INSERT_INTO_CDT_H +#include "../../igl_inline.h" + +#include // Workaround https://github.com/CGAL/cgal/issues/2182 with CGAL 4.10-1 +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a current 2D constrained Delaunay triangulation (cdt), insert a + // 3D "object" (e.g., resulting from intersecting two triangles) into the + // cdt, by projecting it via the given plane (P) and adding appropriate + // constraints. + // + // Inputs: + // obj CGAL::Object representing a vertex, segment, or (convex) + // polygon. All vertices should lie on the plane P. If not, then this + // adds the _projection_ of this object to the cdt and that might not + // be what you wanted to do. + // P plane obj lies on and upon which the cdt is being performed + // cdt current CDT, see output + // Outputs: + // cdt CDT updated to contain constraints for the given object + // + template + IGL_INLINE void insert_into_cdt( + const CGAL::Object & obj, + const CGAL::Plane_3 & P, + CGAL::Constrained_triangulation_plus_2< + CGAL::Constrained_Delaunay_triangulation_2< + Kernel, + CGAL::Triangulation_data_structure_2< + CGAL::Triangulation_vertex_base_2, + CGAL::Constrained_triangulation_face_base_2< Kernel> + >, + CGAL::Exact_intersections_tag + > + > + & cdt); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "insert_into_cdt.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/insphere.cpp b/src/igl/copyleft/cgal/insphere.cpp new file mode 100644 index 000000000..db12fea33 --- /dev/null +++ b/src/igl/copyleft/cgal/insphere.cpp @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "insphere.h" +#include +#include + +template +IGL_INLINE short igl::copyleft::cgal::insphere( + const Scalar pa[3], + const Scalar pb[3], + const Scalar pc[3], + const Scalar pd[3], + const Scalar pe[3]) +{ + typedef CGAL::Exact_predicates_exact_constructions_kernel Epeck; + typedef CGAL::Exact_predicates_inexact_constructions_kernel Epick; + typedef typename std::conditional::value, + Epeck, Epick>::type Kernel; + + switch(CGAL::side_of_oriented_sphere( + typename Kernel::Point_3(pa[0], pa[1], pa[2]), + typename Kernel::Point_3(pb[0], pb[1], pb[2]), + typename Kernel::Point_3(pc[0], pc[1], pc[2]), + typename Kernel::Point_3(pd[0], pd[1], pd[2]), + typename Kernel::Point_3(pe[0], pe[1], pe[2]))) { + case CGAL::ON_POSITIVE_SIDE: + return 1; + case CGAL::ON_NEGATIVE_SIDE: + return -1; + case CGAL::ON_ORIENTED_BOUNDARY: + return 0; + default: + throw "Invalid incircle result"; + } +} diff --git a/src/igl/copyleft/cgal/insphere.h b/src/igl/copyleft/cgal/insphere.h new file mode 100644 index 000000000..88a926668 --- /dev/null +++ b/src/igl/copyleft/cgal/insphere.h @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_INSPHERE_H +#define IGL_COPYLEFT_CGAL_INSPHERE_H + +#include "../../igl_inline.h" + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // pa,pb,pc,pd,pe 3D points. + // Output: + // 1 if pe is inside of the oriented sphere formed by pa,pb,pc,pd. + // 0 if pe is co-spherical with pa,pb,pc,pd. + // -1 if pe is outside of the oriented sphere formed by pa,pb,pc,pd. + template + IGL_INLINE short insphere( + const Scalar pa[3], + const Scalar pb[3], + const Scalar pc[3], + const Scalar pd[3], + const Scalar pe[3]); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "insphere.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/intersect_other.cpp b/src/igl/copyleft/cgal/intersect_other.cpp new file mode 100644 index 000000000..9f984f956 --- /dev/null +++ b/src/igl/copyleft/cgal/intersect_other.cpp @@ -0,0 +1,289 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "intersect_other.h" +#include "CGAL_includes.hpp" +#include "mesh_to_cgal_triangle_list.h" +#include "remesh_intersections.h" +#include "../../slice_mask.h" +#include "../../remove_unreferenced.h" + +#ifndef IGL_FIRST_HIT_EXCEPTION +#define IGL_FIRST_HIT_EXCEPTION 10 +#endif + +// Un-exposed helper functions +namespace igl +{ + namespace copyleft + { + namespace cgal + { + template + static IGL_INLINE void push_result( + const Eigen::PlainObjectBase & F, + const int f, + const int f_other, + const CGAL::Object & result, + std::map< + typename DerivedF::Index, + std::vector > > & + offending) + //std::map< + // std::pair, + // std::vector > & edge2faces) + { + typedef typename DerivedF::Index Index; + typedef std::pair EMK; + if(offending.count(f) == 0) + { + // first time marking, initialize with new id and empty list + offending[f] = {}; + for(Index e = 0; e<3;e++) + { + // append face to edge's list + Index i = F(f,(e+1)%3) < F(f,(e+2)%3) ? F(f,(e+1)%3) : F(f,(e+2)%3); + Index j = F(f,(e+1)%3) < F(f,(e+2)%3) ? F(f,(e+2)%3) : F(f,(e+1)%3); + //edge2faces[EMK(i,j)].push_back(f); + } + } + offending[f].push_back({f_other,result}); + } + template < + typename Kernel, + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedIF, + typename DerivedVVAB, + typename DerivedFFAB, + typename DerivedJAB, + typename DerivedIMAB> + static IGL_INLINE bool intersect_other_helper( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & VVAB, + Eigen::PlainObjectBase & FFAB, + Eigen::PlainObjectBase & JAB, + Eigen::PlainObjectBase & IMAB) + { + + using namespace std; + using namespace Eigen; + + typedef typename DerivedFA::Index Index; + // 3D Primitives + typedef CGAL::Point_3 Point_3; + typedef CGAL::Segment_3 Segment_3; + typedef CGAL::Triangle_3 Triangle_3; + typedef CGAL::Plane_3 Plane_3; + typedef CGAL::Tetrahedron_3 Tetrahedron_3; + // 2D Primitives + typedef CGAL::Point_2 Point_2; + typedef CGAL::Segment_2 Segment_2; + typedef CGAL::Triangle_2 Triangle_2; + // 2D Constrained Delaunay Triangulation types + typedef CGAL::Triangulation_vertex_base_2 TVB_2; + typedef CGAL::Constrained_triangulation_face_base_2 CTAB_2; + typedef CGAL::Triangulation_data_structure_2 TDS_2; + typedef CGAL::Exact_intersections_tag Itag; + // Axis-align boxes for all-pairs self-intersection detection + typedef std::vector Triangles; + typedef typename Triangles::iterator TrianglesIterator; + typedef typename Triangles::const_iterator TrianglesConstIterator; + typedef + CGAL::Box_intersection_d::Box_with_handle_d + Box; + typedef + std::map > > + OffendingMap; + typedef std::map,std::vector > EdgeMap; + typedef std::pair EMK; + + Triangles TA,TB; + // Compute and process self intersections + mesh_to_cgal_triangle_list(VA,FA,TA); + mesh_to_cgal_triangle_list(VB,FB,TB); + // http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Box_intersection_d/Chapter_main.html#Section_63.5 + // Create the corresponding vector of bounding boxes + std::vector A_boxes,B_boxes; + const auto box_up = [](Triangles & T, std::vector & boxes) -> void + { + boxes.reserve(T.size()); + for ( + TrianglesIterator tit = T.begin(); + tit != T.end(); + ++tit) + { + boxes.push_back(Box(tit->bbox(), tit)); + } + }; + box_up(TA,A_boxes); + box_up(TB,B_boxes); + OffendingMap offendingA,offendingB; + //EdgeMap edge2facesA,edge2facesB; + + std::list lIF; + const auto cb = [&](const Box &a, const Box &b) -> void + { + using namespace std; + // index in F and T + int fa = a.handle()-TA.begin(); + int fb = b.handle()-TB.begin(); + const Triangle_3 & A = *a.handle(); + const Triangle_3 & B = *b.handle(); + if(CGAL::do_intersect(A,B)) + { + // There was an intersection + lIF.push_back(fa); + lIF.push_back(fb); + if(params.first_only) + { + throw IGL_FIRST_HIT_EXCEPTION; + } + if(!params.detect_only) + { + CGAL::Object result = CGAL::intersection(A,B); + + push_result(FA,fa,fb,result,offendingA); + push_result(FB,fb,fa,result,offendingB); + } + } + }; + try{ + CGAL::box_intersection_d( + A_boxes.begin(), A_boxes.end(), + B_boxes.begin(), B_boxes.end(), + cb); + }catch(int e) + { + // Rethrow if not FIRST_HIT_EXCEPTION + if(e != IGL_FIRST_HIT_EXCEPTION) + { + throw e; + } + // Otherwise just fall through + } + + // Convert lIF to Eigen matrix + assert(lIF.size()%2 == 0); + IF.resize(lIF.size()/2,2); + { + int i=0; + for( + list::const_iterator ifit = lIF.begin(); + ifit!=lIF.end(); + ) + { + IF(i,0) = (*ifit); + ifit++; + IF(i,1) = (*ifit); + ifit++; + i++; + } + } + if(!params.detect_only) + { + // Obsolete, now remesh_intersections expects a single mesh + // remesh_intersections(VA,FA,TA,offendingA,VVA,FFA,JA,IMA); + // remesh_intersections(VB,FB,TB,offendingB,VVB,FFB,JB,IMB); + // Combine mesh and offending maps + DerivedVA VAB(VA.rows()+VB.rows(),3); + VAB< 0; + } + } + } +} + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedIF, + typename DerivedVVAB, + typename DerivedFFAB, + typename DerivedJAB, + typename DerivedIMAB> +IGL_INLINE bool igl::copyleft::cgal::intersect_other( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & VVAB, + Eigen::PlainObjectBase & FFAB, + Eigen::PlainObjectBase & JAB, + Eigen::PlainObjectBase & IMAB) +{ + if(params.detect_only) + { + return intersect_other_helper + (VA,FA,VB,FB,params,IF,VVAB,FFAB,JAB,IMAB); + }else + { + return intersect_other_helper + (VA,FA,VB,FB,params,IF,VVAB,FFAB,JAB,IMAB); + } +} + +IGL_INLINE bool igl::copyleft::cgal::intersect_other( + const Eigen::MatrixXd & VA, + const Eigen::MatrixXi & FA, + const Eigen::MatrixXd & VB, + const Eigen::MatrixXi & FB, + const bool first_only, + Eigen::MatrixXi & IF) +{ + Eigen::MatrixXd VVAB; + Eigen::MatrixXi FFAB; + Eigen::VectorXi JAB,IMAB; + return intersect_other( + VA,FA,VB,FB,{true,first_only},IF,VVAB,FFAB,JAB,IMAB); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::intersect_other, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::intersect_other, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/intersect_other.h b/src/igl/copyleft/cgal/intersect_other.h new file mode 100644 index 000000000..c1afdf87d --- /dev/null +++ b/src/igl/copyleft/cgal/intersect_other.h @@ -0,0 +1,91 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_INTERSECT_OTHER_H +#define IGL_COPYLEFT_CGAL_INTERSECT_OTHER_H +#include "../../igl_inline.h" +#include "RemeshSelfIntersectionsParam.h" + +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // INTERSECT_OTHER Given a triangle mesh (VA,FA) and another mesh (VB,FB) + // find all pairs of intersecting faces. Note that self-intersections are + // ignored. + // + // Inputs: + // VA #V by 3 list of vertex positions + // FA #F by 3 list of triangle indices into VA + // VB #V by 3 list of vertex positions + // FB #F by 3 list of triangle indices into VB + // params whether to detect only and then whether to only find first + // intersection + // Outputs: + // IF #intersecting face pairs by 2 list of intersecting face pairs, + // indexing FA and FB + // VVAB #VVAB by 3 list of vertex positions + // FFAB #FFAB by 3 list of triangle indices into VVA + // JAB #FFAB list of indices into [FA;FB] denoting birth triangle + // IMAB #VVAB list of indices stitching duplicates (resulting from + // mesh intersections) together + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedIF, + typename DerivedVVAB, + typename DerivedFFAB, + typename DerivedJAB, + typename DerivedIMAB> + IGL_INLINE bool intersect_other( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & VVAB, + Eigen::PlainObjectBase & FFAB, + Eigen::PlainObjectBase & JAB, + Eigen::PlainObjectBase & IMAB); + // Legacy wrapper for detect only using common types. + // + // Inputs: + // VA #V by 3 list of vertex positions + // FA #F by 3 list of triangle indices into VA + // VB #V by 3 list of vertex positions + // FB #F by 3 list of triangle indices into VB + // first_only whether to only detect the first intersection. + // Outputs: + // IF #intersecting face pairs by 2 list of intersecting face pairs, + // indexing FA and FB + // Returns true if any intersections were found + // + // See also: remesh_self_intersections + IGL_INLINE bool intersect_other( + const Eigen::MatrixXd & VA, + const Eigen::MatrixXi & FA, + const Eigen::MatrixXd & VB, + const Eigen::MatrixXi & FB, + const bool first_only, + Eigen::MatrixXi & IF); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "intersect_other.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.cpp b/src/igl/copyleft/cgal/intersect_with_half_space.cpp new file mode 100644 index 000000000..c4ba4dbb8 --- /dev/null +++ b/src/igl/copyleft/cgal/intersect_with_half_space.cpp @@ -0,0 +1,88 @@ +#include "intersect_with_half_space.h" +#include "mesh_boolean.h" +#include "half_space_box.h" + +template < + typename DerivedV, + typename DerivedF, + typename Derivedp, + typename Derivedn, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & n, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + typedef CGAL::Plane_3 Plane; + typedef CGAL::Point_3 Point; + typedef CGAL::Vector_3 Vector; + Plane P(Point(p(0),p(1),p(2)),Vector(n(0),n(1),n(2))); + return intersect_with_half_space(V,F,P,VC,FC,J); +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedequ, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & equ, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + typedef CGAL::Plane_3 Plane; + Plane P(equ(0),equ(1),equ(2),equ(3)); + return intersect_with_half_space(V,F,P,VC,FC,J); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const CGAL::Plane_3 & P, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + Eigen::Matrix BV; + Eigen::Matrix BF; + half_space_box(P,V,BV,BF); + // Disturbingly, (BV,BF) must be first argument + const bool ret = mesh_boolean(BV,BF,V,F,MESH_BOOLEAN_TYPE_INTERSECT,VC,FC,J); + // But now J is wrong... + std::for_each( + J.data(), + J.data()+J.size(), + [&BF,&F](typename DerivedJ::Scalar & j) + {j = (j, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::intersect_with_half_space, Eigen::Matrix, Eigen::Matrix, Eigen::CwiseUnaryOp, Eigen::Matrix const>, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, Eigen::Matrix const> > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::intersect_with_half_space, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::intersect_with_half_space, Eigen::Matrix, Eigen::Matrix, Eigen::CwiseUnaryOp, Eigen::Matrix const>, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, Eigen::Matrix const> > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.h b/src/igl/copyleft/cgal/intersect_with_half_space.h new file mode 100644 index 000000000..9fdf9ae2b --- /dev/null +++ b/src/igl/copyleft/cgal/intersect_with_half_space.h @@ -0,0 +1,96 @@ +#ifndef IGL_COPYLEFT_CGAL_INTERSECT_WITH_HALF_SPACE_H +#define IGL_COPYLEFT_CGAL_INTERSECT_WITH_HALF_SPACE_H +#include "../../igl_inline.h" +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Intersect a PWN mesh with a half-space. Point on plane, normal pointing + // outward. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // p 3d point on plane + // n 3d vector of normal of plane pointing away from inside + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [F;F.rows()+[1;2]] revealing "birth" + // facet + template < + typename DerivedV, + typename DerivedF, + typename Derivedp, + typename Derivedn, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & n, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // Intersect a PWN mesh with a half-space. Plane equation. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // equ plane equation: P(x,y,z) = a*x+b*y+c*z + d = 0, P(x,y,z) < 0 is + // _inside_. + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [F;F.rows()+[1;2]] revealing "birth" facet + template < + typename DerivedV, + typename DerivedF, + typename Derivedequ, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & equ, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // Intersect a PWN mesh with a half-space. CGAL Plane. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // P plane + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [F;F.rows()+[1;2]] revealing "birth" facet + template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool intersect_with_half_space( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const CGAL::Plane_3 & P, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "intersect_with_half_space.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.cpp b/src/igl/copyleft/cgal/lexicographic_triangulation.cpp new file mode 100644 index 000000000..719b50dbf --- /dev/null +++ b/src/igl/copyleft/cgal/lexicographic_triangulation.cpp @@ -0,0 +1,24 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "lexicographic_triangulation.h" +#include "../../lexicographic_triangulation.h" +#include "orient2D.h" + +template< + typename DerivedP, + typename DerivedF + > +IGL_INLINE void igl::copyleft::cgal::lexicographic_triangulation( + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& F) +{ + typedef typename DerivedP::Scalar Scalar; + igl::lexicographic_triangulation(P, orient2D, F); +} diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.h b/src/igl/copyleft/cgal/lexicographic_triangulation.h new file mode 100644 index 000000000..5773a3942 --- /dev/null +++ b/src/igl/copyleft/cgal/lexicographic_triangulation.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_LEXICOGRAPHIC_TRIANGULATION_H +#define IGL_COPYLEFT_CGAL_LEXICOGRAPHIC_TRIANGULATION_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + + // Given a set of points in 2D, return a lexicographic triangulation of these + // points. + // + // Inputs: + // P #P by 2 list of vertex positions + // + // Outputs: + // F #F by 3 of faces in lexicographic triangulation. + template< + typename DerivedP, + typename DerivedF + > + IGL_INLINE void lexicographic_triangulation( + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& F); + } + } +} + + + + +#ifndef IGL_STATIC_LIBRARY +# include "lexicographic_triangulation.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/list_to_matrix.cpp b/src/igl/copyleft/cgal/list_to_matrix.cpp new file mode 100644 index 000000000..e94dde6b8 --- /dev/null +++ b/src/igl/copyleft/cgal/list_to_matrix.cpp @@ -0,0 +1,15 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../list_to_matrix.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../list_to_matrix.cpp" +template bool igl::list_to_matrix, Eigen::Matrix, -1, 3, 0, -1, 3> >(std::vector, std::allocator > >, std::allocator, std::allocator > > > > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +#endif diff --git a/src/igl/copyleft/cgal/mesh_boolean.cpp b/src/igl/copyleft/cgal/mesh_boolean.cpp new file mode 100644 index 000000000..5d289f6c3 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_boolean.cpp @@ -0,0 +1,466 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "mesh_boolean.h" +#include "assign.h" +#include "extract_cells.h" +#include "mesh_boolean_type_to_funcs.h" +#include "propagate_winding_numbers.h" +#include "relabel_small_immersed_cells.h" +#include "remesh_self_intersections.h" +#include "string_to_mesh_boolean_type.h" +#include "../../combine.h" +#include "../../cumsum.h" +#include "../../extract_manifold_patches.h" +#include "../../get_seconds.h" +#include "../../remove_unreferenced.h" +#include "../../resolve_duplicated_faces.h" +#include "../../slice.h" +#include "../../unique_edge_map.h" +#include "../../unique_simplices.h" + +#include +#include + +//#define MESH_BOOLEAN_TIMING +//#define DOUBLE_CHECK_EXACT_OUTPUT +//#define SMALL_CELL_REMOVAL + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + std::function keep; + std::function) > wind_num_op; + mesh_boolean_type_to_funcs(type,wind_num_op,keep); + return mesh_boolean(VA,FA,VB,FB,wind_num_op,keep,VC,FC,J); +} +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const std::string & type_str, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + return mesh_boolean( + VA,FA,VB,FB,string_to_mesh_boolean_type(type_str),VC,FC,J); +} + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + // Generate combined mesh (VA,FA,VB,FB) -> (V,F) + Eigen::Matrix sizes(FA.rows(),FB.rows()); + // TODO: This is a precision template **bug** that results in failure to + // compile. If DerivedVA::Scalar is double and DerivedVB::Scalar is + // CGAL::Epeck::FT then the following assignment will not compile. This + // implies that VA must have the trumping precision (and a valid assignment + // operator from VB's type). + Eigen::Matrix VV(VA.rows() + VB.rows(), 3); + DerivedFC FF(FA.rows() + FB.rows(), 3); + // Can't use comma initializer + for(int a = 0;a 0) + { + FF.block(FA.rows(), 0, FB.rows(), 3) = FB.array() + VA.rows(); + } + return mesh_boolean(VV,FF,sizes,wind_num_op,keep,VC,FC,J); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const std::vector & Vlist, + const std::vector & Flist, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + DerivedV VV; + DerivedF FF; + Eigen::Matrix Vsizes,Fsizes; + igl::combine(Vlist,Flist,VV,FF,Vsizes,Fsizes); + return mesh_boolean(VV,FF,Fsizes,wind_num_op,keep,VC,FC,J); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const std::vector & Vlist, + const std::vector & Flist, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ + DerivedV VV; + DerivedF FF; + Eigen::Matrix Vsizes,Fsizes; + igl::combine(Vlist,Flist,VV,FF,Vsizes,Fsizes); + std::function keep; + std::function) > wind_num_op; + mesh_boolean_type_to_funcs(type,wind_num_op,keep); + return mesh_boolean(VV,FF,Fsizes,wind_num_op,keep,VC,FC,J); +} + +template < + typename DerivedVV, + typename DerivedFF, + typename Derivedsizes, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const Eigen::MatrixBase & VV, + const Eigen::MatrixBase & FF, + const Eigen::MatrixBase & sizes, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J) +{ +#ifdef MESH_BOOLEAN_TIMING + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void { + std::cout << "mesh_boolean." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#endif + typedef typename DerivedVC::Scalar Scalar; + typedef CGAL::Epeck Kernel; + typedef Kernel::FT ExactScalar; + typedef Eigen::Matrix MatrixX3S; + typedef Eigen::Matrix VectorXJ; + typedef Eigen::Matrix< + ExactScalar, + Eigen::Dynamic, + Eigen::Dynamic, + DerivedVC::IsRowMajor> MatrixXES; + MatrixXES V; + DerivedFC F; + VectorXJ CJ; + { + Eigen::VectorXi I; + igl::copyleft::cgal::RemeshSelfIntersectionsParam params; + params.stitch_all = true; + MatrixXES Vr; + DerivedFC Fr; + Eigen::MatrixXi IF; + igl::copyleft::cgal::remesh_self_intersections( + VV, FF, params, Vr, Fr, IF, CJ, I); + assert(I.size() == Vr.rows()); + // Merge coinciding vertices into non-manifold vertices. + std::for_each(Fr.data(), Fr.data()+Fr.size(), + [&I](typename DerivedFC::Scalar& a) { a=I[a]; }); + // Remove unreferenced vertices. + Eigen::VectorXi UIM; + igl::remove_unreferenced(Vr, Fr, V, F, UIM); + } +#ifdef MESH_BOOLEAN_TIMING + log_time("resolve_self_intersection"); +#endif + + // Compute edges of (F) --> (E,uE,EMAP,uE2E) + Eigen::MatrixXi E, uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + + // Compute patches (F,EMAP,uE2E) --> (P) + Eigen::VectorXi P; + const size_t num_patches = igl::extract_manifold_patches(F, EMAP, uE2E, P); +#ifdef MESH_BOOLEAN_TIMING + log_time("patch_extraction"); +#endif + + // Compute cells (V,F,P,E,uE,EMAP) -> (per_patch_cells) + Eigen::MatrixXi per_patch_cells; + const size_t num_cells = + igl::copyleft::cgal::extract_cells( + V, F, P, E, uE, uE2E, EMAP, per_patch_cells); +#ifdef MESH_BOOLEAN_TIMING + log_time("cell_extraction"); +#endif + + // Compute winding numbers on each side of each facet. + const size_t num_faces = F.rows(); + // W(f,:) --> [w1out,w1in,w2out,w2in, ... wnout,wnint] winding numbers above + // and below each face w.r.t. each input mesh, so that W(f,2*i) is the + // winding number above face f w.r.t. input i, and W(f,2*i+1) is the winding + // number below face f w.r.t. input i. + Eigen::MatrixXi W; + // labels(f) = i means that face f comes from mesh i + Eigen::VectorXi labels(num_faces); + // cumulative sizes + Derivedsizes cumsizes; + igl::cumsum(sizes,1,cumsizes); + const size_t num_inputs = sizes.size(); + std::transform( + CJ.data(), + CJ.data()+CJ.size(), + labels.data(), + // Determine which input mesh birth face i comes from + [&num_inputs,&cumsizes](int i)->int + { + for(int k = 0;k 0) + { + valid = valid & + igl::copyleft::cgal::propagate_winding_numbers( + V, F, uE, uE2E, num_patches, P, num_cells, per_patch_cells, labels, W); + } else + { + W.resize(0, 2*num_inputs); + } + assert((size_t)W.rows() == num_faces); + // If W doesn't have enough columns, pad with zeros + if (W.cols() <= 2*num_inputs) + { + const int old_ncols = W.cols(); + W.conservativeResize(num_faces,2*num_inputs); + W.rightCols(2*num_inputs-old_ncols).setConstant(0); + } + assert((size_t)W.cols() == 2*num_inputs); +#ifdef MESH_BOOLEAN_TIMING + log_time("propagate_input_winding_number"); +#endif + + // Compute resulting winding number. + Eigen::MatrixXi Wr(num_faces, 2); + for (size_t i=0; i int + { + return (i+1)*(ori?1:-1); + }; + //auto signed_index_to_index = [&](int i) -> size_t { + // return abs(i) - 1; + //}; + std::vector selected; + for(size_t i=0; i 0) + { + selected.push_back(index_to_signed_index(i, true)); + } else if (should_keep < 0) + { + selected.push_back(index_to_signed_index(i, false)); + } + } + + const size_t num_selected = selected.size(); + DerivedFC kept_faces(num_selected, 3); + DerivedJ kept_face_indices(num_selected, 1); + for (size_t i=0; i 0) + { + kept_faces.row(i) = F.row(idx); + } else + { + kept_faces.row(i) = F.row(idx).reverse(); + } + kept_face_indices(i, 0) = CJ[idx]; + } +#ifdef MESH_BOOLEAN_TIMING + log_time("extract_output"); +#endif + + // Finally, remove duplicated faces and unreferenced vertices. + { + DerivedFC G; + DerivedJ JJ; + igl::resolve_duplicated_faces(kept_faces, G, JJ); + igl::slice(kept_face_indices, JJ, 1, J); + +#ifdef DOUBLE_CHECK_EXACT_OUTPUT + { + // Sanity check on exact output. + igl::copyleft::cgal::RemeshSelfIntersectionsParam params; + params.detect_only = true; + params.first_only = true; + MatrixXES dummy_VV; + DerivedFC dummy_FF, dummy_IF; + Eigen::VectorXi dummy_J, dummy_IM; + igl::copyleft::cgal::SelfIntersectMesh< + Kernel, + MatrixXES, DerivedFC, + MatrixXES, DerivedFC, + DerivedFC, + Eigen::VectorXi, + Eigen::VectorXi + > checker(V, G, params, + dummy_VV, dummy_FF, dummy_IF, dummy_J, dummy_IM); + if (checker.count != 0) + { + throw "Self-intersection not fully resolved."; + } + } +#endif + + MatrixX3S Vs; + assign(V,Vs); + Eigen::VectorXi newIM; + igl::remove_unreferenced(Vs,G,VC,FC,newIM); + } +#ifdef MESH_BOOLEAN_TIMING + log_time("clean_up"); +#endif + return valid; +} + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC> +IGL_INLINE bool igl::copyleft::cgal::mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC) +{ + Eigen::Matrix J; + return igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,type,VC,FC,J); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::mesh_boolean, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, -1, 4, 0, -1, 4>, Eigen::Matrix, Eigen::Matrix, -1, 4, 0, -1, 4>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, 4, 0, -1, 4> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, 4, 0, -1, 4> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::vector, std::allocator > > const&, std::vector, std::allocator > > const&, std::function)> const&, std::function const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::vector, std::allocator > > const&, std::vector, std::allocator > > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template bool igl::copyleft::cgal::mesh_boolean, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::MatrixBase, -1, 3, 0, -1, 3>> const &, class Eigen::MatrixBase> const &, class Eigen::MatrixBase, -1, 3, 0, -1, 3>> const &, class Eigen::MatrixBase> const &, enum igl::MeshBooleanType const &, class Eigen::PlainObjectBase, -1, 3, 0, -1, 3>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/mesh_boolean.h b/src/igl/copyleft/cgal/mesh_boolean.h new file mode 100644 index 000000000..24c99ed41 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_boolean.h @@ -0,0 +1,229 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_MESH_BOOLEAN_H +#define IGL_COPYLEFT_CGAL_MESH_BOOLEAN_H + +#include "../../igl_inline.h" +#include "../../MeshBooleanType.h" +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // MESH_BOOLEAN Compute boolean csg operations on "solid", consistently + // oriented meshes. + // + // Inputs: + // VA #VA by 3 list of vertex positions of first mesh + // FA #FA by 3 list of triangle indices into VA + // VB #VB by 3 list of vertex positions of second mesh + // FB #FB by 3 list of triangle indices into VB + // type type of boolean operation + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [FA;FA.rows()+FB] revealing "birth" facet + // Returns true if inputs induce a piecewise constant winding number + // field and type is valid + // + // See also: mesh_boolean_cork, intersect_other, + // remesh_self_intersections + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const std::string & type_str, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // + // Inputs: + // VA #VA by 3 list of vertex positions of first mesh + // FA #FA by 3 list of triangle indices into VA + // VB #VB by 3 list of vertex positions of second mesh + // FB #FB by 3 list of triangle indices into VB + // wind_num_op function handle for filtering winding numbers from + // tuples of integer values to [0,1] outside/inside values + // keep function handle for determining if a patch should be "kept" + // in the output based on the winding number on either side + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [FA;FB] revealing "birth" facet + // Returns true iff inputs induce a piecewise constant winding number + // field + // + // See also: mesh_boolean_cork, intersect_other, + // remesh_self_intersections + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // MESH_BOOLEAN Variadic boolean operations + // + // Inputs: + // Vlist k-long list of lists of mesh vertex positions + // Flist k-long list of lists of mesh face indices, so that Flist[i] indexes + // vertices in Vlist[i] + // wind_num_op function handle for filtering winding numbers from + // n-tuples of integer values to [0,1] outside/inside values + // keep function handle for determining if a patch should be "kept" + // in the output based on the winding number on either side + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of indices into [Flist[0];Flist[1];...;Flist[k]] + // revealing "birth" facet + // Returns true iff inputs induce a piecewise constant winding number + // field + // + // See also: mesh_boolean_cork, intersect_other, + // remesh_self_intersections + template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const std::vector & Vlist, + const std::vector & Flist, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + template < + typename DerivedV, + typename DerivedF, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const std::vector & Vlist, + const std::vector & Flist, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // Given a merged mesh (V,F) and list of sizes of inputs + // + // Inputs: + // V #V by 3 list of merged mesh vertex positions + // F #F by 3 list of merged mesh face indices so that first sizes(0) + // faces come from the first input, and the next sizes(1) faces come + // from the second input, and so on. + // sizes #inputs list of sizes so that sizes(i) is the #faces in the + // ith input + // wind_num_op function handle for filtering winding numbers from + // tuples of integer values to [0,1] outside/inside values + // keep function handle for determining if a patch should be "kept" + // in the output based on the winding number on either side + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // J #FC list of birth parent indices + // + template < + typename DerivedVV, + typename DerivedFF, + typename Derivedsizes, + typename DerivedVC, + typename DerivedFC, + typename DerivedJ> + IGL_INLINE bool mesh_boolean( + const Eigen::MatrixBase & VV, + const Eigen::MatrixBase & FF, + const Eigen::MatrixBase & sizes, + const std::function) >& wind_num_op, + const std::function & keep, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC, + Eigen::PlainObjectBase & J); + // Inputs: + // VA #VA by 3 list of vertex positions of first mesh + // FA #FA by 3 list of triangle indices into VA + // VB #VB by 3 list of vertex positions of second mesh + // FB #FB by 3 list of triangle indices into VB + // type type of boolean operation + // Outputs: + // VC #VC by 3 list of vertex positions of boolean result mesh + // FC #FC by 3 list of triangle indices into VC + // Returns true ff inputs induce a piecewise constant winding number + // field and type is valid + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC> + IGL_INLINE bool mesh_boolean( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "mesh_boolean.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp b/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp new file mode 100644 index 000000000..41b67c1b7 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp @@ -0,0 +1,39 @@ +#include "mesh_boolean_type_to_funcs.h" +#include "BinaryWindingNumberOperations.h" + +IGL_INLINE void igl::copyleft::cgal::mesh_boolean_type_to_funcs( + const MeshBooleanType & type, + std::function) >& wind_num_op, + std::function & keep) +{ + switch (type) + { + case MESH_BOOLEAN_TYPE_UNION: + wind_num_op = BinaryUnion(); + keep = KeepInside(); + return; + case MESH_BOOLEAN_TYPE_INTERSECT: + wind_num_op = BinaryIntersect(); + keep = KeepInside(); + return; + case MESH_BOOLEAN_TYPE_MINUS: + wind_num_op = BinaryMinus(); + keep = KeepInside(); + return; + case MESH_BOOLEAN_TYPE_XOR: + wind_num_op = BinaryXor(); + keep = KeepInside(); + return; + case MESH_BOOLEAN_TYPE_RESOLVE: + wind_num_op = BinaryResolve(); + keep = KeepAll(); + return; + default: + assert(false && "Unsupported boolean type."); + return; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h b/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h new file mode 100644 index 000000000..2a269d770 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h @@ -0,0 +1,37 @@ +#ifndef IGL_COPYLEFT_CGAL_MESH_BOOLEAN_TYPE_TO_FUNCS_H +#define IGL_COPYLEFT_CGAL_MESH_BOOLEAN_TYPE_TO_FUNCS_H + +#include "../../igl_inline.h" +#include "../../MeshBooleanType.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Convert a MeshBooleanType enum to a pair of winding number conversion + // function and "keep" function used by mesh_boolean + // + // Inputs: + // type MeshBooleanType enum value + // Outputs: + // wind_num_op function handle for filtering winding numbers from + // tuples of integer values to [0,1] outside/inside values + // keep function handle for determining if a patch should be "kept" + // in the output based on the winding number on either side + // + // See also: string_to_mesh_boolean_type + IGL_INLINE void mesh_boolean_type_to_funcs( + const MeshBooleanType & type, + std::function) >& + wind_num_op, + std::function & keep); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "mesh_boolean_type_to_funcs.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp b/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp new file mode 100644 index 000000000..7f3af539d --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp @@ -0,0 +1,76 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mesh_to_cgal_triangle_list.h" +#include "assign.h" + +#include + +template < + typename DerivedV, + typename DerivedF, + typename Kernel> +IGL_INLINE void igl::copyleft::cgal::mesh_to_cgal_triangle_list( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + std::vector > & T) +{ + typedef CGAL::Point_3 Point_3; + typedef CGAL::Triangle_3 Triangle_3; + // Must be 3D + assert(V.cols() == 3); + // **Copy** to convert to output type (this is especially/only needed if the + // input type DerivedV::Scalar is CGAL::Epeck + Eigen::Matrix< + typename Kernel::FT, + DerivedV::RowsAtCompileTime, + DerivedV::ColsAtCompileTime> + KV(V.rows(),V.cols()); + assign(V,KV); + // Must be triangles + assert(F.cols() == 3); + T.reserve(F.rows()); + // Loop over faces + for(int f = 0;f<(int)F.rows();f++) + { + T.push_back( + Triangle_3( + Point_3( KV(F(f,0),0), KV(F(f,0),1), KV(F(f,0),2)), + Point_3( KV(F(f,1),0), KV(F(f,1),1), KV(F(f,1),2)), + Point_3( KV(F(f,2),0), KV(F(f,2),1), KV(F(f,2),2)))); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, -1, 1, -1, -1>, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, -1, 1, -1, -1>, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, 8, 3, 0, 8, 3>, Eigen::Matrix, CGAL::Epick>(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +template void igl::copyleft::cgal::mesh_to_cgal_triangle_list, 8, 3, 0, 8, 3>, Eigen::Matrix, CGAL::Epeck>(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > >&); +#endif diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h b/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h new file mode 100644 index 000000000..492215bd5 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_MESH_TO_CGAL_TRIANGLE_LIST_H +#define IGL_COPYLEFT_CGAL_MESH_TO_CGAL_TRIANGLE_LIST_H +#include "../../igl_inline.h" +#include +#include "CGAL_includes.hpp" +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Convert a mesh (V,F) to a list of CGAL triangles + // + // Templates: + // Kernal CGAL computation and construction kernel (e.g. + // CGAL::Exact_predicates_exact_constructions_kernel) + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // Outputs: + // T #F list of CGAL triangles + template < + typename DerivedV, + typename DerivedF, + typename Kernel> + IGL_INLINE void mesh_to_cgal_triangle_list( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + std::vector > & T); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "mesh_to_cgal_triangle_list.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.cpp b/src/igl/copyleft/cgal/mesh_to_polyhedron.cpp new file mode 100644 index 000000000..b0bfe7079 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_to_polyhedron.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mesh_to_polyhedron.h" +#include +#include + + +template +IGL_INLINE bool igl::copyleft::cgal::mesh_to_polyhedron( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + Polyhedron & poly) +{ + typedef typename Polyhedron::HalfedgeDS HalfedgeDS; + // Postcondition: hds is a valid polyhedral surface. + CGAL::Polyhedron_incremental_builder_3 B(poly.hds()); + B.begin_surface(V.rows(),F.rows()); + typedef typename HalfedgeDS::Vertex Vertex; + typedef typename Vertex::Point Point; + assert(V.cols() == 3 && "V must be #V by 3"); + for(int v = 0;v +#include +template bool igl::copyleft::cgal::mesh_to_polyhedron, CGAL::Polyhedron_items_with_id_3, CGAL::HalfedgeDS_default, std::allocator > >(Eigen::Matrix const&, Eigen::Matrix const&, CGAL::Polyhedron_3, CGAL::Polyhedron_items_with_id_3, CGAL::HalfedgeDS_default, std::allocator >&); +#endif diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.h b/src/igl/copyleft/cgal/mesh_to_polyhedron.h new file mode 100644 index 000000000..de17d8aa2 --- /dev/null +++ b/src/igl/copyleft/cgal/mesh_to_polyhedron.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_MESH_TO_POLYHEDRON_H +#define IGL_COPYLEFT_CGAL_MESH_TO_POLYHEDRON_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Convert a mesh (V,F) to a CGAL Polyhedron + // + // Templates: + // Polyhedron CGAL Polyhedron type (e.g. Polyhedron_3) + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // Outputs: + // poly cgal polyhedron + // Returns true only if (V,F) can be converted to a valid polyhedron (i.e. if + // (V,F) is vertex and edge manifold). + template + IGL_INLINE bool mesh_to_polyhedron( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + Polyhedron & poly); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "mesh_to_polyhedron.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/minkowski_sum.cpp b/src/igl/copyleft/cgal/minkowski_sum.cpp new file mode 100644 index 000000000..c9045a4c7 --- /dev/null +++ b/src/igl/copyleft/cgal/minkowski_sum.cpp @@ -0,0 +1,395 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "minkowski_sum.h" +#include "mesh_boolean.h" + +#include "../../slice.h" +#include "../../slice_mask.h" +#include "../../LinSpaced.h" +#include "../../unique_rows.h" +#include "../../get_seconds.h" +#include "../../edges.h" +#include +#include +#include +#include + + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedW, + typename DerivedG, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const bool resolve_overlaps, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J) +{ + using namespace std; + using namespace Eigen; + assert(FA.cols() == 3 && "FA must contain a closed triangle mesh"); + assert(FB.cols() <= FA.cols() && + "FB must contain lower diemnsional simplices than FA"); + const auto tictoc = []()->double + { + static double t_start; + double now = igl::get_seconds(); + double interval = now-t_start; + t_start = now; + return interval; + }; + tictoc(); + Matrix EB; + edges(FB,EB); + Matrix EA(0,2); + if(FB.cols() == 3) + { + edges(FA,EA); + } + // number of copies of A along edges of B + const int n_ab = EB.rows(); + // number of copies of B along edges of A + const int n_ba = EA.rows(); + + vector vW(n_ab + n_ba); + vector vG(n_ab + n_ba); + vector vJ(n_ab + n_ba); + vector offsets(n_ab + n_ba + 1); + offsets[0] = 0; + // sweep A along edges of B + for(int e = 0;e eJ; + minkowski_sum( + VA, + FA, + VB.row(EB(e,0)).eval(), + VB.row(EB(e,1)).eval(), + false, + vW[e], + vG[e], + eJ); + assert(vG[e].rows() == eJ.rows()); + assert(eJ.cols() == 1); + vJ[e].resize(vG[e].rows(),2); + vJ[e].col(0) = eJ; + vJ[e].col(1).setConstant(e); + offsets[e+1] = offsets[e] + vW[e].rows(); + } + // sweep B along edges of A + for(int e = 0;e eJ; + const int ee = n_ab+e; + minkowski_sum( + VB, + FB, + VA.row(EA(e,0)).eval(), + VA.row(EA(e,1)).eval(), + false, + vW[ee], + vG[ee], + eJ); + vJ[ee].resize(vG[ee].rows(),2); + vJ[ee].col(0) = eJ.array() + (FA.rows()+1); + vJ[ee].col(1).setConstant(ee); + offsets[ee+1] = offsets[ee] + vW[ee].rows(); + } + // Combine meshes + int n=0,m=0; + for_each(vW.begin(),vW.end(),[&n](const DerivedW & w){n+=w.rows();}); + for_each(vG.begin(),vG.end(),[&m](const DerivedG & g){m+=g.rows();}); + assert(n == offsets.back()); + + W.resize(n,3); + G.resize(m,3); + J.resize(m,2); + { + int m_off = 0,n_off = 0; + for(int i = 0;i SJ; + mesh_boolean( + DerivedW(W), + DerivedG(G), + Matrix(), + Matrix(), + MESH_BOOLEAN_TYPE_UNION, + W, + G, + SJ); + slice(DerivedJ(J),SJ,1,J); + } +} + +template < + typename DerivedVA, + typename DerivedFA, + typename sType, int sCols, int sOptions, + typename dType, int dCols, int dOptions, + typename DerivedW, + typename DerivedG, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::Matrix & s, + const Eigen::Matrix & d, + const bool resolve_overlaps, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J) +{ + using namespace Eigen; + using namespace std; + assert(s.cols() == 3 && "s should be a 3d point"); + assert(d.cols() == 3 && "d should be a 3d point"); + // silly base case + if(FA.size() == 0) + { + W.resize(0,3); + G.resize(0,3); + return; + } + const int dim = VA.cols(); + assert(dim == 3 && "dim must be 3D"); + assert(s.size() == 3 && "s must be 3D point"); + assert(d.size() == 3 && "d must be 3D point"); + // segment vector + const CGAL::Vector_3 v(d(0)-s(0),d(1)-s(1),d(2)-s(2)); + // number of vertices + const int n = VA.rows(); + // duplicate vertices at s and d, we'll remove unreferernced later + W.resize(2*n,dim); + for(int i = 0;i P(m,1),N(m,1); + // loop over faces + int mp = 0,mn = 0; + for(int f = 0;f plane( + CGAL::Point_3(VA(FA(f,0),0),VA(FA(f,0),1),VA(FA(f,0),2)), + CGAL::Point_3(VA(FA(f,1),0),VA(FA(f,1),1),VA(FA(f,1),2)), + CGAL::Point_3(VA(FA(f,2),0),VA(FA(f,2),1),VA(FA(f,2),2))); + const auto normal = plane.orthogonal_vector(); + const auto dt = normal * v; + if(dt > 0) + { + P(f) = true; + N(f) = false; + mp++; + }else + //}else if(dt < 0) + { + P(f) = false; + N(f) = true; + mn++; + //}else + //{ + // P(f) = false; + // N(f) = false; + } + } + + typedef Matrix MatrixXI; + typedef Matrix VectorXI; + MatrixXI GT(mp+mn,3); + GT<< slice_mask(FA,N,1), slice_mask((FA.array()+n).eval(),P,1); + // J indexes FA for parts at s and m+FA for parts at d + J.derived() = igl::LinSpaced(m,0,m-1); + DerivedJ JT(mp+mn); + JT << slice_mask(J,P,1), slice_mask(J,N,1); + JT.block(mp,0,mn,1).array()+=m; + + // Original non-co-planar faces with positively oriented reversed + MatrixXI BA(mp+mn,3); + BA << slice_mask(FA,P,1).rowwise().reverse(), slice_mask(FA,N,1); + // Quads along **all** sides + MatrixXI GQ((mp+mn)*3,4); + GQ<< + BA.col(1), BA.col(0), BA.col(0).array()+n, BA.col(1).array()+n, + BA.col(2), BA.col(1), BA.col(1).array()+n, BA.col(2).array()+n, + BA.col(0), BA.col(2), BA.col(2).array()+n, BA.col(0).array()+n; + + MatrixXI uGQ; + VectorXI S,sI,sJ; + // Inputs: + // F #F by d list of polygons + // Outputs: + // S #uF list of signed incidences for each unique face + // uF #uF by d list of unique faces + // I #uF index vector so that uF = sort(F,2)(I,:) + // J #F index vector so that sort(F,2) = uF(J,:) + []( + const MatrixXI & F, + VectorXI & S, + MatrixXI & uF, + VectorXI & I, + VectorXI & J) + { + const int m = F.rows(); + const int d = F.cols(); + MatrixXI sF = F; + const auto MN = sF.rowwise().minCoeff().eval(); + // rotate until smallest index is first + for(int p = 0;p M = Matrix::Zero(m,1); + { + VectorXI P = igl::LinSpaced(d,0,d-1); + for(int p = 0;p SJ; + mesh_boolean( + DerivedW(W),DerivedG(G), + Matrix(),MatrixXI(), + MESH_BOOLEAN_TYPE_UNION, + W,G,SJ); + J.derived() = slice(DerivedJ(J),SJ,1); + } +} + +template < + typename DerivedVA, + typename DerivedFA, + typename sType, int sCols, int sOptions, + typename dType, int dCols, int dOptions, + typename DerivedW, + typename DerivedG, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::Matrix & s, + const Eigen::Matrix & d, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J) +{ + return minkowski_sum(VA,FA,s,d,true,W,G,J); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::minkowski_sum, -1, -1, 1, -1, -1>, Eigen::Matrix, CGAL::Lazy_exact_nt, 3, 1, CGAL::Lazy_exact_nt, 3, 1, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::Matrix, 1, 3, 1, 1, 3> const&, Eigen::Matrix, 1, 3, 1, 1, 3> const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::minkowski_sum< + Eigen::Matrix, + Eigen::Matrix, + double, 3, 1, + float, 3, 1, + Eigen::Matrix, -1, -1, 1, -1, -1>, + Eigen::Matrix, + Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/minkowski_sum.h b/src/igl/copyleft/cgal/minkowski_sum.h new file mode 100644 index 000000000..1631f0766 --- /dev/null +++ b/src/igl/copyleft/cgal/minkowski_sum.h @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_MINKOWSKI_SUM_H +#define IGL_COPYLEFT_CGAL_MINKOWSKI_SUM_H + +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Compute the Minkowski sum of a closed triangle mesh (V,F) and a + // set of simplices in 3D. + // + // Inputs: + // VA #VA by 3 list of mesh vertices in 3D + // FA #FA by 3 list of triangle indices into VA + // VB #VB by 3 list of mesh vertices in 3D + // FB #FB by ss list of simplex indices into VB, ss<=3 + // resolve_overlaps whether or not to resolve self-union. If false + // then result may contain self-intersections if input mesh is + // non-convex. + // Outputs: + // W #W by 3 list of mesh vertices in 3D + // G #G by 3 list of triangle indices into W + // J #G by 2 list of indices into + // + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedW, + typename DerivedG, + typename DerivedJ> + IGL_INLINE void minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::MatrixBase & VB, + const Eigen::MatrixBase & FB, + const bool resolve_overlaps, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J); + // Compute the Minkowski sum of a closed triangle mesh (V,F) and a + // segment [s,d] in 3D. + // + // Inputs: + // VA #VA by 3 list of mesh vertices in 3D + // FA #FA by 3 list of triangle indices into VA + // s segment source endpoint in 3D + // d segment source endpoint in 3D + // resolve_overlaps whether or not to resolve self-union. If false + // then result may contain self-intersections if input mesh is + // non-convex. + // Outputs: + // W #W by 3 list of mesh vertices in 3D + // G #G by 3 list of triangle indices into W + // J #G list of indices into [F;#V+F;[s d]] of birth parents + // + template < + typename DerivedVA, + typename DerivedFA, + typename sType, int sCols, int sOptions, + typename dType, int dCols, int dOptions, + typename DerivedW, + typename DerivedG, + typename DerivedJ> + IGL_INLINE void minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::Matrix & s, + const Eigen::Matrix & d, + const bool resolve_overlaps, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J); + template < + typename DerivedVA, + typename DerivedFA, + typename sType, int sCols, int sOptions, + typename dType, int dCols, int dOptions, + typename DerivedW, + typename DerivedG, + typename DerivedJ> + IGL_INLINE void minkowski_sum( + const Eigen::MatrixBase & VA, + const Eigen::MatrixBase & FA, + const Eigen::Matrix & s, + const Eigen::Matrix & d, + Eigen::PlainObjectBase & W, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "minkowski_sum.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.cpp b/src/igl/copyleft/cgal/order_facets_around_edge.cpp new file mode 100644 index 000000000..13b585b50 --- /dev/null +++ b/src/igl/copyleft/cgal/order_facets_around_edge.cpp @@ -0,0 +1,428 @@ +#include "order_facets_around_edge.h" +#include +#include + +#include + +// adj_faces contains signed index starting from +- 1. +template< + typename DerivedV, + typename DerivedF, + typename DerivedI > +void igl::copyleft::cgal::order_facets_around_edge( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + size_t s, + size_t d, + const std::vector& adj_faces, + Eigen::PlainObjectBase& order, bool debug) +{ + // Although we only need exact predicates in the algorithm, + // exact constructions are needed to avoid degeneracies due to + // casting to double. + typedef CGAL::Exact_predicates_exact_constructions_kernel K; + typedef K::Point_3 Point_3; + typedef K::Plane_3 Plane_3; + + auto get_face_index = [&](int adj_f)->size_t + { + return abs(adj_f) - 1; + }; + + auto get_opposite_vertex = [&](size_t fid)->size_t + { + typedef typename DerivedF::Scalar Index; + if (F(fid, 0) != (Index)s && F(fid, 0) != (Index)d) return F(fid, 0); + if (F(fid, 1) != (Index)s && F(fid, 1) != (Index)d) return F(fid, 1); + if (F(fid, 2) != (Index)s && F(fid, 2) != (Index)d) return F(fid, 2); + assert(false); + return -1; + }; + + // Handle base cases + if (adj_faces.size() == 0) + { + order.resize(0, 1); + return; + } else if (adj_faces.size() == 1) + { + order.resize(1, 1); + order(0, 0) = 0; + return; + } else if (adj_faces.size() == 2) + { + const size_t o1 = get_opposite_vertex(get_face_index(adj_faces[0])); + const size_t o2 = get_opposite_vertex(get_face_index(adj_faces[1])); + const Point_3 ps(V(s, 0), V(s, 1), V(s, 2)); + const Point_3 pd(V(d, 0), V(d, 1), V(d, 2)); + const Point_3 p1(V(o1, 0), V(o1, 1), V(o1, 2)); + const Point_3 p2(V(o2, 0), V(o2, 1), V(o2, 2)); + order.resize(2, 1); + switch (CGAL::orientation(ps, pd, p1, p2)) + { + case CGAL::POSITIVE: + order(0, 0) = 1; + order(1, 0) = 0; + break; + case CGAL::NEGATIVE: + order(0, 0) = 0; + order(1, 0) = 1; + break; + case CGAL::COPLANAR: + { + switch (CGAL::coplanar_orientation(ps, pd, p1, p2)) { + case CGAL::POSITIVE: + // Duplicated face, use index to break tie. + order(0, 0) = adj_faces[0] < adj_faces[1] ? 0:1; + order(1, 0) = adj_faces[0] < adj_faces[1] ? 1:0; + break; + case CGAL::NEGATIVE: + // Coplanar faces, one on each side of the edge. + // It is equally valid to order them (0, 1) or (1, 0). + // I cannot think of any reason to prefer one to the + // other. So just use (0, 1) ordering by default. + order(0, 0) = 0; + order(1, 0) = 1; + break; + case CGAL::COLLINEAR: + std::cerr << "Degenerated triangle detected." << + std::endl; + assert(false); + break; + default: + assert(false); + } + } + break; + default: + assert(false); + } + return; + } + + const size_t num_adj_faces = adj_faces.size(); + const size_t o = get_opposite_vertex( get_face_index(adj_faces[0])); + const Point_3 p_s(V(s, 0), V(s, 1), V(s, 2)); + const Point_3 p_d(V(d, 0), V(d, 1), V(d, 2)); + const Point_3 p_o(V(o, 0), V(o, 1), V(o, 2)); + const Plane_3 separator(p_s, p_d, p_o); + if (separator.is_degenerate()) { + throw std::runtime_error( + "Cannot order facets around edge due to degenerated facets"); + } + + std::vector opposite_vertices; + for (size_t i=0; i positive_side; + std::vector negative_side; + std::vector tie_positive_oriented; + std::vector tie_negative_oriented; + + std::vector positive_side_index; + std::vector negative_side_index; + std::vector tie_positive_oriented_index; + std::vector tie_negative_oriented_index; + + for (size_t i=0; i& data) -> std::vector{ + const size_t len = data.size(); + std::vector order(len); + for (size_t i=0; i tie_positive_order = index_sort(tie_positive_oriented); + std::vector tie_negative_order = index_sort(tie_negative_oriented); + + // Copy results into order vector. + const size_t tie_positive_size = tie_positive_oriented.size(); + const size_t tie_negative_size = tie_negative_oriented.size(); + const size_t positive_size = positive_order.size(); + const size_t negative_size = negative_order.size(); + + order.resize( + tie_positive_size + positive_size + tie_negative_size + negative_size,1); + + size_t count=0; + for (size_t i=0; i +IGL_INLINE +void igl::copyleft::cgal::order_facets_around_edge( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + size_t s, + size_t d, + const std::vector& adj_faces, + const Eigen::PlainObjectBase& pivot_point, + Eigen::PlainObjectBase& order) +{ + assert(V.cols() == 3); + assert(F.cols() == 3); + assert(pivot_point.cols() == 3); + auto signed_index_to_index = [&](int signed_idx) + { + return abs(signed_idx) -1; + }; + auto get_opposite_vertex_index = [&](size_t fid) -> typename DerivedF::Scalar + { + typedef typename DerivedF::Scalar Index; + if (F(fid, 0) != (Index)s && F(fid, 0) != (Index)d) return F(fid, 0); + if (F(fid, 1) != (Index)s && F(fid, 1) != (Index)d) return F(fid, 1); + if (F(fid, 2) != (Index)s && F(fid, 2) != (Index)d) return F(fid, 2); + assert(false); + // avoid warning + return -1; + }; + + { + // Check if s, d and pivot are collinear. + typedef CGAL::Exact_predicates_exact_constructions_kernel K; + K::Point_3 ps(V(s,0), V(s,1), V(s,2)); + K::Point_3 pd(V(d,0), V(d,1), V(d,2)); + K::Point_3 pp(pivot_point(0,0), pivot_point(0,1), pivot_point(0,2)); + if (CGAL::collinear(ps, pd, pp)) { + throw std::runtime_error( + "Pivot point is collinear with the outer edge!"); + } + } + + const size_t N = adj_faces.size(); + const size_t num_faces = N + 1; // N adj faces + 1 pivot face + + // Because face indices are used for tie breaking, the original face indices + // in the new faces array must be ascending. + auto comp = [&](int i, int j) + { + return signed_index_to_index(adj_faces[i]) < + signed_index_to_index(adj_faces[j]); + }; + std::vector adj_order(N); + for (size_t i=0; i adj_faces_with_pivot(num_faces); + for (size_t i=0; i, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase >&, bool); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase >&, bool); +template void igl::copyleft::cgal::order_facets_around_edge, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase >&, bool); +template void igl::copyleft::cgal::order_facets_around_edge, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase >&, bool); +template void igl::copyleft::cgal::order_facets_around_edge, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::order_facets_around_edge, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::order_facets_around_edge, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, unsigned long, unsigned long, std::vector > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase> &, bool); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase> &, bool); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::order_facets_around_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase> &, bool); +template void igl::copyleft::cgal::order_facets_around_edge, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, 3, 0, -1, 3>> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase, -1, 3, 0, -1, 3>> const &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::order_facets_around_edge, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::order_facets_around_edge, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, unsigned __int64, unsigned __int64, class std::vector> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.h b/src/igl/copyleft/cgal/order_facets_around_edge.h new file mode 100644 index 000000000..6e09129ac --- /dev/null +++ b/src/igl/copyleft/cgal/order_facets_around_edge.h @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_ORDER_FACETS_AROUND_EDGE_H +#define IGL_COPYLEFT_CGAL_ORDER_FACETS_AROUND_EDGE_H +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a directed edge, sort its adjacent faces. Assuming the + // directed edge is (s, d). Sort the adjacent faces clockwise around the + // axis (d - s), i.e. left-hand rule. An adjacent face is consistently + // oriented if it contains (d, s) as a directed edge. + // + // For overlapping faces, break the tie using signed face index, smaller + // signed index comes before the larger signed index. Signed index is + // computed as (consistent? 1:-1) * (face_index + 1). + // + // Inputs: + // V #V by 3 list of vertices. + // F #F by 3 list of faces + // s Index of source vertex. + // d Index of destination vertex. + // adj_faces List of adjacent face signed indices. + // Output: + // order List of face indices that orders adjacent faces around edge + // (s, d) clockwise. + template< + typename DerivedV, + typename DerivedF, + typename DerivedI > + IGL_INLINE + void order_facets_around_edge( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + size_t s, + size_t d, + const std::vector& adj_faces, + Eigen::PlainObjectBase& order, + bool debug=false); + + // This function is a wrapper around the one above. Since the ordering + // is circular, the pivot point is used to define a starting point. So + // order[0] is the index into adj_face that is immediately after the + // pivot face (s, d, pivot point) in clockwise order. + template< + typename DerivedV, + typename DerivedF, + typename DerivedI> + IGL_INLINE + void order_facets_around_edge( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + size_t s, + size_t d, + const std::vector& adj_faces, + const Eigen::PlainObjectBase& pivot_point, + Eigen::PlainObjectBase& order); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "order_facets_around_edge.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.cpp b/src/igl/copyleft/cgal/order_facets_around_edges.cpp new file mode 100644 index 000000000..767d3e899 --- /dev/null +++ b/src/igl/copyleft/cgal/order_facets_around_edges.cpp @@ -0,0 +1,332 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "order_facets_around_edges.h" +#include "order_facets_around_edge.h" +#include "../../sort_angles.h" +#include +#include +#include + +template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DeriveduE, + typename uE2EType, + typename uE2oEType, + typename uE2CType > +IGL_INLINE +typename std::enable_if::value, void>::type +igl::copyleft::cgal::order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& N, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ) { + + typedef Eigen::Matrix Vector3F; + const typename DerivedV::Scalar EPS = 1e-12; + const size_t num_faces = F.rows(); + const size_t num_undirected_edges = uE.rows(); + + auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; }; + auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; }; + + uE2oE.resize(num_undirected_edges); + uE2C.resize(num_undirected_edges); + + for(size_t ui = 0;ui 0); + + const auto ref_edge = adj_edges[0]; + const auto ref_face = edge_index_to_face_index(ref_edge); + Vector3F ref_normal = N.row(ref_face); + + const auto ref_corner_o = edge_index_to_corner_index(ref_edge); + const auto ref_corner_s = (ref_corner_o+1)%3; + const auto ref_corner_d = (ref_corner_o+2)%3; + + const typename DerivedF::Scalar o = F(ref_face, ref_corner_o); + const typename DerivedF::Scalar s = F(ref_face, ref_corner_s); + const typename DerivedF::Scalar d = F(ref_face, ref_corner_d); + + Vector3F edge = V.row(d) - V.row(s); + auto edge_len = edge.norm(); + bool degenerated = edge_len < EPS; + if (degenerated) { + if (edge_valance <= 2) { + // There is only one way to order 2 or less faces. + edge.setZero(); + } else { + edge.setZero(); + Eigen::Matrix + normals(edge_valance, 3); + for (size_t fei=0; fei= EPS) { + edge.normalize(); + break; + } + } + + // Ensure edge direction are consistent with reference face. + Vector3F in_face_vec = V.row(o) - V.row(s); + if (edge.cross(in_face_vec).dot(ref_normal) < 0) { + edge *= -1; + } + + if (edge.norm() < EPS) { + std::cerr << "=====================================" << std::endl; + std::cerr << " ui: " << ui << std::endl; + std::cerr << "edge: " << ref_edge << std::endl; + std::cerr << "face: " << ref_face << std::endl; + std::cerr << " vs: " << V.row(s) << std::endl; + std::cerr << " vd: " << V.row(d) << std::endl; + std::cerr << "adj face normals: " << std::endl; + std::cerr << normals << std::endl; + std::cerr << "Very degenerated case detected:" << std::endl; + std::cerr << "Near zero edge surrounded by " + << edge_valance << " neearly colinear faces" << + std::endl; + std::cerr << "=====================================" << std::endl; + } + } + } else { + edge.normalize(); + } + + Eigen::MatrixXd angle_data(edge_valance, 3); + std::vector cons(edge_valance); + + for (size_t fei=0; fei +IGL_INLINE +typename std::enable_if::value, void>::type +igl::copyleft::cgal::order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& N, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ) { + + typedef Eigen::Matrix Vector3F; + typedef Eigen::Matrix Vector3E; + const typename DerivedV::Scalar EPS = 1e-12; + const size_t num_faces = F.rows(); + const size_t num_undirected_edges = uE.rows(); + + auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; }; + auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; }; + + uE2oE.resize(num_undirected_edges); + uE2C.resize(num_undirected_edges); + + for(size_t ui = 0;ui 0); + + const auto ref_edge = adj_edges[0]; + const auto ref_face = edge_index_to_face_index(ref_edge); + Vector3F ref_normal = N.row(ref_face); + + const auto ref_corner_o = edge_index_to_corner_index(ref_edge); + const auto ref_corner_s = (ref_corner_o+1)%3; + const auto ref_corner_d = (ref_corner_o+2)%3; + + const typename DerivedF::Scalar o = F(ref_face, ref_corner_o); + const typename DerivedF::Scalar s = F(ref_face, ref_corner_s); + const typename DerivedF::Scalar d = F(ref_face, ref_corner_d); + + Vector3E exact_edge = V.row(d) - V.row(s); + exact_edge.array() /= exact_edge.squaredNorm(); + Vector3F edge( + CGAL::to_double(exact_edge[0]), + CGAL::to_double(exact_edge[1]), + CGAL::to_double(exact_edge[2])); + edge.normalize(); + + Eigen::MatrixXd angle_data(edge_valance, 3); + std::vector cons(edge_valance); + + for (size_t fei=0; fei +IGL_INLINE void igl::copyleft::cgal::order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ) { + + //typedef Eigen::Matrix Vector3E; + const size_t num_faces = F.rows(); + const size_t num_undirected_edges = uE.rows(); + + auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; }; + auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; }; + + uE2oE.resize(num_undirected_edges); + uE2C.resize(num_undirected_edges); + + for(size_t ui = 0;ui 0); + + const auto ref_edge = adj_edges[0]; + const auto ref_face = edge_index_to_face_index(ref_edge); + + const auto ref_corner_o = edge_index_to_corner_index(ref_edge); + const auto ref_corner_s = (ref_corner_o+1)%3; + const auto ref_corner_d = (ref_corner_o+2)%3; + + //const typename DerivedF::Scalar o = F(ref_face, ref_corner_o); + const typename DerivedF::Scalar s = F(ref_face, ref_corner_s); + const typename DerivedF::Scalar d = F(ref_face, ref_corner_d); + + std::vector cons(edge_valance); + std::vector adj_faces(edge_valance); + for (size_t fei=0; fei, Eigen::Matrix, Eigen::Matrix, int, int, bool>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template std::enable_if::Scalar, CGAL::Lazy_exact_nt >::value), void>::type igl::copyleft::cgal::order_facets_around_edges, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int, int, bool>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::copyleft::cgal::order_facets_around_edges, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, long, long, bool>(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::copyleft::cgal::order_facets_around_edges, Eigen::Matrix, Eigen::Matrix, long, long, bool>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::copyleft::cgal::order_facets_around_edges, Eigen::Matrix, Eigen::Matrix, long, long, bool>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::copyleft::cgal::order_facets_around_edges, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, long, long, bool>(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +#endif diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.h b/src/igl/copyleft/cgal/order_facets_around_edges.h new file mode 100644 index 000000000..89ac2918e --- /dev/null +++ b/src/igl/copyleft/cgal/order_facets_around_edges.h @@ -0,0 +1,107 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_ORDER_FACETS_AROUND_EDGES_H +#define IGL_COPYLEFT_CGAL_ORDER_FACETS_AROUND_EDGES_H +#include "../../igl_inline.h" +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // For each undirected edge, sort its adjacent faces. Assuming the + // undirected edge is (s, d). Sort the adjacent faces clockwise around the + // axis (d - s), i.e. left-hand rule. An adjacent face is consistently + // oriented if it contains (d, s) as a directed edge. + // + // For overlapping faces, break the tie using signed face index, smaller + // signed index comes before the larger signed index. Signed index is + // computed as (consistent? 1:-1) * index. + // + // Inputs: + // V #V by 3 list of vertices. + // F #F by 3 list of faces + // N #F by 3 list of face normals. + // uE #uE by 2 list of vertex_indices, represents undirected edges. + // uE2E #uE list of lists that maps uE to E. (a one-to-many map) + // + // Outputs: + // uE2oE #uE list of lists that maps uE to an ordered list of E. (a + // one-to-many map) + // uE2C #uE list of lists of bools indicates whether each face in + // uE2oE[i] is consistently orientated as the ordering. + // + template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DeriveduE, + typename uE2EType, + typename uE2oEType, + typename uE2CType > + IGL_INLINE + typename std::enable_if::value, void>::type + order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& N, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ); + + template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DeriveduE, + typename uE2EType, + typename uE2oEType, + typename uE2CType > + IGL_INLINE + typename std::enable_if::value, void>::type + order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& N, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ); + + // Order faces around each edge. Only exact predicate is used in the algorithm. + // Normal is not needed. + template< + typename DerivedV, + typename DerivedF, + typename DeriveduE, + typename uE2EType, + typename uE2oEType, + typename uE2CType > + IGL_INLINE void order_facets_around_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + std::vector >& uE2oE, + std::vector >& uE2C ); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "order_facets_around_edges.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/orient2D.cpp b/src/igl/copyleft/cgal/orient2D.cpp new file mode 100644 index 000000000..ee5933bb4 --- /dev/null +++ b/src/igl/copyleft/cgal/orient2D.cpp @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "orient2D.h" +#include +#include + +template +IGL_INLINE short igl::copyleft::cgal::orient2D( + const Scalar pa[2], + const Scalar pb[2], + const Scalar pc[2]) +{ + typedef CGAL::Exact_predicates_exact_constructions_kernel Epeck; + typedef CGAL::Exact_predicates_inexact_constructions_kernel Epick; + typedef typename std::conditional::value, + Epeck, Epick>::type Kernel; + + switch(CGAL::orientation( + typename Kernel::Point_2(pa[0], pa[1]), + typename Kernel::Point_2(pb[0], pb[1]), + typename Kernel::Point_2(pc[0], pc[1]))) { + case CGAL::LEFT_TURN: + return 1; + case CGAL::RIGHT_TURN: + return -1; + case CGAL::COLLINEAR: + return 0; + default: + throw "Invalid orientation"; + } +} diff --git a/src/igl/copyleft/cgal/orient2D.h b/src/igl/copyleft/cgal/orient2D.h new file mode 100644 index 000000000..0f2483dfd --- /dev/null +++ b/src/igl/copyleft/cgal/orient2D.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_ORIENT_2D_H +#define IGL_COPYLEFT_CGAL_ORIENT_2D_H + +#include "../../igl_inline.h" + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // pa,pb,pc 2D points. + // Output: + // 1 if pa,pb,pc are counterclockwise oriented. + // 0 if pa,pb,pc are counterclockwise oriented. + // -1 if pa,pb,pc are clockwise oriented. + template + IGL_INLINE short orient2D( + const Scalar pa[2], + const Scalar pb[2], + const Scalar pc[2]); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "orient2D.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/orient3D.cpp b/src/igl/copyleft/cgal/orient3D.cpp new file mode 100644 index 000000000..3bdbfb80e --- /dev/null +++ b/src/igl/copyleft/cgal/orient3D.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "orient3D.h" +#include +#include + +template +IGL_INLINE short igl::copyleft::cgal::orient3D( + const Scalar pa[3], + const Scalar pb[3], + const Scalar pc[3], + const Scalar pd[3]) +{ + typedef CGAL::Exact_predicates_exact_constructions_kernel Epeck; + typedef CGAL::Exact_predicates_inexact_constructions_kernel Epick; + typedef typename std::conditional::value, + Epeck, Epick>::type Kernel; + + switch(CGAL::orientation( + typename Kernel::Point_3(pa[0], pa[1], pa[2]), + typename Kernel::Point_3(pb[0], pb[1], pb[2]), + typename Kernel::Point_3(pc[0], pc[1], pc[2]), + typename Kernel::Point_3(pd[0], pd[1], pd[2]))) { + case CGAL::POSITIVE: + return 1; + case CGAL::NEGATIVE: + return -1; + case CGAL::COPLANAR: + return 0; + default: + throw "Invalid orientation"; + } +} diff --git a/src/igl/copyleft/cgal/orient3D.h b/src/igl/copyleft/cgal/orient3D.h new file mode 100644 index 000000000..029e208b1 --- /dev/null +++ b/src/igl/copyleft/cgal/orient3D.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_ORIENT_3D_H +#define IGL_COPYLEFT_CGAL_ORIENT_3D_H + +#include "../../igl_inline.h" + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // pa,pb,pc,pd 3D points. + // Output: + // 1 if pa,pb,pc,pd forms a tet of positive volume. + // 0 if pa,pb,pc,pd are coplanar. + // -1 if pa,pb,pc,pd forms a tet of negative volume. + template + IGL_INLINE short orient3D( + const Scalar pa[3], + const Scalar pb[3], + const Scalar pc[3], + const Scalar pd[3]); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "orient3D.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/outer_element.cpp b/src/igl/copyleft/cgal/outer_element.cpp new file mode 100644 index 000000000..2bef8cae1 --- /dev/null +++ b/src/igl/copyleft/cgal/outer_element.cpp @@ -0,0 +1,232 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "outer_element.h" +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > +IGL_INLINE void igl::copyleft::cgal::outer_vertex( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v_index, + Eigen::PlainObjectBase & A) +{ + // Algorithm: + // Find an outer vertex (i.e. vertex reachable from infinity) + // Return the vertex with the largest X value. + // If there is a tie, pick the one with largest Y value. + // If there is still a tie, pick the one with the largest Z value. + // If there is still a tie, then there are duplicated vertices within the + // mesh, which violates the precondition. + typedef typename DerivedF::Scalar Index; + const Index INVALID = std::numeric_limits::max(); + const size_t num_selected_faces = I.rows(); + std::vector candidate_faces; + Index outer_vid = INVALID; + typename DerivedV::Scalar outer_val = 0; + for (size_t i=0; i outer_val) + { + outer_val = vx; + outer_vid = v; + candidate_faces = {f}; + } else if (v == outer_vid) + { + candidate_faces.push_back(f); + } else if (vx == outer_val) + { + // Break tie. + auto vy = V(v,1); + auto vz = V(v, 2); + auto outer_y = V(outer_vid, 1); + auto outer_z = V(outer_vid, 2); + assert(!(vy == outer_y && vz == outer_z)); + bool replace = (vy > outer_y) || + ((vy == outer_y) && (vz > outer_z)); + if (replace) + { + outer_val = vx; + outer_vid = v; + candidate_faces = {f}; + } + } + } + } + + assert(outer_vid != INVALID); + assert(candidate_faces.size() > 0); + v_index = outer_vid; + A.resize(candidate_faces.size()); + std::copy(candidate_faces.begin(), candidate_faces.end(), A.data()); +} + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > +IGL_INLINE void igl::copyleft::cgal::outer_edge( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v1, + IndexType & v2, + Eigen::PlainObjectBase & A) { + // Algorithm: + // Find an outer vertex first. + // Find the incident edge with largest abs slope when projected onto XY plane. + // If there is a tie, check the signed slope and use the positive one. + // If there is still a tie, break it using the projected slope onto ZX plane. + // If there is still a tie, again check the signed slope and use the positive one. + // If there is still a tie, then there are multiple overlapping edges, + // which violates the precondition. + typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedV::Index Index; + typedef typename Eigen::Matrix ScalarArray3; + typedef typename Eigen::Matrix IndexArray3; + const Index INVALID = std::numeric_limits::max(); + + Index outer_vid; + Eigen::Matrix candidate_faces; + outer_vertex(V, F, I, outer_vid, candidate_faces); + const ScalarArray3& outer_v = V.row(outer_vid); + assert(candidate_faces.size() > 0); + + auto get_vertex_index = [&](const IndexArray3& f, Index vid) -> Index + { + if (f[0] == vid) return 0; + if (f[1] == vid) return 1; + if (f[2] == vid) return 2; + assert(false); + return -1; + }; + + auto unsigned_value = [](Scalar v) -> Scalar { + if (v < 0) return v * -1; + else return v; + }; + + Scalar outer_slope_YX = 0; + Scalar outer_slope_ZX = 0; + Index outer_opp_vid = INVALID; + bool infinite_slope_detected = false; + std::vector incident_faces; + auto check_and_update_outer_edge = [&](Index opp_vid, Index fid) -> void { + if (opp_vid == outer_opp_vid) + { + incident_faces.push_back(fid); + return; + } + + const ScalarArray3 opp_v = V.row(opp_vid); + if (!infinite_slope_detected && outer_v[0] != opp_v[0]) + { + // Finite slope + const ScalarArray3 diff = opp_v - outer_v; + const Scalar slope_YX = diff[1] / diff[0]; + const Scalar slope_ZX = diff[2] / diff[0]; + const Scalar u_slope_YX = unsigned_value(slope_YX); + const Scalar u_slope_ZX = unsigned_value(slope_ZX); + bool update = false; + if (outer_opp_vid == INVALID) { + update = true; + } else { + const Scalar u_outer_slope_YX = unsigned_value(outer_slope_YX); + if (u_slope_YX > u_outer_slope_YX) { + update = true; + } else if (u_slope_YX == u_outer_slope_YX && + slope_YX > outer_slope_YX) { + update = true; + } else if (slope_YX == outer_slope_YX) { + const Scalar u_outer_slope_ZX = + unsigned_value(outer_slope_ZX); + if (u_slope_ZX > u_outer_slope_ZX) { + update = true; + } else if (u_slope_ZX == u_outer_slope_ZX && + slope_ZX > outer_slope_ZX) { + update = true; + } else if (slope_ZX == u_outer_slope_ZX) { + assert(false); + } + } + } + + if (update) { + outer_opp_vid = opp_vid; + outer_slope_YX = slope_YX; + outer_slope_ZX = slope_ZX; + incident_faces = {fid}; + } + } else if (!infinite_slope_detected) + { + // Infinite slope + outer_opp_vid = opp_vid; + infinite_slope_detected = true; + incident_faces = {fid}; + } + }; + + const size_t num_candidate_faces = candidate_faces.size(); + for (size_t i=0; i +template void igl::copyleft::cgal::outer_edge, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_edge, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, long, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, long&, long&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::copyleft::cgal::outer_edge, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase, -1, 3, 0, -1, 3>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::outer_edge, class Eigen::Matrix, class Eigen::Matrix, __int64, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, __int64 &, __int64 &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/outer_element.h b/src/igl/copyleft/cgal/outer_element.h new file mode 100644 index 000000000..adaef3464 --- /dev/null +++ b/src/igl/copyleft/cgal/outer_element.h @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_OUTER_ELEMENT_H +#define IGL_COPYLEFT_CGAL_OUTER_ELEMENT_H +#include "../../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Find a vertex that is reachable from infinite without crossing any faces. + // Such vertex is called "outer vertex." + // + // Precondition: The input mesh must have all self-intersection resolved and + // no duplicated vertices. See cgal::remesh_self_intersections.h for how to + // obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // I #I list of facets to consider + // Outputs: + // v_index index of outer vertex + // A #A list of facets incident to the outer vertex + template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > + IGL_INLINE void outer_vertex( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v_index, + Eigen::PlainObjectBase & A); + // Find an edge that is reachable from infinity without crossing any faces. + // Such edge is called "outer edge." + // + // Precondition: The input mesh must have all self-intersection resolved + // and no duplicated vertices. The correctness of the output depends on + // the fact that there is no edge overlap. See + // cgal::remesh_self_intersections.h for how to obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // I #I list of facets to consider + // Outputs: + // v1 index of the first end point of outer edge + // v2 index of the second end point of outer edge + // A #A list of facets incident to the outer edge + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > + IGL_INLINE void outer_edge( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v1, + IndexType & v2, + Eigen::PlainObjectBase & A); + + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "outer_element.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/outer_facet.cpp b/src/igl/copyleft/cgal/outer_facet.cpp new file mode 100644 index 000000000..4525dd856 --- /dev/null +++ b/src/igl/copyleft/cgal/outer_facet.cpp @@ -0,0 +1,180 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "outer_facet.h" +#include "outer_element.h" +#include "order_facets_around_edge.h" +#include +#include + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType + > +IGL_INLINE void igl::copyleft::cgal::outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped) { + + // Algorithm: + // + // 1. Find an outer edge (s, d). + // + // 2. Order adjacent facets around this edge. Because the edge is an + // outer edge, there exists a plane passing through it such that all its + // adjacent facets lie on the same side. The implementation of + // order_facets_around_edge() will find a natural start facet such that + // The first and last facets according to this order are on the outside. + // + // 3. Because the vertex s is an outer vertex by construction (see + // implemnetation of outer_edge()). The first adjacent facet is facing + // outside (i.e. flipped=false) if it has positive X normal component. + // If it has zero normal component, it is facing outside if it contains + // directed edge (s, d). + + //typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedV::Index Index; + + Index s,d; + Eigen::Matrix incident_faces; + outer_edge(V, F, I, s, d, incident_faces); + assert(incident_faces.size() > 0); + + auto convert_to_signed_index = [&](size_t fid) -> int{ + if ((F(fid, 0) == s && F(fid, 1) == d) || + (F(fid, 1) == s && F(fid, 2) == d) || + (F(fid, 2) == s && F(fid, 0) == d) ) { + return int(fid+1) * -1; + } else { + return int(fid+1); + } + }; + + auto signed_index_to_index = [&](int signed_id) -> size_t { + return size_t(abs(signed_id) - 1); + }; + + std::vector adj_faces(incident_faces.size()); + std::transform(incident_faces.data(), + incident_faces.data() + incident_faces.size(), + adj_faces.begin(), + convert_to_signed_index); + + DerivedV pivot_point = V.row(s); + pivot_point(0, 0) += 1.0; + + Eigen::VectorXi order; + order_facets_around_edge(V, F, s, d, adj_faces, pivot_point, order); + + f = signed_index_to_index(adj_faces[order[0]]); + flipped = adj_faces[order[0]] > 0; +} + + + +template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedI, + typename IndexType + > +IGL_INLINE void igl::copyleft::cgal::outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped) { + // Algorithm: + // Find an outer edge. + // Find the incident facet with the largest absolute X normal component. + // If there is a tie, keep the one with positive X component. + // If there is still a tie, pick the face with the larger signed index + // (flipped face has negative index). + typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedV::Index Index; + const size_t INVALID = std::numeric_limits::max(); + + Index v1,v2; + Eigen::Matrix incident_faces; + outer_edge(V, F, I, v1, v2, incident_faces); + assert(incident_faces.size() > 0); + + auto generic_fabs = [&](const Scalar& val) -> const Scalar { + if (val >= 0) return val; + else return -val; + }; + + Scalar max_nx = 0; + size_t outer_fid = INVALID; + const size_t num_incident_faces = incident_faces.size(); + for (size_t i=0; i generic_fabs(max_nx)) { + max_nx = nx; + outer_fid = fid; + } else if (nx == -max_nx && nx > 0) { + max_nx = nx; + outer_fid = fid; + } else if (nx == max_nx) { + if ((max_nx >= 0 && outer_fid < fid) || + (max_nx < 0 && outer_fid > fid)) { + max_nx = nx; + outer_fid = fid; + } + } + } + } + + assert(outer_fid != INVALID); + f = outer_fid; + flipped = max_nx < 0; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +#include +#include +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +template void igl::copyleft::cgal::outer_facet, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::copyleft::cgal::outer_facet, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +#ifdef WIN32 +template void igl::copyleft::cgal::outer_facet, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64>(class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, unsigned __int64 &, bool &); +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, unsigned __int64 &, bool &); +template void igl::copyleft::cgal::outer_facet, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, unsigned __int64 &, bool &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/outer_facet.h b/src/igl/copyleft/cgal/outer_facet.h new file mode 100644 index 000000000..b025a94ab --- /dev/null +++ b/src/igl/copyleft/cgal/outer_facet.h @@ -0,0 +1,91 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_COPYLEFT_CGAL_OUTER_FACET_H +#define IGL_COPYLEFT_CGAL_OUTER_FACET_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Find a facet that is reachable from infinity without crossing any faces. + // Such facet is called "outer facet." + // + // Precondition: The input mesh must have all self-intersection resolved. I.e + // there is no duplicated vertices, no overlapping edge and no intersecting + // faces (the only exception is there could be topologically duplicated faces). + // See cgal::remesh_self_intersections.h for how to obtain such input. + // + // This function differ from igl::outer_facet() in the fact this + // function is more robust because it does not rely on facet normals. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // I #I list of facets to consider + // Outputs: + // f Index of the outer facet. + // flipped true iff the normal of f points inwards. + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType + > + IGL_INLINE void outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped); + + // Find a facet that is reachable from infinity without crossing any faces. + // Such facet is called "outer facet." + // + // Precondition: The input mesh must have all self-intersection resolved. + // I.e there is no duplicated vertices, no overlapping edge and no + // intersecting faces (the only exception is there could be topologically + // duplicated faces). See cgal::remesh_self_intersections.h for how to + // obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // N #N by 3 list of face normals + // I #I list of facets to consider + // Outputs: + // f Index of the outer facet. + // flipped true iff the normal of f points inwards. + template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedI, + typename IndexType + > + IGL_INLINE void outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped); + + } + + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "outer_facet.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/outer_hull.cpp b/src/igl/copyleft/cgal/outer_hull.cpp new file mode 100644 index 000000000..71586f707 --- /dev/null +++ b/src/igl/copyleft/cgal/outer_hull.cpp @@ -0,0 +1,535 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "outer_hull.h" +#include "extract_cells.h" +#include "remesh_self_intersections.h" +#include "assign.h" +#include "../../remove_unreferenced.h" + +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedHV, + typename DerivedHF, + typename DerivedJ, + typename Derivedflip> +IGL_INLINE void igl::copyleft::cgal::outer_hull( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & HV, + Eigen::PlainObjectBase & HF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & flip) +{ + // Exact types + typedef CGAL::Epeck Kernel; + typedef Kernel::FT ExactScalar; + typedef + Eigen::Matrix< + ExactScalar, + Eigen::Dynamic, + Eigen::Dynamic, + DerivedHV::IsRowMajor> + MatrixXES; + // Remesh self-intersections + MatrixXES Vr; + DerivedHF Fr; + DerivedJ Jr; + { + RemeshSelfIntersectionsParam params; + params.stitch_all = true; + Eigen::VectorXi I; + Eigen::MatrixXi IF; + remesh_self_intersections(V, F, params, Vr, Fr, IF, Jr, I); + // Merge coinciding vertices into non-manifold vertices. + std::for_each(Fr.data(), Fr.data()+Fr.size(), + [&I](typename DerivedHF::Scalar& a) { a=I[a]; }); + // Remove unreferenced vertices. + Eigen::VectorXi UIM; + remove_unreferenced(MatrixXES(Vr),DerivedHF(Fr), Vr, Fr, UIM); + } + // Extract cells for each face + Eigen::MatrixXi C; + extract_cells(Vr,Fr,C); + // Extract faces on ambient cell + int num_outer = 0; + for(int i = 0;i +#include +#include +#include +#include +#include +#include +//#define IGL_OUTER_HULL_DEBUG + +template < + typename DerivedV, + typename DerivedF, + typename DerivedG, + typename DerivedJ, + typename Derivedflip> +IGL_INLINE void igl::copyleft::cgal::outer_hull_legacy( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & flip) +{ +#ifdef IGL_OUTER_HULL_DEBUG + std::cerr << "Extracting outer hull" << std::endl; +#endif + using namespace Eigen; + using namespace std; + typedef typename DerivedF::Index Index; + Matrix C; + typedef Matrix MatrixXV; + //typedef Matrix MatrixXF; + typedef Matrix MatrixXG; + typedef Matrix MatrixXJ; + const Index m = F.rows(); + + // UNUSED: + //const auto & duplicate_simplex = [&F](const int f, const int g)->bool + //{ + // return + // (F(f,0) == F(g,0) && F(f,1) == F(g,1) && F(f,2) == F(g,2)) || + // (F(f,1) == F(g,0) && F(f,2) == F(g,1) && F(f,0) == F(g,2)) || + // (F(f,2) == F(g,0) && F(f,0) == F(g,1) && F(f,1) == F(g,2)) || + // (F(f,0) == F(g,2) && F(f,1) == F(g,1) && F(f,2) == F(g,0)) || + // (F(f,1) == F(g,2) && F(f,2) == F(g,1) && F(f,0) == F(g,0)) || + // (F(f,2) == F(g,2) && F(f,0) == F(g,1) && F(f,1) == F(g,0)); + //}; + +#ifdef IGL_OUTER_HULL_DEBUG + cout<<"outer hull..."< MatrixX2I; + typedef Matrix VectorXI; + //typedef Matrix Vector3F; + MatrixX2I E,uE; + VectorXI EMAP; + vector > uE2E; + unique_edge_map(F,E,uE,EMAP,uE2E); +#ifdef IGL_OUTER_HULL_DEBUG + for (size_t ui=0; ui > uE2oE; + std::vector > uE2C; + order_facets_around_edges(V, F, uE, uE2E, uE2oE, uE2C); + uE2E = uE2oE; + VectorXI diIM(3*m); + for (auto ue : uE2E) { + for (size_t i=0; i > > TT,_1; + triangle_triangle_adjacency(E,EMAP,uE2E,false,TT,_1); + VectorXI counts; +#ifdef IGL_OUTER_HULL_DEBUG + cout<<"facet components..."< FH(m,false); + vector EH(3*m,false); + vector vG(ncc); + vector vJ(ncc); + vector vIM(ncc); + //size_t face_count = 0; + for(size_t id = 0;id g(ncc,0); + // place order of each face in its respective component + for(Index f = 0;f Q; + Q.push(f+0*m); + Q.push(f+1*m); + Q.push(f+2*m); + flip(f) = f_flip; + //std::cout << "face " << face_count++ << ": " << f << std::endl; + //std::cout << "f " << F.row(f).array()+1 << std::endl; + //cout<<"flip("< "<<(nf+1)<<", |"<< + // di[EMAP(e)][diIM(e)]<<" - "< "<<(nf+1)<= 0) + { + max_ne = uE2E[EMAP(e)][nfei]; + } + + if(max_ne>=0) + { + // face of neighbor + const int nf = max_ne%m; +#ifdef IGL_OUTER_HULL_DEBUG + if(!FH[nf]) + { + // first time seeing face + cout<<(f+1)<<" --> "<<(nf+1)< & V, + const MatrixXG & A, + const MatrixXG & B)->bool + { + const auto & bounding_box = []( + const Eigen::PlainObjectBase & V, + const MatrixXG & F)-> + DerivedV + { + DerivedV BB(2,3); + BB<< + 1e26,1e26,1e26, + -1e26,-1e26,-1e26; + const size_t m = F.rows(); + for(size_t f = 0;f0 || + (ABB.row(0)-BBB.row(1)).maxCoeff()>0 ) + { + // bounding boxes do not overlap + return false; + } else { + return true; + } + }; + + // Reject components which are completely inside other components + vector keep(ncc,true); + size_t nG = 0; + // This is O( ncc * ncc * m) + for(size_t id = 0;id unresolved; + for(size_t oid = 0;oid, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_hull_legacy< Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &); +template void igl::copyleft::cgal::outer_hull_legacy< Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_hull_legacy, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::outer_hull_legacy, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/outer_hull.h b/src/igl/copyleft/cgal/outer_hull.h new file mode 100644 index 000000000..191a8d492 --- /dev/null +++ b/src/igl/copyleft/cgal/outer_hull.h @@ -0,0 +1,84 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_OUTER_HULL_H +#define IGL_COPYLEFT_CGAL_OUTER_HULL_H +#include "../../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Compute the "outer hull" of a piecewise constant winding number induce + // triangle mesh (V,F). + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // HV #HV by 3 list of output vertex positions + // HF #HF by 3 list of output triangle indices into HV + // J #HF list of indices into F + // flip #HF list of whether facet was flipped when added to HF + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedHV, + typename DerivedHF, + typename DerivedJ, + typename Derivedflip> + IGL_INLINE void outer_hull( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & HV, + Eigen::PlainObjectBase & HF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & flip); + // Compute the "outer hull" of a potentially non-manifold mesh (V,F) whose + // intersections have been "resolved" (e.g. using `cork` or + // `igl::copyleft::cgal::selfintersect`). The outer hull is defined to be all facets + // (regardless of orientation) for which there exists some path from infinity + // to the face without intersecting any other facets. For solids, this is the + // surface of the solid. In general this includes any thin "wings" or + // "flaps". This implementation largely follows Section 3.6 of "Direct + // repair of self-intersecting meshes" [Attene 2014]. + // + // Note: This doesn't require the input mesh to be piecewise constant + // winding number, but won't handle multiple non-nested connected + // components. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // G #G by 3 list of output triangle indices into V + // J #G list of indices into F + // flip #F list of whether facet was added to G **and** flipped orientation + // (false for faces not added to G) + template < + typename DerivedV, + typename DerivedF, + typename DerivedG, + typename DerivedJ, + typename Derivedflip> + IGL_INLINE void outer_hull_legacy( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & flip); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "outer_hull.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.cpp b/src/igl/copyleft/cgal/peel_outer_hull_layers.cpp new file mode 100644 index 000000000..73ae07eea --- /dev/null +++ b/src/igl/copyleft/cgal/peel_outer_hull_layers.cpp @@ -0,0 +1,124 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "peel_outer_hull_layers.h" +#include "outer_hull.h" +#include "../../LinSpaced.h" +#include +#include +//#define IGL_PEEL_OUTER_HULL_LAYERS_DEBUG +#ifdef IGL_PEEL_OUTER_HULL_LAYERS_DEBUG +#include "../../writePLY.h" +#include "../../writeDMAT.h" +#include "../../STR.h" +#endif + +template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename Derivedflip> +IGL_INLINE size_t igl::copyleft::cgal::peel_outer_hull_layers( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & flip) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedF::Index Index; + typedef Matrix MatrixXF; + typedef Matrix MatrixXI; + typedef Matrix MatrixXflip; + const Index m = F.rows(); +#ifdef IGL_PEEL_OUTER_HULL_LAYERS_DEBUG + cout<<"peel outer hull layers..."<(m,0,m-1); + // This is O(n * layers) + MatrixXI P(m,1); + Index iter = 0; + while(Fr.size() > 0) + { + assert(Fr.rows() == IM.rows()); + // Compute outer hull of current Fr + MatrixXF Fo; + MatrixXI Jo; + MatrixXflip flipr; +#ifdef IGL_PEEL_OUTER_HULL_LAYERS_DEBUG + { + cout<<"calling outer hull..." << iter < in_outer(Fr.rows(),false); + for(Index g = 0;g +#include +template unsigned long igl::copyleft::cgal::peel_outer_hull_layers, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template size_t igl::copyleft::cgal::peel_outer_hull_layers, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.h b/src/igl/copyleft/cgal/peel_outer_hull_layers.h new file mode 100644 index 000000000..ee2752b29 --- /dev/null +++ b/src/igl/copyleft/cgal/peel_outer_hull_layers.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_PEEL_OUTER_HULL_LAYERS_H +#define IGL_COPYLEFT_CGAL_PEEL_OUTER_HULL_LAYERS_H +#include "../../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Computes necessary generic information for boolean operations by + // successively "peeling" off the "outer hull" of a mesh (V,F) resulting from + // "resolving" all (self-)intersections. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // I #F list of which peel Iation a facet belongs + // flip #F list of whether a facet's orientation was flipped when facet + // "peeled" into its associated outer hull layer. + // Returns number of peels + template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename Derivedflip> + IGL_INLINE size_t peel_outer_hull_layers( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & flip); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "peel_outer_hull_layers.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/peel_winding_number_layers.cpp b/src/igl/copyleft/cgal/peel_winding_number_layers.cpp new file mode 100644 index 000000000..2bb71a780 --- /dev/null +++ b/src/igl/copyleft/cgal/peel_winding_number_layers.cpp @@ -0,0 +1,33 @@ +#include "peel_winding_number_layers.h" + +#include + +#include "propagate_winding_numbers.h" + +template< +typename DerivedV, +typename DerivedF, +typename DerivedW > +IGL_INLINE size_t igl::copyleft::cgal::peel_winding_number_layers( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase& W) { + const size_t num_faces = F.rows(); + Eigen::VectorXi labels(num_faces); + labels.setZero(); + + Eigen::MatrixXi winding_numbers; + igl::copyleft::cgal::propagate_winding_numbers(V, F, labels, winding_numbers); + assert(winding_numbers.rows() == num_faces); + assert(winding_numbers.cols() == 2); + + int min_w = winding_numbers.minCoeff(); + int max_w = winding_numbers.maxCoeff(); + assert(max_w > min_w); + + W.resize(num_faces, 1); + for (size_t i=0; i + +namespace igl { + namespace copyleft { + namespace cgal { + template< + typename DerivedV, + typename DerivedF, + typename DerivedW > + IGL_INLINE size_t peel_winding_number_layers( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase& W); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "peel_winding_number_layers.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp b/src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp new file mode 100644 index 000000000..0156edbe2 --- /dev/null +++ b/src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "piecewise_constant_winding_number.h" +#include "../../piecewise_constant_winding_number.h" +#include "remesh_self_intersections.h" +#include +#include + +template < typename DerivedV, typename DerivedF> +IGL_INLINE bool igl::copyleft::cgal::piecewise_constant_winding_number( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F) +{ + Eigen::Matrix VV; + Eigen::Matrix FF; + Eigen::Matrix IF; + Eigen::Matrix J; + Eigen::Matrix UIM,IM; + // resolve intersections + remesh_self_intersections(V,F,{false,false,true},VV,FF,IF,J,IM); + return igl::piecewise_constant_winding_number(FF); +} diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.h b/src/igl/copyleft/cgal/piecewise_constant_winding_number.h new file mode 100644 index 000000000..2b93913b3 --- /dev/null +++ b/src/igl/copyleft/cgal/piecewise_constant_winding_number.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_PIECEWISE_CONSTANT_WINDING_NUMBER_H +#define IGL_COPYLEFT_CGAL_PIECEWISE_CONSTANT_WINDING_NUMBER_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // PIECEWISE_CONSTANT_WINDING_NUMBER Determine if a given mesh induces a + // piecewise constant winding number field: Is this mesh valid input to + // solid set operations. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of triangle indices into V + // Returns true if the mesh _combinatorially_ induces a piecewise + // constant winding number field. + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE bool piecewise_constant_winding_number( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase& F); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "piecewise_constant_winding_number.cpp" +#endif +#endif + diff --git a/src/igl/copyleft/cgal/point_areas.cpp b/src/igl/copyleft/cgal/point_areas.cpp new file mode 100644 index 000000000..bb98bac3c --- /dev/null +++ b/src/igl/copyleft/cgal/point_areas.cpp @@ -0,0 +1,181 @@ +#include "point_areas.h" +#include "delaunay_triangulation.h" + +#include "../../colon.h" +#include "../../slice.h" +#include "../../slice_mask.h" +#include "../../parallel_for.h" + +#include "CGAL/Exact_predicates_inexact_constructions_kernel.h" +#include "CGAL/Triangulation_vertex_base_with_info_2.h" +#include "CGAL/Triangulation_data_structure_2.h" +#include "CGAL/Delaunay_triangulation_2.h" + + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef CGAL::Triangulation_vertex_base_with_info_2 Vb; +typedef CGAL::Triangulation_data_structure_2 Tds; +typedef CGAL::Delaunay_triangulation_2 Delaunay; +typedef Kernel::Point_2 Point; + +namespace igl { + namespace copyleft{ + namespace cgal{ + + template + IGL_INLINE void point_areas( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& I, + const Eigen::MatrixBase& N, + Eigen::PlainObjectBase & A) + { + Eigen::MatrixXd T; + point_areas(P,I,N,A,T); + } + + + template + IGL_INLINE void point_areas( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& I, + const Eigen::MatrixBase& N, + Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & T) + { + typedef typename DerivedP::Scalar real; + typedef typename DerivedN::Scalar scalarN; + typedef typename DerivedA::Scalar scalarA; + typedef Eigen::Matrix RowVec3; + typedef Eigen::Matrix RowVec2; + + typedef Eigen::Matrix MatrixP; + typedef Eigen::Matrix MatrixN; + typedef Eigen::Matrix VecotorO; + typedef Eigen::Matrix MatrixI; + + + + const int n = P.rows(); + + assert(P.cols() == 3 && "P must have exactly 3 columns"); + assert(P.rows() == N.rows() + && "P and N must have the same number of rows"); + assert(P.rows() == I.rows() + && "P and I must have the same number of rows"); + + A.setZero(n,1); + T.setZero(n,3); + igl::parallel_for(P.rows(),[&](int i) + { + MatrixI neighbor_index = I.row(i); + MatrixP neighbors; + igl::slice(P,neighbor_index,1,neighbors); + if(N.rows() && neighbors.rows() > 1){ + MatrixN neighbor_normals; + igl::slice(N,neighbor_index,1,neighbor_normals); + Eigen::Matrix poi_normal = neighbor_normals.row(0); + Eigen::Matrix dotprod = + poi_normal(0)*neighbor_normals.col(0) + + poi_normal(1)*neighbor_normals.col(1) + + poi_normal(2)*neighbor_normals.col(2); + Eigen::Array keep = dotprod.array() > 0; + igl::slice_mask(Eigen::MatrixXd(neighbors),keep,1,neighbors); + } + if(neighbors.rows() <= 2){ + A(i) = 0; + } else { + //subtract the mean from neighbors, then take svd, + //the scores will be U*S. This is our pca plane fitting + RowVec3 mean = neighbors.colwise().mean(); + MatrixP mean_centered = neighbors.rowwise() - mean; + Eigen::JacobiSVD svd(mean_centered, + Eigen::ComputeThinU | Eigen::ComputeThinV); + MatrixP scores = svd.matrixU() * svd.singularValues().asDiagonal(); + + T.row(i) = svd.matrixV().col(2).transpose(); + if(T.row(i).dot(N.row(i)) < 0){ + T.row(i) *= -1; + } + + MatrixP plane; + igl::slice(scores,igl::colon(0,scores.rows()-1), + igl::colon(0,1),plane); + + std::vector< std::pair > points; + //This is where we obtain a delaunay triangulation of the points + for(unsigned iter = 0; iter < plane.rows(); iter++){ + points.push_back( std::make_pair( + Point(plane(iter,0),plane(iter,1)), iter ) ); + } + Delaunay triangulation; + triangulation.insert(points.begin(),points.end()); + Eigen::MatrixXi F(triangulation.number_of_faces(),3); + int f_row = 0; + for(Delaunay::Finite_faces_iterator fit = + triangulation.finite_faces_begin(); + fit != triangulation.finite_faces_end(); ++fit) { + Delaunay::Face_handle face = fit; + F.row(f_row) = Eigen::RowVector3i((int)face->vertex(0)->info(), + (int)face->vertex(1)->info(), + (int)face->vertex(2)->info()); + f_row++; + } + + //Here we calculate the voronoi area of the point + scalarA area_accumulator = 0; + for(int f = 0; f < F.rows(); f++){ + int X = -1; + for(int face_iter = 0; face_iter < 3; face_iter++){ + if(F(f,face_iter) == 0){ + X = face_iter; + } + } + if(X >= 0){ + //Triangle XYZ with X being the point we want the area of + int Y = (X+1)%3; + int Z = (X+2)%3; + scalarA x = (plane.row(F(f,Y))-plane.row(F(f,Z))).norm(); + scalarA y = (plane.row(F(f,X))-plane.row(F(f,Z))).norm(); + scalarA z = (plane.row(F(f,Y))-plane.row(F(f,X))).norm(); + scalarA cosX = (z*z + y*y - x*x)/(2*y*z); + scalarA cosY = (z*z + x*x - y*y)/(2*x*z); + scalarA cosZ = (x*x + y*y - z*z)/(2*y*x); + Eigen::Matrix barycentric; + barycentric << x*cosX, y*cosY, z*cosZ; + barycentric /= (barycentric(0)+barycentric(1)+barycentric(2)); + + //TODO: to make numerically stable, reorder so that x≥y≥z: + scalarA full_area = 0.25*std::sqrt( + (x+(y+z))*(z-(x-y))*(z+(x-y))*(x+(y-z))); + Eigen::Matrix partial_area = + barycentric * full_area; + if(cosX < 0){ + area_accumulator += 0.5*full_area; + } else if (cosY < 0 || cosZ < 0){ + area_accumulator += 0.25*full_area; + } else { + area_accumulator += (partial_area(1) + partial_area(2))/2; + } + } + } + + if(std::isfinite(area_accumulator)){ + A(i) = area_accumulator; + } else { + A(i) = 0; + } + } + },1000); + } + } + } +} + + + + diff --git a/src/igl/copyleft/cgal/point_areas.h b/src/igl/copyleft/cgal/point_areas.h new file mode 100644 index 000000000..ec0366488 --- /dev/null +++ b/src/igl/copyleft/cgal/point_areas.h @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Gavin Barill +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/ + +#ifndef IGL_POINT_AREAS_H +#define IGL_POINT_AREAS_H +#include "../../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a 3D set of points P, each with a list of k-nearest-neighbours, + // estimate the geodesic voronoi area associated with each point. + // + // The k nearest neighbours may be known from running igl::knn_octree on + // the output data from igl::octree. We reccomend using a k value + // between 15 and 20 inclusive for accurate area estimation. + // + // N is used filter the neighbours, to ensure area estimation only occurs + // using neighbors that are on the same side of the surface (ie for thin + // sheets), as well as to solve the orientation ambiguity of the tangent + // plane normal. + // + // Note: This function *should* be implemented by pre-filtering I, rather + // than filtering in this function using N. In this case, the function + // would only take P and I as input. + // + // Inputs: + // P #P by 3 list of point locations + // I #P by k list of k-nearest-neighbor indices into P + // N #P by 3 list of point normals + // Outputs: + // A #P list of estimated areas + template + IGL_INLINE void point_areas( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& I, + const Eigen::MatrixBase& N, + Eigen::PlainObjectBase & A); + + // This version can be used to output the tangent plane normal at each + // point. Since we area already fitting a plane to each point's neighbour + // set, the tangent plane normals come "for free" + // + // Inputs: + // P #P by 3 list of point locations + // I #P by k list of k-nearest-neighbor indices into P + // N #P by 3 list of point normals + // Outputs: + // A #P list of estimated areas + // T #P by 3 list of tangent plane normals for each point + template + IGL_INLINE void point_areas( + const Eigen::MatrixBase& P, + const Eigen::MatrixBase& I, + const Eigen::MatrixBase& N, + Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & T); + + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_areas.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.cpp b/src/igl/copyleft/cgal/point_mesh_squared_distance.cpp new file mode 100644 index 000000000..18b5706ae --- /dev/null +++ b/src/igl/copyleft/cgal/point_mesh_squared_distance.cpp @@ -0,0 +1,138 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_mesh_squared_distance.h" +#include "mesh_to_cgal_triangle_list.h" +#include "assign_scalar.h" + +template < + typename Kernel, + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::copyleft::cgal::point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) +{ + using namespace std; + typedef CGAL::Triangle_3 Triangle_3; + typedef typename std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + Tree tree; + vector T; + point_mesh_squared_distance_precompute(V,F,tree,T); + return point_mesh_squared_distance(P,tree,T,sqrD,I,C); +} + +template +IGL_INLINE void igl::copyleft::cgal::point_mesh_squared_distance_precompute( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & tree, + std::vector > & T) +{ + using namespace std; + + typedef CGAL::Triangle_3 Triangle_3; + typedef CGAL::Point_3 Point_3; + typedef typename std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + + // Must be 3D + assert(V.cols() == 3); + // Must be triangles + assert(F.cols() == 3); + + // WTF ALERT!!!! + // + // There's a bug in clang probably or at least in cgal. Without calling this + // line (I guess invoking some compilation from ), clang will vomit + // errors inside CGAL. + // + // http://stackoverflow.com/questions/27748442/is-clangs-c11-support-reliable + T.reserve(0); + + // Make list of cgal triangles + mesh_to_cgal_triangle_list(V,F,T); + tree.clear(); + tree.insert(T.begin(),T.end()); + tree.accelerate_distance_queries(); + // accelerate_distance_queries doesn't seem actually to do _all_ of the + // precomputation. the tree (despite being const) will still do more + // precomputation and reorganizing on the first call of `closest_point` or + // `closest_point_and_primitive`. Therefore, call it once here. + tree.closest_point_and_primitive(Point_3(0,0,0)); +} + +template < + typename Kernel, + typename DerivedP, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::copyleft::cgal::point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & tree, + const std::vector > & T, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) +{ + typedef CGAL::Triangle_3 Triangle_3; + typedef typename std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + typedef typename Tree::Point_and_primitive_id Point_and_primitive_id; + typedef CGAL::Point_3 Point_3; + assert(P.cols() == 3); + const int n = P.rows(); + sqrD.resize(n,1); + I.resize(n,1); + C.resize(n,P.cols()); + for(int p = 0;p < n;p++) + { + Point_3 query(P(p,0),P(p,1),P(p,2)); + // Find closest point and primitive id + Point_and_primitive_id pp = tree.closest_point_and_primitive(query); + Point_3 closest_point = pp.first; + assign_scalar(closest_point[0],C(p,0)); + assign_scalar(closest_point[1],C(p,1)); + assign_scalar(closest_point[2],C(p,2)); + assign_scalar((closest_point-query).squared_length(),sqrD(p)); + I(p) = pp.second - T.begin(); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::copyleft::cgal::point_mesh_squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::point_mesh_squared_distance, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, 1, 0, -1, 1>, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, 1, 0, -1, 1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.h b/src/igl/copyleft/cgal/point_mesh_squared_distance.h new file mode 100644 index 000000000..b525bddda --- /dev/null +++ b/src/igl/copyleft/cgal/point_mesh_squared_distance.h @@ -0,0 +1,104 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POINT_MESH_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_POINT_MESH_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include +#include +#include "CGAL_includes.hpp" +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Compute distances from a set of points P to a triangle mesh (V,F) + // + // Templates: + // Kernal CGAL computation and construction kernel (e.g. + // CGAL::Simple_cartesian) + // Inputs: + // P #P by 3 list of query point positions + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // Outputs: + // sqrD #P list of smallest squared distances + // I #P list of facet indices corresponding to smallest distances + // C #P by 3 list of closest points + // + // Known bugs: This only computes distances to triangles. So unreferenced + // vertices and degenerate triangles (segments) are ignored. + template < + typename Kernel, + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE void point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C); + // Probably can do this in a way that we don't pass around `tree` and `T` + // + // Outputs: + // tree CGAL's AABB tree + // T list of CGAL triangles in order of F (for determining which was found + // in computation) + template < + typename Kernel, + typename DerivedV, + typename DerivedF + > + IGL_INLINE void point_mesh_squared_distance_precompute( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & tree, + std::vector > & T); + // Inputs: + // see above + // Outputs: + // see above + template < + typename Kernel, + typename DerivedP, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE void point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const CGAL::AABB_tree< + CGAL::AABB_traits >::iterator + > + > + > & tree, + const std::vector > & T, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_mesh_squared_distance.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.cpp b/src/igl/copyleft/cgal/point_segment_squared_distance.cpp new file mode 100644 index 000000000..257206ae6 --- /dev/null +++ b/src/igl/copyleft/cgal/point_segment_squared_distance.cpp @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_segment_squared_distance.h" + +template < typename Kernel> +IGL_INLINE void igl::copyleft::cgal::point_segment_squared_distance( + const CGAL::Point_3 & P1, + const CGAL::Segment_3 & S2, + CGAL::Point_3 & P2, + typename Kernel::FT & d) +{ + if(S2.is_degenerate()) + { + P2 = S2.source(); + d = (P1-P2).squared_length(); + return; + } + // http://stackoverflow.com/a/1501725/148668 + const auto sqr_len = S2.squared_length(); + assert(sqr_len != 0); + const auto & V = S2.source(); + const auto & W = S2.target(); + const auto t = (P1-V).dot(W-V)/sqr_len; + if(t<0) + { + P2 = V; + }else if(t>1) + { + P2 = W; + }else + { + P2 = V + t*(W-V); + } + d = (P1-P2).squared_length(); +} + diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.h b/src/igl/copyleft/cgal/point_segment_squared_distance.h new file mode 100644 index 000000000..eac628b2a --- /dev/null +++ b/src/igl/copyleft/cgal/point_segment_squared_distance.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POINT_SEGMENT_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_POINT_SEGMENT_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a point P1 and segment S2 find the points on each of closest + // approach and the squared distance thereof. + // + // Inputs: + // P1 point + // S2 segment + // Outputs: + // P2 point on S2 closest to P1 + // d distance betwee P1 and S2 + template < typename Kernel> + IGL_INLINE void point_segment_squared_distance( + const CGAL::Point_3 & P1, + const CGAL::Segment_3 & S2, + CGAL::Point_3 & P2, + typename Kernel::FT & d + ); + + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "point_segment_squared_distance.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp b/src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp new file mode 100644 index 000000000..7aef2efd1 --- /dev/null +++ b/src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_solid_signed_squared_distance.h" +#include "points_inside_component.h" +#include "point_mesh_squared_distance.h" +#include "../../list_to_matrix.h" +#include "../../slice_mask.h" +#include +#include + +template < + typename DerivedQ, + typename DerivedVB, + typename DerivedFB, + typename DerivedD> +IGL_INLINE void igl::copyleft::cgal::point_solid_signed_squared_distance( + const Eigen::PlainObjectBase & Q, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Eigen::PlainObjectBase & D) +{ + // compute unsigned distances + Eigen::VectorXi I; + DerivedVB C; + point_mesh_squared_distance(Q,VB,FB,D,I,C); + // Collect queries that have non-zero distance + Eigen::Array NZ = D.array()!=0; + // Compute sign for non-zero distance queries + DerivedQ QNZ; + slice_mask(Q,NZ,1,QNZ); + Eigen::Array DNZ; + igl::copyleft::cgal::points_inside_component(VB,FB,QNZ,DNZ); + // Apply sign to distances + DerivedD S = DerivedD::Zero(Q.rows(),1); + { + int k = 0; + for(int q = 0;q, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, -1, 1, 0, -1, 1> >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, 1, 0, -1, 1> >&); +#endif diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.h b/src/igl/copyleft/cgal/point_solid_signed_squared_distance.h new file mode 100644 index 000000000..845fad133 --- /dev/null +++ b/src/igl/copyleft/cgal/point_solid_signed_squared_distance.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POINT_SOLID_SIGNED_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_POINT_SOLID_SIGNED_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // POINT_SOLID_SIGNED_SQUARED_DISTANCE Given a set of points (Q) and the + // boundary mesh (VB,FB) of a solid (as defined in [Zhou et al. 2016], + // determine the signed squared distance for each point q in Q so that d(q,B) is + // negative if inside and positive if outside. + // + // Inputs: + // Q #Q by 3 list of query point positions + // VB #VB by 3 list of mesh vertex positions of B + // FB #FB by 3 list of mesh triangle indices into VB + // Outputs: + // D + template < + typename DerivedQ, + typename DerivedVB, + typename DerivedFB, + typename DerivedD> + IGL_INLINE void point_solid_signed_squared_distance( + const Eigen::PlainObjectBase & Q, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Eigen::PlainObjectBase & D); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_solid_signed_squared_distance.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.cpp b/src/igl/copyleft/cgal/point_triangle_squared_distance.cpp new file mode 100644 index 000000000..2ea29bc37 --- /dev/null +++ b/src/igl/copyleft/cgal/point_triangle_squared_distance.cpp @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_triangle_squared_distance.h" +#include +template < typename Kernel> +IGL_INLINE void point_triangle_squared_distance( + const CGAL::Point_3 & P1, + const CGAL::Triangle_3 & T2, + CGAL::Point_3 & P2, + typename Kernel::FT & d) +{ + assert(!T2.is_degenerate()); + if(T2.has_on(P1)) + { + P2 = P1; + d = 0; + return; + } + const auto proj_1 = T2.supporting_plane().projection(P2); + if(T2.has_on(proj_1)) + { + P2 = proj_1; + d = (proj_1-P1).squared_length(); + return; + } + // closest point must be on the boundary + bool first = true; + // loop over edges + for(int i=0;i<3;i++) + { + CGAL::Point_3 P2i; + typename Kernel::FT di; + const CGAL::Segment_3 si( T2.vertex(i+1), T2.vertex(i+2)); + point_segment_squared_distance(P1,si,P2i,di); + if(first || di < d) + { + first = false; + d = di; + P2 = P2i; + } + } +} diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.h b/src/igl/copyleft/cgal/point_triangle_squared_distance.h new file mode 100644 index 000000000..37b63adcb --- /dev/null +++ b/src/igl/copyleft/cgal/point_triangle_squared_distance.h @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POINT_TRIANGLE_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_POINT_TRIANGLE_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a point P1 and triangle T2 find the points on each of closest + // approach and the squared distance thereof. + // + // Inputs: + // P1 point + // T2 triangle + // Outputs: + // P2 point on T2 closest to P1 + // d distance betwee P1 and T2 + template < typename Kernel> + IGL_INLINE void point_triangle_squared_distance( + const CGAL::Point_3 & P1, + const CGAL::Triangle_3 & T2, + CGAL::Point_3 & P2, + typename Kernel::FT & d + ); + + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "point_triangle_squared_distance.cpp" +#endif + +#endif + + diff --git a/src/igl/copyleft/cgal/points_inside_component.cpp b/src/igl/copyleft/cgal/points_inside_component.cpp new file mode 100644 index 000000000..1558e130d --- /dev/null +++ b/src/igl/copyleft/cgal/points_inside_component.cpp @@ -0,0 +1,350 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "points_inside_component.h" +#include "../../LinSpaced.h" +#include "order_facets_around_edge.h" +#include "assign_scalar.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace igl { + namespace copyleft + { + namespace cgal { + namespace points_inside_component_helper { + typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; + typedef Kernel::Ray_3 Ray_3; + typedef Kernel::Point_3 Point_3; + typedef Kernel::Vector_3 Vector_3; + typedef Kernel::Triangle_3 Triangle; + typedef Kernel::Plane_3 Plane_3; + typedef std::vector::iterator Iterator; + typedef CGAL::AABB_triangle_primitive Primitive; + typedef CGAL::AABB_traits AABB_triangle_traits; + typedef CGAL::AABB_tree Tree; + + template + void extract_adj_faces( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const size_t s, const size_t d, + std::vector& adj_faces) { + const size_t num_faces = I.rows(); + for (size_t i=0; i + void extract_adj_vertices( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const size_t v, std::vector& adj_vertices) { + std::set unique_adj_vertices; + const size_t num_faces = I.rows(); + for (size_t i=0; i + bool determine_point_edge_orientation( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Point_3& query, size_t s, size_t d) { + // Algorithm: + // + // Order the adj faces around the edge (s,d) clockwise using + // query point as pivot. (i.e. The first face of the ordering + // is directly after the pivot point, and the last face is + // directly before the pivot.) + // + // The point is outside if the first and last faces of the + // ordering forms a convex angle. This check can be done + // without any construction by looking at the orientation of the + // faces. The angle is convex iff the first face contains (s,d) + // as an edge and the last face contains (d,s) as an edge. + // + // The point is inside if the first and last faces of the + // ordering forms a concave angle. That is the first face + // contains (d,s) as an edge and the last face contains (s,d) as + // an edge. + // + // In the special case of duplicated faces. I.e. multiple faces + // sharing the same 3 corners, but not necessarily the same + // orientation. The ordering will always rank faces containing + // edge (s,d) before faces containing edge (d,s). + // + // Therefore, if there are any duplicates of the first faces, + // the ordering will always choose the one with edge (s,d) if + // possible. The same for the last face. + // + // In the very degenerated case where the first and last face + // are duplicates, but with different orientations, it is + // equally valid to think the angle formed by them is either 0 + // or 360 degrees. By default, 0 degree is used, and thus the + // query point is outside. + + std::vector adj_faces; + extract_adj_faces(F, I, s, d, adj_faces); + const size_t num_adj_faces = adj_faces.size(); + assert(num_adj_faces > 0); + + DerivedV pivot_point(1, 3); + igl::copyleft::cgal::assign_scalar(query.x(), pivot_point(0, 0)); + igl::copyleft::cgal::assign_scalar(query.y(), pivot_point(0, 1)); + igl::copyleft::cgal::assign_scalar(query.z(), pivot_point(0, 2)); + Eigen::VectorXi order; + order_facets_around_edge(V, F, s, d, + adj_faces, pivot_point, order); + assert((size_t)order.size() == num_adj_faces); + if (adj_faces[order[0]] > 0 && + adj_faces[order[num_adj_faces-1] < 0]) { + return true; + } else if (adj_faces[order[0]] < 0 && + adj_faces[order[num_adj_faces-1] > 0]) { + return false; + } else { + throw "The input mesh does not represent a valid volume"; + } + throw "The input mesh does not represent a valid volume"; + return false; + } + + template + bool determine_point_vertex_orientation( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Point_3& query, size_t s) { + std::vector adj_vertices; + extract_adj_vertices(F, I, s, adj_vertices); + const size_t num_adj_vertices = adj_vertices.size(); + + std::vector adj_points; + for (size_t i=0; i bool{ + size_t positive=0; + size_t negative=0; + size_t coplanar=0; + for (const auto& point : adj_points) { + switch(separator.oriented_side(point)) { + case CGAL::ON_POSITIVE_SIDE: + positive++; + break; + case CGAL::ON_NEGATIVE_SIDE: + negative++; + break; + case CGAL::ON_ORIENTED_BOUNDARY: + coplanar++; + break; + default: + throw "Unknown plane-point orientation"; + } + } + auto query_orientation = separator.oriented_side(query); + bool r = + (positive == 0 && query_orientation == CGAL::POSITIVE) + || + (negative == 0 && query_orientation == CGAL::NEGATIVE); + return r; + }; + + size_t d = std::numeric_limits::max(); + Point_3 p(V(s,0), V(s,1), V(s,2)); + for (size_t i=0; i (size_t)V.rows()) { + // All adj faces are coplanar, use the first edge. + d = adj_vertices[0]; + } + return determine_point_edge_orientation(V, F, I, query, s, d); + } + + template + bool determine_point_face_orientation( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Point_3& query, size_t fid) { + // Algorithm: A point is on the inside of a face if the + // tetrahedron formed by them is negatively oriented. + + Eigen::Vector3i f = F.row(I(fid, 0)); + const Point_3 v0(V(f[0], 0), V(f[0], 1), V(f[0], 2)); + const Point_3 v1(V(f[1], 0), V(f[1], 1), V(f[1], 2)); + const Point_3 v2(V(f[2], 0), V(f[2], 1), V(f[2], 2)); + auto result = CGAL::orientation(v0, v1, v2, query); + if (result == CGAL::COPLANAR) { + throw "Cannot determine inside/outside because query point lies exactly on the input surface."; + } + return result == CGAL::NEGATIVE; + } + } + } + } +} + +template +IGL_INLINE void igl::copyleft::cgal::points_inside_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& inside) { + using namespace igl::copyleft::cgal::points_inside_component_helper; + if (F.rows() <= 0 || I.rows() <= 0) { + throw "Inside check cannot be done on empty facet component."; + } + + const size_t num_faces = I.rows(); + std::vector triangles; + for (size_t i=0; i ElementType{ + const Eigen::Vector3i f = F.row(I(fid, 0)); + const Point_3 p0(V(f[0], 0), V(f[0], 1), V(f[0], 2)); + const Point_3 p1(V(f[1], 0), V(f[1], 1), V(f[1], 2)); + const Point_3 p2(V(f[2], 0), V(f[2], 1), V(f[2], 2)); + + if (p == p0) { element_index = 0; return VERTEX; } + if (p == p1) { element_index = 1; return VERTEX; } + if (p == p2) { element_index = 2; return VERTEX; } + if (CGAL::collinear(p0, p1, p)) { element_index = 2; return EDGE; } + if (CGAL::collinear(p1, p2, p)) { element_index = 0; return EDGE; } + if (CGAL::collinear(p2, p0, p)) { element_index = 1; return EDGE; } + + element_index = 0; + return FACE; + }; + + const size_t num_queries = P.rows(); + inside.resize(num_queries, 1); + for (size_t i=0; i +IGL_INLINE void igl::copyleft::cgal::points_inside_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& inside) { + Eigen::VectorXi I = igl::LinSpaced(F.rows(), 0, F.rows()-1); + igl::copyleft::cgal::points_inside_component(V, F, I, P, inside); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::points_inside_component, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Array >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component< Eigen::Matrix, Eigen::Matrix< int, -1, -1, 0, -1, -1>, Eigen::Matrix< int, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix< int, -1, -1, 0, -1, -1> > ( Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component< Eigen::Matrix, Eigen::Matrix< int, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix< int, -1, -1, 0, -1, -1> > ( Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix >(Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::points_inside_component, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/points_inside_component.h b/src/igl/copyleft/cgal/points_inside_component.h new file mode 100644 index 000000000..f21354455 --- /dev/null +++ b/src/igl/copyleft/cgal/points_inside_component.h @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POINTS_INSIDE_COMPONENTS +#define IGL_COPYLEFT_CGAL_POINTS_INSIDE_COMPONENTS + +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal { + + // Determine if queries points P are inside of connected facet component + // (V, F, I), where I indicates a subset of facets that forms the + // component. + // + // Precondition: + // The input mesh must be a closed, self-intersection free, + // non-degenerated surface. Queries points must be either inside or + // outside of the mesh (i.e. not on the surface of the mesh). + // + // Inputs: + // V #V by 3 array of vertex positions. + // F #F by 3 array of triangles. + // I #I list of triangle indices to consider. + // P #P by 3 array of query points. + // + // Outputs: + // inside #P list of booleans that is true iff the corresponding + // query point is inside of the mesh. + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedP, + typename DerivedB> + IGL_INLINE void points_inside_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& inside); + + // Determine if query points P is inside of the mesh (V, F). + // See above for precondition and I/O specs. + template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedB> + IGL_INLINE void points_inside_component( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& inside); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "points_inside_component.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.cpp b/src/igl/copyleft/cgal/polyhedron_to_mesh.cpp new file mode 100644 index 000000000..b51e41b60 --- /dev/null +++ b/src/igl/copyleft/cgal/polyhedron_to_mesh.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "polyhedron_to_mesh.h" +#include + +template < + typename Polyhedron, + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::copyleft::cgal::polyhedron_to_mesh( + const Polyhedron & poly, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + using namespace std; + V.resize(poly.size_of_vertices(),3); + F.resize(poly.size_of_facets(),3); + typedef typename Polyhedron::Vertex_const_iterator Vertex_iterator; + std::map vertex_to_index; + { + size_t v = 0; + for( + typename Polyhedron::Vertex_const_iterator p = poly.vertices_begin(); + p != poly.vertices_end(); + p++) + { + V(v,0) = p->point().x(); + V(v,1) = p->point().y(); + V(v,2) = p->point().z(); + vertex_to_index[p] = v; + v++; + } + } + { + size_t f = 0; + for( + typename Polyhedron::Facet_const_iterator facet = poly.facets_begin(); + facet != poly.facets_end(); + ++facet) + { + typename Polyhedron::Halfedge_around_facet_const_circulator he = + facet->facet_begin(); + // Facets in polyhedral surfaces are at least triangles. + assert(CGAL::circulator_size(he) == 3 && "Facets should be triangles"); + size_t c = 0; + do { + //// This is stooopidly slow + // F(f,c) = std::distance(poly.vertices_begin(), he->vertex()); + F(f,c) = vertex_to_index[he->vertex()]; + c++; + } while ( ++he != facet->facet_begin()); + f++; + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#include +#include +#include +template void igl::copyleft::cgal::polyhedron_to_mesh >, Eigen::Matrix, Eigen::Matrix >(CGAL::Polyhedron_3 > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::polyhedron_to_mesh,CGAL::Polyhedron_items_with_id_3, CGAL::HalfedgeDS_default, std::allocator >, Eigen::Matrix, Eigen::Matrix >(CGAL::Polyhedron_3,CGAL::Polyhedron_items_with_id_3, CGAL::HalfedgeDS_default, std::allocator > const&,Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.h b/src/igl/copyleft/cgal/polyhedron_to_mesh.h new file mode 100644 index 000000000..3f8c66a88 --- /dev/null +++ b/src/igl/copyleft/cgal/polyhedron_to_mesh.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_POLYHEDRON_TO_MESH_H +#define IGL_COPYLEFT_CGAL_POLYHEDRON_TO_MESH_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Convert a CGAL Polyhedron to a mesh (V,F) + // + // Templates: + // Polyhedron CGAL Polyhedron type (e.g. Polyhedron_3) + // Inputs: + // poly cgal polyhedron + // Outputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + template < + typename Polyhedron, + typename DerivedV, + typename DerivedF> + IGL_INLINE void polyhedron_to_mesh( + const Polyhedron & poly, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "polyhedron_to_mesh.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/projected_cdt.cpp b/src/igl/copyleft/cgal/projected_cdt.cpp new file mode 100644 index 000000000..f34352c66 --- /dev/null +++ b/src/igl/copyleft/cgal/projected_cdt.cpp @@ -0,0 +1,82 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "projected_cdt.h" +#include "insert_into_cdt.h" +#include "assign_scalar.h" +#include "../../list_to_matrix.h" +template +IGL_INLINE void igl::copyleft::cgal::projected_cdt( + const std::vector & objects, + const CGAL::Plane_3 & P, + std::vector >& vertices, + std::vector >& faces) +{ + typedef CGAL::Triangulation_vertex_base_2 TVB_2; + typedef CGAL::Constrained_triangulation_face_base_2 CTFB_2; + typedef CGAL::Triangulation_data_structure_2 TDS_2; + typedef CGAL::Exact_intersections_tag Itag; + typedef CGAL::Constrained_Delaunay_triangulation_2 CDT_2; + typedef CGAL::Constrained_triangulation_plus_2 CDT_plus_2; + CDT_plus_2 cdt; + for(const auto & obj : objects) insert_into_cdt(obj,P,cdt); + // Read off vertices of the cdt, remembering index + std::map v2i; + size_t count=0; + for ( + auto itr = cdt.finite_vertices_begin(); + itr != cdt.finite_vertices_end(); + itr++) + { + vertices.push_back(P.to_3d(itr->point())); + v2i[itr] = count; + count++; + } + // Read off faces and store index triples + for ( + auto itr = cdt.finite_faces_begin(); + itr != cdt.finite_faces_end(); + itr++) + { + faces.push_back( + { v2i[itr->vertex(0)], v2i[itr->vertex(1)], v2i[itr->vertex(2)] }); + } +} + +template < typename Kernel, typename DerivedV, typename DerivedF> +IGL_INLINE void igl::copyleft::cgal::projected_cdt( + const std::vector & objects, + const CGAL::Plane_3 & P, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + std::vector > vertices; + std::vector > faces; + projected_cdt(objects,P,vertices,faces); + V.resize(vertices.size(),3); + for(int v = 0;v(std::vector > const&, CGAL::Plane_3 const&, std::vector, std::allocator > >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::projected_cdt(std::vector > const&, CGAL::Plane_3 const&, std::vector, std::allocator > >&, std::vector >, std::allocator > > >&); +#include +#ifdef WIN32 +template void igl::copyleft::cgal::projected_cdt(class std::vector> const &, class CGAL::Plane_3 const &, class std::vector, class std::allocator>> &, class std::vector>, class std::allocator>>> &); +template void igl::copyleft::cgal::projected_cdt(class std::vector> const &, class CGAL::Plane_3 const &, class std::vector, class std::allocator>> &, class std::vector>, class std::allocator>>> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/projected_cdt.h b/src/igl/copyleft/cgal/projected_cdt.h new file mode 100644 index 000000000..a161cc450 --- /dev/null +++ b/src/igl/copyleft/cgal/projected_cdt.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_PROJECTED_CDT_H +#define IGL_COPYLEFT_CGAL_PROJECTED_CDT_H +#include "../../igl_inline.h" +#include +#include +#include +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a list of objects (e.g., resulting from intersecting a triangle + // with many other triangles), construct a constrained Delaunay + // triangulation on a given plane (P), by inersting constraints for each + // object projected onto that plane. + // + // Inputs: + // objects list of objects. This should lie on the given plane (P), + // otherwise they are added to the cdt _after_ their non-trivial + // projection + // P plane upon which all objects lie and upon which the CDT is + // conducted + // Outputs: + // vertices list of vertices of the CDT mesh _back on the 3D plane_ + // faces list of list of triangle indices into vertices + // + template + IGL_INLINE void projected_cdt( + const std::vector & objects, + const CGAL::Plane_3 & P, + std::vector >& vertices, + std::vector >& faces); + // Outputs: + // V #V by 3 list of vertices of the CDT mesh _back on the 3D plane_, + // **cast** from the number type of Kernel to the number type of + // DerivedV + // F #F by 3 list of triangle indices into V + template < typename Kernel, typename DerivedV, typename DerivedF> + IGL_INLINE void projected_cdt( + const std::vector & objects, + const CGAL::Plane_3 & P, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "projected_cdt.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/projected_delaunay.cpp b/src/igl/copyleft/cgal/projected_delaunay.cpp new file mode 100644 index 000000000..52ea49274 --- /dev/null +++ b/src/igl/copyleft/cgal/projected_delaunay.cpp @@ -0,0 +1,106 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "projected_delaunay.h" +#include "../../REDRUM.h" +#include +#include + +#if CGAL_VERSION_NR < 1040611000 +# warning "CGAL Version < 4.6.1 may result in crashes. Please upgrade CGAL" +#endif + +template +IGL_INLINE void igl::copyleft::cgal::projected_delaunay( + const CGAL::Triangle_3 & A, + const std::vector & A_objects_3, + CGAL::Constrained_triangulation_plus_2< + CGAL::Constrained_Delaunay_triangulation_2< + Kernel, + CGAL::Triangulation_data_structure_2< + CGAL::Triangulation_vertex_base_2, + CGAL::Constrained_triangulation_face_base_2 >, + CGAL::Exact_intersections_tag> > & cdt) +{ + using namespace std; + // 3D Primitives + typedef CGAL::Point_3 Point_3; + typedef CGAL::Segment_3 Segment_3; + typedef CGAL::Triangle_3 Triangle_3; + typedef CGAL::Plane_3 Plane_3; + //typedef CGAL::Tetrahedron_3 Tetrahedron_3; + typedef CGAL::Point_2 Point_2; + //typedef CGAL::Segment_2 Segment_2; + //typedef CGAL::Triangle_2 Triangle_2; + typedef CGAL::Triangulation_vertex_base_2 TVB_2; + typedef CGAL::Constrained_triangulation_face_base_2 CTFB_2; + typedef CGAL::Triangulation_data_structure_2 TDS_2; + typedef CGAL::Exact_intersections_tag Itag; + typedef CGAL::Constrained_Delaunay_triangulation_2 + CDT_2; + typedef CGAL::Constrained_triangulation_plus_2 CDT_plus_2; + + // http://www.cgal.org/Manual/3.2/doc_html/cgal_manual/Triangulation_2/Chapter_main.html#Section_2D_Triangulations_Constrained_Plus + // Plane of triangle A + Plane_3 P(A.vertex(0),A.vertex(1),A.vertex(2)); + // Insert triangle into vertices + typename CDT_plus_2::Vertex_handle corners[3]; + typedef size_t Index; + for(Index i = 0;i<3;i++) + { + const Point_3 & p3 = A.vertex(i); + const Point_2 & p2 = P.to_2d(p3); + typename CDT_plus_2::Vertex_handle corner = cdt.insert(p2); + corners[i] = corner; + } + // Insert triangle edges as constraints + for(Index i = 0;i<3;i++) + { + cdt.insert_constraint( corners[(i+1)%3], corners[(i+2)%3]); + } + // Insert constraints for intersection objects + for( const auto & obj : A_objects_3) + { + if(const Segment_3 *iseg = CGAL::object_cast(&obj)) + { + // Add segment constraint + cdt.insert_constraint(P.to_2d(iseg->vertex(0)),P.to_2d(iseg->vertex(1))); + }else if(const Point_3 *ipoint = CGAL::object_cast(&obj)) + { + // Add point + cdt.insert(P.to_2d(*ipoint)); + } else if(const Triangle_3 *itri = CGAL::object_cast(&obj)) + { + // Add 3 segment constraints + cdt.insert_constraint(P.to_2d(itri->vertex(0)),P.to_2d(itri->vertex(1))); + cdt.insert_constraint(P.to_2d(itri->vertex(1)),P.to_2d(itri->vertex(2))); + cdt.insert_constraint(P.to_2d(itri->vertex(2)),P.to_2d(itri->vertex(0))); + } else if(const std::vector *polyp = + CGAL::object_cast< std::vector >(&obj)) + { + //cerr< & poly = *polyp; + const Index m = poly.size(); + assert(m>=2); + for(Index p = 0;p(CGAL::Triangle_3 const&, std::vector > const&, CGAL::Constrained_triangulation_plus_2 >, CGAL::Constrained_triangulation_face_base_2 > > >, CGAL::Exact_intersections_tag> >&); +template void igl::copyleft::cgal::projected_delaunay(CGAL::Triangle_3 const&, std::vector > const&, CGAL::Constrained_triangulation_plus_2 >, CGAL::Constrained_triangulation_face_base_2 > > >, CGAL::Exact_intersections_tag> >&); +#endif diff --git a/src/igl/copyleft/cgal/projected_delaunay.h b/src/igl/copyleft/cgal/projected_delaunay.h new file mode 100644 index 000000000..ee72305cd --- /dev/null +++ b/src/igl/copyleft/cgal/projected_delaunay.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_PROJECTED_DELAUNAY_H +#define IGL_COPYLEFT_CGAL_PROJECTED_DELAUNAY_H +#include "../../igl_inline.h" +#include "CGAL_includes.hpp" +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Compute 2D delaunay triangulation of a given 3d triangle and a list of + // intersection objects (points,segments,triangles). CGAL uses an affine + // projection rather than an isometric projection, so we're not guaranteed + // that the 2D delaunay triangulation here will be a delaunay triangulation + // in 3D. + // + // Inputs: + // A triangle in 3D + // A_objects_3 updated list of intersection objects for A + // Outputs: + // cdt Contrained delaunay triangulation in projected 2D plane + template + IGL_INLINE void projected_delaunay( + const CGAL::Triangle_3 & A, + const std::vector & A_objects_3, + CGAL::Constrained_triangulation_plus_2< + CGAL::Constrained_Delaunay_triangulation_2< + Kernel, + CGAL::Triangulation_data_structure_2< + CGAL::Triangulation_vertex_base_2, + CGAL::Constrained_triangulation_face_base_2 >, + CGAL::Exact_intersections_tag> > & cdt); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "projected_delaunay.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.cpp b/src/igl/copyleft/cgal/propagate_winding_numbers.cpp new file mode 100644 index 000000000..7de9d98d2 --- /dev/null +++ b/src/igl/copyleft/cgal/propagate_winding_numbers.cpp @@ -0,0 +1,324 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "propagate_winding_numbers.h" +#include "../../extract_manifold_patches.h" +#include "../../extract_non_manifold_edge_curves.h" +#include "../../facet_components.h" +#include "../../unique_edge_map.h" +#include "../../piecewise_constant_winding_number.h" +#include "../../writeOBJ.h" +#include "../../writePLY.h" +#include "../../get_seconds.h" +#include "../../LinSpaced.h" +#include "order_facets_around_edge.h" +#include "outer_facet.h" +#include "closest_facet.h" +#include "assign.h" +#include "extract_cells.h" +#include "cell_adjacency.h" + +#include +#include +#include +#include +#include + +//#define PROPAGATE_WINDING_NUMBER_TIMING + +template< + typename DerivedV, + typename DerivedF, + typename DerivedL, + typename DerivedW> +IGL_INLINE bool igl::copyleft::cgal::propagate_winding_numbers( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& labels, + Eigen::PlainObjectBase& W) +{ +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void { + std::cout << "propagate_winding_num." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#endif + + Eigen::MatrixXi E, uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + + Eigen::VectorXi P; + const size_t num_patches = igl::extract_manifold_patches(F, EMAP, uE2E, P); + + DerivedW per_patch_cells; + const size_t num_cells = + igl::copyleft::cgal::extract_cells( + V, F, P, E, uE, uE2E, EMAP, per_patch_cells); +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + log_time("cell_extraction"); +#endif + + return propagate_winding_numbers(V, F, + uE, uE2E, + num_patches, P, + num_cells, per_patch_cells, + labels, W); +} + + +template< + typename DerivedV, + typename DerivedF, + typename DeriveduE, + typename uE2EType, + typename DerivedP, + typename DerivedC, + typename DerivedL, + typename DerivedW> +IGL_INLINE bool igl::copyleft::cgal::propagate_winding_numbers( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const size_t num_patches, + const Eigen::PlainObjectBase& P, + const size_t num_cells, + const Eigen::PlainObjectBase& C, + const Eigen::PlainObjectBase& labels, + Eigen::PlainObjectBase& W) +{ +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void { + std::cout << "propagate_winding_num." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#endif + + bool valid = true; + // https://github.com/libigl/libigl/issues/674 + if (!igl::piecewise_constant_winding_number(F, uE, uE2E)) + { + assert(false && "Input mesh is not PWN"); + std::cerr << "Input mesh is not PWN!" << std::endl; + valid = false; + } + + const size_t num_faces = F.rows(); + typedef std::tuple CellConnection; + std::vector > cell_adj; + igl::copyleft::cgal::cell_adjacency(C, num_cells, cell_adj); +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + log_time("cell_connectivity"); +#endif + + auto save_cell = [&](const std::string& filename, size_t cell_id) -> void{ + std::vector faces; + for (size_t i=0; i std::list { + std::list path; + path.push_back(idx); + while ((size_t)parents[path.back()] != path.back()) { + path.push_back(parents[path.back()]); + } + return path; + }; + for (size_t i=0; i Q; + Q.push(i); + parents[i] = i; + while (!Q.empty()) { + size_t curr_idx = Q.front(); + Q.pop(); + int curr_label = cell_labels[curr_idx]; + for (const auto& neighbor : cell_adj[curr_idx]) { + if (cell_labels[std::get<0>(neighbor)] == 0) + { + cell_labels[std::get<0>(neighbor)] = curr_label * -1; + Q.push(std::get<0>(neighbor)); + parents[std::get<0>(neighbor)] = curr_idx; + } else + { + if (cell_labels[std::get<0>(neighbor)] != curr_label * -1) + { + std::cerr << "Odd cell cycle detected!" << std::endl; + auto path = trace_parents(curr_idx); + path.reverse(); + auto path2 = trace_parents(std::get<0>(neighbor)); + path.insert(path.end(), path2.begin(), path2.end()); + for (auto cell_id : path) + { + std::cout << cell_id << " "; + std::stringstream filename; + filename << "cell_" << cell_id << ".ply"; + save_cell(filename.str(), cell_id); + } + std::cout << std::endl; + valid = false; + } + // Do not fail when odd cycle is detected because the resulting + // integer winding number field, although inconsistent, may still + // be used if the problem region is local and embedded within a + // valid volume. + //assert(cell_labels[std::get<0>(neighbor)] == curr_label * -1); + } + } + } + } + } +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + log_time("odd_cycle_check"); +#endif + } +#endif + + size_t outer_facet; + bool flipped; + Eigen::VectorXi I = igl::LinSpaced(num_faces, 0, num_faces-1); + igl::copyleft::cgal::outer_facet(V, F, I, outer_facet, flipped); +#ifdef PROPAGATE_WINDING_NUMBER_TIMING + log_time("outer_facet"); +#endif + + const size_t outer_patch = P[outer_facet]; + const size_t infinity_cell = C(outer_patch, flipped?1:0); + + Eigen::VectorXi patch_labels(num_patches); + const int INVALID = std::numeric_limits::max(); + patch_labels.setConstant(INVALID); + for (size_t i=0; i Q; + Q.push(infinity_cell); + while (!Q.empty()) { + size_t curr_cell = Q.front(); + Q.pop(); + for (const auto& neighbor : cell_adj[curr_cell]) { + size_t neighbor_cell, patch_idx; + bool direction; + std::tie(neighbor_cell, direction, patch_idx) = neighbor; + if ((per_cell_W.row(neighbor_cell).array() == INVALID).any()) { + per_cell_W.row(neighbor_cell) = per_cell_W.row(curr_cell); + for (size_t i=0; i, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, unsigned long, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, unsigned long, Eigen::PlainObjectBase > const&, unsigned long, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::copyleft::cgal::propagate_winding_numbers, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, unsigned long, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, unsigned long, Eigen::PlainObjectBase > const&, unsigned long, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template bool igl::copyleft::cgal::propagate_winding_numbers, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template bool igl::copyleft::cgal::propagate_winding_numbers, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, unsigned __int64, class Eigen::PlainObjectBase> const &, unsigned __int64, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +template bool igl::copyleft::cgal::propagate_winding_numbers, -1, -1, 1, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix>(class Eigen::PlainObjectBase, -1, -1, 1, -1, -1>> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, unsigned __int64, class Eigen::PlainObjectBase> const &, unsigned __int64, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.h b/src/igl/copyleft/cgal/propagate_winding_numbers.h new file mode 100644 index 000000000..77d8e7666 --- /dev/null +++ b/src/igl/copyleft/cgal/propagate_winding_numbers.h @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_PROPAGATE_WINDING_NUMBERS_H +#define IGL_COPYLEFT_CGAL_PROPAGATE_WINDING_NUMBERS_H +#include "../../igl_inline.h" +#include +#include + +// The following methods compute the winding number on each side of each facet +// or patch of a 3D mesh. The input mesh is valid if it splits the ambient +// space, R^3, into subspaces with constant integer winding numbers. That is +// the input mesh should be closed and for each directed edge the number of +// clockwise facing facets should equal the number of counterclockwise facing +// facets. + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // TODO: This shouldn't need to be in igl::copyleft::cgal, it should + // instead take as input an index of the ambient cell and the winding + // number vector there. + // + // Compute winding number on each side of the face. The input mesh + // could contain multiple connected components. The input mesh must + // represent the boundary of a valid 3D volume, which means it is + // closed, consistently oriented and induces integer winding numbers. + // + // Inputs: + // V #V by 3 list of vertex positions. + // F #F by 3 list of triangle indices into V. + // labels #F list of facet labels ranging from 0 to k-1. + // Output: + // W #F by k*2 list of winding numbers. ``W(i,j*2)`` is the winding + // number on the positive side of facet ``i`` with respect to the + // facets labeled ``j``. Similarly, ``W(i,j*2+1)`` is the winding + // number on the negative side of facet ``i`` with respect to the + // facets labeled ``j``. + // Returns true iff the input induces a piecewise-constant winding number + // field. + template< + typename DerivedV, + typename DerivedF, + typename DerivedL, + typename DerivedW> + IGL_INLINE bool propagate_winding_numbers( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& labels, + Eigen::PlainObjectBase& W); + + // Inputs: + // V #V by 3 list of vertex positions. + // F #F by 3 list of triangle indices into V. + // uE #uE by 2 list of vertex_indices, represents undirected edges. + // uE2E #uE list of lists that maps uE to E. (a one-to-many map) + // num_patches number of patches + // P #F list of patch ids. + // num_cells number of cells + // C #P by 2 list of cell ids on each side of each patch. + // labels #F list of facet labels ranging from 0 to k-1. + // Output: + // W #F by k*2 list of winding numbers. ``W(i,j*2)`` is the winding + // number on the positive side of facet ``i`` with respect to the + // facets labeled ``j``. Similarly, ``W(i,j*2+1)`` is the winding + // number on the negative side of facet ``i`` with respect to the + // facets labeled ``j``. + template< + typename DerivedV, + typename DerivedF, + typename DeriveduE, + typename uE2EType, + typename DerivedP, + typename DerivedC, + typename DerivedL, + typename DerivedW> + IGL_INLINE bool propagate_winding_numbers( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& uE, + const std::vector >& uE2E, + const size_t num_patches, + const Eigen::PlainObjectBase& P, + const size_t num_cells, + const Eigen::PlainObjectBase& C, + const Eigen::PlainObjectBase& labels, + Eigen::PlainObjectBase& W); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "propagate_winding_numbers.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.cpp b/src/igl/copyleft/cgal/read_triangle_mesh.cpp new file mode 100644 index 000000000..24ebbb127 --- /dev/null +++ b/src/igl/copyleft/cgal/read_triangle_mesh.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "read_triangle_mesh.h" +#include "assign.h" +#include "../../read_triangle_mesh.h" + +template +IGL_INLINE bool igl::copyleft::cgal::read_triangle_mesh( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + Eigen::MatrixXd Vd; + bool ret = igl::read_triangle_mesh(str,Vd,F); + if(ret) + { + assign(Vd,V); + } + return ret; +} diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.h b/src/igl/copyleft/cgal/read_triangle_mesh.h new file mode 100644 index 000000000..c1dae2a22 --- /dev/null +++ b/src/igl/copyleft/cgal/read_triangle_mesh.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_READ_TRIANGLE_MESH_H +#define IGL_COPYLEFT_CGAL_READ_TRIANGLE_MESH_H +#include "../../igl_inline.h" + +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Simple wrapper, reads floating point precision but assigns to + // DerivedV::Scalar which may be a CGAL type + // + // Inputs: + // str path to file + // Outputs: + // V eigen double matrix #V by 3 + // F eigen int matrix #F by 3 + // Returns true iff success + template + IGL_INLINE bool read_triangle_mesh( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "read_triangle_mesh.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp b/src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp new file mode 100644 index 000000000..993e009ec --- /dev/null +++ b/src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp @@ -0,0 +1,117 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// + +#include "relabel_small_immersed_cells.h" +#include "../../centroid.h" +#include "assign.h" +#include "cell_adjacency.h" + +#include + +template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedC, + typename FT, + typename DerivedW> +IGL_INLINE void igl::copyleft::cgal::relabel_small_immersed_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const size_t num_patches, + const Eigen::PlainObjectBase& P, + const size_t num_cells, + const Eigen::PlainObjectBase& C, + const FT vol_threashold, + Eigen::PlainObjectBase& W) +{ + const size_t num_vertices = V.rows(); + const size_t num_faces = F.rows(); + typedef std::tuple CellConnection; + std::vector > cell_adj; + igl::copyleft::cgal::cell_adjacency(C, num_cells, cell_adj); + + Eigen::MatrixXd VV; + assign(V,VV); + + auto compute_cell_volume = [&](size_t cell_id) { + std::vector is_involved(num_patches, 0); + for (size_t i=0; i involved_positive_faces; + std::vector involved_negative_faces; + for (size_t i=0; i cell_volumes(num_cells); + for (size_t i=0; i cell_values(num_cells); + for (size_t i=0; i= vol_threashold) continue; + std::set neighbor_values; + const auto neighbors = cell_adj[i]; + for (const auto& entry : neighbors) { + const auto& j = std::get<0>(entry); + neighbor_values.insert(cell_values[j]); + } + // If cell i is immersed, assign its value to be the immersed value. + if (neighbor_values.size() == 1) { + cell_values[i] = *neighbor_values.begin(); + } + } + + for (size_t i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_RELABEL_SMALL_IMMERSED_CELLS +#define IGL_RELABEL_SMALL_IMMERSED_CELLS + +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Inputs: + // V #V by 3 list of vertex positions. + // F #F by 3 list of triangle indices into V. + // num_patches number of patches + // P #F list of patch ids. + // num_cells number of cells + // C #P by 2 list of cell ids on each side of each patch. + // vol_threshold Volume threshold, cells smaller than this + // and is completely immersed will be relabeled. + // + // In/Output: + // W #F by 2 cell labels. W(i,0) is the label on the positive side of + // face i, W(i,1) is the label on the negative side of face i. W + // will be modified in place by this method. + template< + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedC, + typename FT, + typename DerivedW> + IGL_INLINE void relabel_small_immersed_cells( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const size_t num_patches, + const Eigen::PlainObjectBase& P, + const size_t num_cells, + const Eigen::PlainObjectBase& C, + const FT vol_threashold, + Eigen::PlainObjectBase& W); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "relabel_small_immersed_cells.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/remesh_intersections.cpp b/src/igl/copyleft/cgal/remesh_intersections.cpp new file mode 100644 index 000000000..56a715c23 --- /dev/null +++ b/src/igl/copyleft/cgal/remesh_intersections.cpp @@ -0,0 +1,533 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "remesh_intersections.h" +#include "assign_scalar.h" +#include "projected_cdt.h" +#include "../../get_seconds.h" +#include "../../parallel_for.h" +#include "../../LinSpaced.h" +#include "../../unique_rows.h" + +#include +#include +#include +#include +#include + +//#define REMESH_INTERSECTIONS_TIMING + +template < + typename DerivedV, + typename DerivedF, + typename Kernel, + typename DerivedVV, + typename DerivedFF, + typename DerivedJ, + typename DerivedIM> +IGL_INLINE void igl::copyleft::cgal::remesh_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const std::vector > & T, + const std::map< + typename DerivedF::Index, + std::vector< + std::pair > > & offending, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM) +{ + // by default, no stitching + igl::copyleft::cgal::remesh_intersections(V,F,T,offending,false,VV,FF,J,IM); +} + +template < + typename DerivedV, + typename DerivedF, + typename Kernel, + typename DerivedVV, + typename DerivedFF, + typename DerivedJ, + typename DerivedIM> +IGL_INLINE void igl::copyleft::cgal::remesh_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const std::vector > & T, + const std::map< + typename DerivedF::Index, + std::vector< + std::pair > > & offending, + bool stitch_all, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM) +{ + +#ifdef REMESH_INTERSECTIONS_TIMING + const auto & tictoc = []() -> double + { + static double t_start = igl::get_seconds(); + double diff = igl::get_seconds()-t_start; + t_start += diff; + return diff; + }; + const auto log_time = [&](const std::string& label) -> void { + std::cout << "remesh_intersections." << label << ": " + << tictoc() << std::endl; + }; + tictoc(); +#endif + + typedef CGAL::Point_3 Point_3; + typedef CGAL::Segment_3 Segment_3; + typedef CGAL::Plane_3 Plane_3; + typedef CGAL::Triangulation_vertex_base_2 TVB_2; + typedef CGAL::Constrained_triangulation_face_base_2 CTFB_2; + typedef CGAL::Triangulation_data_structure_2 TDS_2; + typedef CGAL::Exact_intersections_tag Itag; + typedef CGAL::Constrained_Delaunay_triangulation_2 + CDT_2; + typedef CGAL::Constrained_triangulation_plus_2 CDT_plus_2; + + typedef typename DerivedF::Index Index; + typedef std::pair Edge; + struct EdgeHash { + size_t operator()(const Edge& e) const { + return (e.first * 805306457) ^ (e.second * 201326611); + } + }; + typedef std::unordered_map, EdgeHash > EdgeMap; + + const size_t num_faces = F.rows(); + const size_t num_base_vertices = V.rows(); + assert(num_faces == T.size()); + + std::vector is_offending(num_faces, false); + for (const auto itr : offending) + { + const auto& fid = itr.first; + is_offending[fid] = true; + } + + // Cluster overlaps so that co-planar clusters are resolved only once + std::unordered_map > intersecting_and_coplanar; + for (const auto itr : offending) + { + const auto& fi = itr.first; + const auto P = T[fi].supporting_plane(); + assert(!P.is_degenerate()); + for (const auto jtr : itr.second) + { + const auto& fj = jtr.first; + const auto& tj = T[fj]; + if (P.has_on(tj[0]) && P.has_on(tj[1]) && P.has_on(tj[2])) + { + auto loc = intersecting_and_coplanar.find(fi); + if (loc == intersecting_and_coplanar.end()) + { + intersecting_and_coplanar[fi] = {fj}; + } else + { + loc->second.push_back(fj); + } + } + } + } +#ifdef REMESH_INTERSECTIONS_TIMING + log_time("overlap_analysis"); +#endif + + std::vector > resolved_faces; + std::vector source_faces; + std::vector new_vertices; + EdgeMap edge_vertices; + // face_vertices: Given a face Index, find vertices inside the face + std::unordered_map> face_vertices; + + // Run constraint Delaunay triangulation on the plane. + // + // Inputs: + // P plane to triangulate upone + // involved_faces #F list of indices into triangle of involved faces + // Outputs: + // vertices #V list of vertex positions of output triangulation + // faces #F list of face indices into vertices of output triangulation + // + auto delaunay_triangulation = [&offending, &T]( + const Plane_3& P, + const std::vector& involved_faces, + std::vector& vertices, + std::vector >& faces) -> void + { + std::vector objects; + + CDT_plus_2 cdt; + // insert each face into a common cdt + for (const auto& fid : involved_faces) + { + const auto itr = offending.find(fid); + const auto& triangle = T[fid]; + objects.push_back(CGAL::make_object(triangle)); + if (itr == offending.end()) + { + continue; + } + for (const auto& index_obj : itr->second) + { + //const auto& ofid = index_obj.first; + const auto& obj = index_obj.second; + objects.push_back(obj); + } + } + projected_cdt(objects,P,vertices,faces); + }; + + // Given p on triangle indexed by ori_f, add point to list of vertices return index of p. + // + // Input: + // p point to search for + // ori_f index of triangle p is corner of + // Returns global index of vertex (dependent on whether stitch_all flag is + // set) + // + auto find_or_append_point = [&]( + const Point_3& p, + const size_t ori_f) -> Index + { + if (stitch_all) + { + // No need to check if p shared by multiple triangles because all shared + // vertices would be merged later on. + const size_t index = num_base_vertices + new_vertices.size(); + new_vertices.push_back(p); + return index; + } else + { + // Stitching triangles according to input connectivity. + // This step is potentially costly. + const auto& triangle = T[ori_f]; + const auto& f = F.row(ori_f).eval(); + + // Check if p is one of the triangle corners. + for (size_t i=0; i<3; i++) + { + if (p == triangle[i]) return f[i]; + } + + // Check if p is on one of the edges. + for (size_t i=0; i<3; i++) { + const Point_3 curr_corner = triangle[i]; + const Point_3 next_corner = triangle[(i+1)%3]; + const Segment_3 edge(curr_corner, next_corner); + if (edge.has_on(p)) { + const Index curr = f[i]; + const Index next = f[(i+1)%3]; + Edge key; + key.first = currsecond) { + if (p == new_vertices[vid - num_base_vertices]) { + return vid; + } + } + const size_t index = num_base_vertices + new_vertices.size(); + new_vertices.push_back(p); + itr->second.push_back(index); + return index; + } + } + } + + // p must be in the middle of the triangle. + auto & existing_face_vertices = face_vertices[ori_f]; + for(const auto vid : existing_face_vertices) { + if (p == new_vertices[vid - num_base_vertices]) { + return vid; + } + } + const size_t index = num_base_vertices + new_vertices.size(); + new_vertices.push_back(p); + existing_face_vertices.push_back(index); + return index; + } + }; + + // Determine the vertex indices for each corner of each output triangle. + // + // Inputs: + // vertices #V list of vertices of cdt + // faces #F list of list of face indices into vertices of cdt + // involved_faces list of involved faces on the plane of cdt + // Side effects: + // - add faces to resolved_faces + // - add corresponding original face to source_faces + // - + auto post_triangulation_process = [&]( + const std::vector& vertices, + const std::vector >& faces, + const std::vector& involved_faces) -> void + { + assert(involved_faces.size() > 0); + // for all faces of the cdt + for (const auto& f : faces) + { + const Point_3& v0 = vertices[f[0]]; + const Point_3& v1 = vertices[f[1]]; + const Point_3& v2 = vertices[f[2]]; + Point_3 center( + (v0[0] + v1[0] + v2[0]) / 3.0, + (v0[1] + v1[1] + v2[1]) / 3.0, + (v0[2] + v1[2] + v2[2]) / 3.0); + if (involved_faces.size() == 1) + { + // If only there is only one involved face, all sub-triangles must + // belong to it and have the correct orientation. + const auto& ori_f = involved_faces[0]; + std::vector corners(3); + corners[0] = find_or_append_point(v0, ori_f); + corners[1] = find_or_append_point(v1, ori_f); + corners[2] = find_or_append_point(v2, ori_f); + resolved_faces.emplace_back(corners); + source_faces.push_back(ori_f); + } else + { + for (const auto& ori_f : involved_faces) + { + const auto& triangle = T[ori_f]; + const Plane_3 P = triangle.supporting_plane(); + if (triangle.has_on(center)) { + std::vector corners(3); + corners[0] = find_or_append_point(v0, ori_f); + corners[1] = find_or_append_point(v1, ori_f); + corners[2] = find_or_append_point(v2, ori_f); + if (CGAL::orientation( + P.to_2d(v0), P.to_2d(v1), P.to_2d(v2)) + == CGAL::RIGHT_TURN) { + std::swap(corners[0], corners[1]); + } + resolved_faces.emplace_back(corners); + source_faces.push_back(ori_f); + } + } + } + } + }; + + // Process un-touched faces. + for (size_t i=0; i processed(num_faces, false); + std::vector > > cdt_inputs; + for (const auto itr : offending) + { + const auto fid = itr.first; + if (processed[fid]) continue; + processed[fid] = true; + + const auto loc = intersecting_and_coplanar.find(fid); + std::vector involved_faces; + if (loc == intersecting_and_coplanar.end()) + { + involved_faces.push_back(fid); + } else + { + std::queue Q; + Q.push(fid); + while (!Q.empty()) + { + const auto index = Q.front(); + involved_faces.push_back(index); + Q.pop(); + + const auto overlapping_faces = intersecting_and_coplanar.find(index); + assert(overlapping_faces != intersecting_and_coplanar.end()); + + for (const auto other_index : overlapping_faces->second) + { + if (processed[other_index]) continue; + processed[other_index] = true; + Q.push(other_index); + } + } + } + + Plane_3 P = T[fid].supporting_plane(); + cdt_inputs.emplace_back(P, involved_faces); + } +#ifdef REMESH_INTERSECTIONS_TIMING + log_time("preprocess"); +#endif + + const size_t num_cdts = cdt_inputs.size(); + std::vector > cdt_vertices(num_cdts); + std::vector > > cdt_faces(num_cdts); + + //// Not clear whether this is safe because of reference counting on Point_3 + //// objects... + //// + //// I tried it and got random segfaults (via MATLAB). Seems this is not + //// safe. + //igl::parallel_for(num_cdts,[&](int i) + for (size_t i=0; i + >(unique_vv.rows(), 0, unique_vv.rows()-1); + }else + { + // Vertices with the same coordinates would be represented by one vertex. + // The IM value of a vertex is the index of the representative vertex. + for (Index v=0; v<(Index)VV_size; v++) { + IM(v) = unique_to_vv[vv_to_unique[v]]; + } + } + +#ifdef REMESH_INTERSECTIONS_TIMING + log_time("store_results"); +#endif +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_intersections, -1, -1, 1, -1, -1>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, -1, 1, -1, -1>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, 8, 3, 0, 8, 3>, Eigen::Matrix, CGAL::Epick, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, 8, 3, 0, 8, 3>, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_intersections, Eigen::Matrix, CGAL::Epeck, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector, std::allocator > > const&, std::map::Index, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > >, std::less::Index>, std::allocator::Index const, std::vector::Index, CGAL::Object>, std::allocator::Index, CGAL::Object> > > > > > const&, bool, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::copyleft::cgal::remesh_intersections, class Eigen::Matrix, class CGAL::Epeck, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, class std::vector, class std::allocator>> const &, class std::map<__int64, class std::vector, class std::allocator>>, struct std::less<__int64>, class std::allocator, class std::allocator>>>>> const &, bool, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::remesh_intersections, class Eigen::Matrix, class CGAL::Epick, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, class std::vector, class std::allocator>> const &, class std::map<__int64, class std::vector, class std::allocator>>, struct std::less<__int64>, class std::allocator, class std::allocator>>>>> const &, bool, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, class Eigen::Matrix, class CGAL::Epeck, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase, -1, 3, 0, -1, 3>> const &, class Eigen::MatrixBase> const &, class std::vector, class std::allocator>> const &, class std::map<__int64, class std::vector, class std::allocator>>, struct std::less<__int64>, class std::allocator, class std::allocator>>>>> const &, bool, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::remesh_intersections, -1, 3, 0, -1, 3>, class Eigen::Matrix, class CGAL::Epick, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase, -1, 3, 0, -1, 3>> const &, class Eigen::MatrixBase> const &, class std::vector, class std::allocator>> const &, class std::map<__int64, class std::vector, class std::allocator>>, struct std::less<__int64>, class std::allocator, class std::allocator>>>>> const &, bool, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/remesh_intersections.h b/src/igl/copyleft/cgal/remesh_intersections.h new file mode 100644 index 000000000..2b6a44a57 --- /dev/null +++ b/src/igl/copyleft/cgal/remesh_intersections.h @@ -0,0 +1,94 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_CGAL_REMESH_INTERSECTIONS_H +#define IGL_COPYLEFT_CGAL_REMESH_INTERSECTIONS_H + +#include "../../igl_inline.h" +#include +#include "CGAL_includes.hpp" + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Remesh faces according to results of intersection detection and + // construction (e.g. from `igl::copyleft::cgal::intersect_other` or + // `igl::copyleft::cgal::SelfIntersectMesh`) + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // T #F list of cgal triangles + // offending #offending map taking face indices into F to pairs of order + // of first finding and list of intersection objects from all + // intersections + // stitch_all if true, merge all vertices with the same coordinate. + // Outputs: + // VV #VV by 3 list of vertex positions, if stitch_all = false then + // first #V vertices will always be V + // FF #FF by 3 list of triangle indices into V + // IF #intersecting face pairs by 2 list of intersecting face pairs, + // indexing F + // J #FF list of indices into F denoting birth triangle + // IM / stitch_all = true #VV list from 0 to #VV-1 + // \ stitch_all = false #VV list of indices into VV of unique vertices. + // + template < + typename DerivedV, + typename DerivedF, + typename Kernel, + typename DerivedVV, + typename DerivedFF, + typename DerivedJ, + typename DerivedIM> + IGL_INLINE void remesh_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const std::vector > & T, + const std::map< + typename DerivedF::Index, + std::vector< + std::pair > > & offending, + bool stitch_all, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + // Same as above except stitch_all is assumed "false" + template < + typename DerivedV, + typename DerivedF, + typename Kernel, + typename DerivedVV, + typename DerivedFF, + typename DerivedJ, + typename DerivedIM> + IGL_INLINE void remesh_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const std::vector > & T, + const std::map< + typename DerivedF::Index, + std::vector< + std::pair > > & offending, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "remesh_intersections.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.cpp b/src/igl/copyleft/cgal/remesh_self_intersections.cpp new file mode 100644 index 000000000..871152b30 --- /dev/null +++ b/src/igl/copyleft/cgal/remesh_self_intersections.cpp @@ -0,0 +1,108 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "remesh_self_intersections.h" +#include "SelfIntersectMesh.h" +#include "../../C_STR.h" +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> +IGL_INLINE void igl::copyleft::cgal::remesh_self_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM) +{ + using namespace std; + if(params.detect_only) + { + //// This is probably a terrible idea, but CGAL is throwing floating point + //// exceptions. + +//#ifdef __APPLE__ +//#define IGL_THROW_FPE 11 +// const auto & throw_fpe = [](int e) +// { +// throw "IGL_THROW_FPE"; +// }; +// signal(SIGFPE,throw_fpe); +//#endif + + typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; + typedef + SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM> + SelfIntersectMeshK; + SelfIntersectMeshK SIM(V,F,params,VV,FF,IF,J,IM); + +//#ifdef __APPLE__ +// signal(SIGFPE,SIG_DFL); +//#endif + + }else + { + typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; + typedef + SelfIntersectMesh< + Kernel, + DerivedV, + DerivedF, + DerivedVV, + DerivedFF, + DerivedIF, + DerivedJ, + DerivedIM> + SelfIntersectMeshK; + SelfIntersectMeshK SIM(V,F,params,VV,FF,IF,J,IM); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, 8, 3, 0, 8, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, 8, 3, 0, 8, 3> > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cgal::remesh_self_intersections, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::copyleft::cgal::RemeshSelfIntersectionsParam const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::copyleft::cgal::remesh_self_intersections, class Eigen::Matrix, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, struct igl::copyleft::cgal::RemeshSelfIntersectionsParam const &, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +template void igl::copyleft::cgal::remesh_self_intersections, -1, 3, 0, -1, 3>, class Eigen::Matrix, class Eigen::Matrix, -1, -1, 0, -1, -1>, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix>(class Eigen::MatrixBase, -1, 3, 0, -1, 3>> const &, class Eigen::MatrixBase> const &, struct igl::copyleft::cgal::RemeshSelfIntersectionsParam const &, class Eigen::PlainObjectBase, -1, -1, 0, -1, -1>> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.h b/src/igl/copyleft/cgal/remesh_self_intersections.h new file mode 100644 index 000000000..e8e8cc492 --- /dev/null +++ b/src/igl/copyleft/cgal/remesh_self_intersections.h @@ -0,0 +1,81 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_REMESH_SELF_INTERSECTIONS_H +#define IGL_COPYLEFT_CGAL_REMESH_SELF_INTERSECTIONS_H +#include "../../igl_inline.h" +#include "RemeshSelfIntersectionsParam.h" + +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given a triangle mesh (V,F) compute a new mesh (VV,FF) which is the same + // as (V,F) except that any self-intersecting triangles in (V,F) have been + // subdivided (new vertices and face created) so that the self-intersection + // contour lies exactly on edges in (VV,FF). New vertices will appear in + // original faces or on original edges. New vertices on edges are "merged" + // only across original faces sharing that edge. This means that if the input + // triangle mesh is a closed manifold the output will be too. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // params struct of optional parameters + // Outputs: + // VV #VV by 3 list of vertex positions + // FF #FF by 3 list of triangle indices into VV + // IF #intersecting face pairs by 2 list of intersecting face pairs, + // indexing F + // J #FF list of indices into F denoting birth triangle + // IM #VV list of indices into VV of unique vertices. + // + // Known bugs: If an existing edge in (V,F) lies exactly on another face then + // any resulting additional vertices along that edge may not get properly + // connected so that the output mesh has the same global topology. This is + // because + // + // Example: + // // resolve intersections + // igl::copyleft::cgal::remesh_self_intersections(V,F,params,VV,FF,IF,J,IM); + // // _apply_ duplicate vertex mapping IM to FF + // for_each(FF.data(),FF.data()+FF.size(),[&IM](int & a){a=IM(a);}); + // // remove any vertices now unreferenced after duplicate mapping. + // igl::remove_unreferenced(VV,FF,SV,SF,UIM); + // // Now (SV,SF) is ready to extract outer hull + // igl::copyleft::cgal::outer_hull(SV,SF,G,J,flip); + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedVV, + typename DerivedFF, + typename DerivedIF, + typename DerivedJ, + typename DerivedIM> + IGL_INLINE void remesh_self_intersections( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const RemeshSelfIntersectionsParam & params, + Eigen::PlainObjectBase & VV, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & IF, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "remesh_self_intersections.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/remove_unreferenced.cpp b/src/igl/copyleft/cgal/remove_unreferenced.cpp new file mode 100644 index 000000000..03c8864cb --- /dev/null +++ b/src/igl/copyleft/cgal/remove_unreferenced.cpp @@ -0,0 +1,20 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../remove_unreferenced.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../remove_unreferenced.cpp" +template void igl::remove_unreferenced, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix, -1, 4, 0, -1, 4>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, 4, 0, -1, 4> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, -1, -1, 1, -1, -1> > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/resolve_intersections.cpp b/src/igl/copyleft/cgal/resolve_intersections.cpp new file mode 100644 index 000000000..2252d87e1 --- /dev/null +++ b/src/igl/copyleft/cgal/resolve_intersections.cpp @@ -0,0 +1,90 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "resolve_intersections.h" +#include "subdivide_segments.h" +#include "row_to_point.h" +#include "../../unique.h" +#include "../../list_to_matrix.h" +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedE, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ, + typename DerivedIM> +IGL_INLINE void igl::copyleft::cgal::resolve_intersections( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM) +{ + using namespace Eigen; + using namespace igl; + using namespace std; + // Exact scalar type + typedef CGAL::Epeck K; + typedef K::FT EScalar; + typedef CGAL::Segment_2 Segment_2; + typedef CGAL::Point_2 Point_2; + typedef Matrix MatrixXE; + + // Convert vertex positions to exact kernel + MatrixXE VE(V.rows(),V.cols()); + for(int i = 0;i > steiner(m); + for(int i = 0;i(VE,E(i,0)),row_to_point(VE,E(i,1))); + steiner[i].push_back(si.vertex(0)); + steiner[i].push_back(si.vertex(1)); + for(int j = i+1;j(VE,E(j,0)),row_to_point(VE,E(j,1))); + // do they intersect? + if(CGAL::do_intersect(si,sj)) + { + CGAL::Object result = CGAL::intersection(si,sj); + if(const Point_2 * p = CGAL::object_cast(&result)) + { + steiner[i].push_back(*p); + steiner[j].push_back(*p); + // add intersection point + }else if(const Segment_2 * s = CGAL::object_cast(&result)) + { + // add both endpoints + steiner[i].push_back(s->vertex(0)); + steiner[j].push_back(s->vertex(0)); + steiner[i].push_back(s->vertex(1)); + steiner[j].push_back(s->vertex(1)); + }else + { + assert(false && "Unknown intersection type"); + } + } + } + } + + subdivide_segments(V,E,steiner,VI,EI,J,IM); +} diff --git a/src/igl/copyleft/cgal/resolve_intersections.h b/src/igl/copyleft/cgal/resolve_intersections.h new file mode 100644 index 000000000..086ef1361 --- /dev/null +++ b/src/igl/copyleft/cgal/resolve_intersections.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_RESOLVE_INTERSECTIONS_H +#define IGL_COPYLEFT_CGAL_RESOLVE_INTERSECTIONS_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + // RESOLVE_INTERSECTIONS Given a list of possible intersecting segments with + // endpoints, split segments to overlap only at endpoints + // + // Inputs: + // V #V by 2 list of vertex positions + // E #E by 2 list of segment indices into V + // Outputs: + // VI #VI by 2 list of output vertex positions, copies of V are always + // the first #V vertices + // EI #EI by 2 list of segment indices into V, #EI ≥ #E + // J #EI list of indices into E revealing "parent segments" + // IM #VI list of indices into VV of unique vertices. + namespace cgal + { + template < + typename DerivedV, + typename DerivedE, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ, + typename DerivedIM> + IGL_INLINE void resolve_intersections( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "resolve_intersections.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/row_to_point.cpp b/src/igl/copyleft/cgal/row_to_point.cpp new file mode 100644 index 000000000..97a4b991e --- /dev/null +++ b/src/igl/copyleft/cgal/row_to_point.cpp @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "row_to_point.h" + +template < + typename Kernel, + typename DerivedV> +IGL_INLINE CGAL::Point_2 igl::copyleft::cgal::row_to_point( + const Eigen::PlainObjectBase & V, + const typename DerivedV::Index & i) +{ + return CGAL::Point_2(V(i,0),V(i,1)); +} diff --git a/src/igl/copyleft/cgal/row_to_point.h b/src/igl/copyleft/cgal/row_to_point.h new file mode 100644 index 000000000..54e27ad4f --- /dev/null +++ b/src/igl/copyleft/cgal/row_to_point.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_ROW_TO_POINT_H +#define IGL_COPYLEFT_CGAL_ROW_TO_POINT_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Extract a row from V and treat as a 2D cgal point (only first two + // columns of V are used). + // + // Inputs: + // V #V by 2 list of vertex positions + // i row index + // Returns 2D cgal point + template < + typename Kernel, + typename DerivedV> + IGL_INLINE CGAL::Point_2 row_to_point( + const Eigen::PlainObjectBase & V, + const typename DerivedV::Index & i); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "row_to_point.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.cpp b/src/igl/copyleft/cgal/segment_segment_squared_distance.cpp new file mode 100644 index 000000000..1ca695995 --- /dev/null +++ b/src/igl/copyleft/cgal/segment_segment_squared_distance.cpp @@ -0,0 +1,129 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "segment_segment_squared_distance.h" +#include + +// http://geomalgorithms.com/a07-_distance.html +template < typename Kernel> +IGL_INLINE bool igl::copyleft::cgal::segment_segment_squared_distance( + const CGAL::Segment_3 & S1, + const CGAL::Segment_3 & S2, + CGAL::Point_3 & P1, + CGAL::Point_3 & P2, + typename Kernel::FT & dst) +{ + typedef CGAL::Point_3 Point_3; + typedef CGAL::Vector_3 Vector_3; + typedef typename Kernel::FT EScalar; + if(S1.is_degenerate()) + { + // All points on S1 are the same + P1 = S1.source(); + point_segment_squared_distance(P1,S2,P2,dst); + return true; + }else if(S2.is_degenerate()) + { + assert(!S1.is_degenerate()); + // All points on S2 are the same + P2 = S2.source(); + point_segment_squared_distance(P2,S1,P1,dst); + return true; + } + + assert(!S1.is_degenerate()); + assert(!S2.is_degenerate()); + + Vector_3 u = S1.target() - S1.source(); + Vector_3 v = S2.target() - S2.source(); + Vector_3 w = S1.source() - S2.source(); + + const auto a = u.dot(u); // always >= 0 + const auto b = u.dot(v); + const auto c = v.dot(v); // always >= 0 + const auto d = u.dot(w); + const auto e = v.dot(w); + const auto D = a*c - b*b; // always >= 0 + assert(D>=0); + const auto sc=D, sN=D, sD = D; // sc = sN / sD, default sD = D >= 0 + const auto tc=D, tN=D, tD = D; // tc = tN / tD, default tD = D >= 0 + + bool parallel = false; + // compute the line parameters of the two closest points + if (D==0) + { + // the lines are almost parallel + parallel = true; + sN = 0.0; // force using source point on segment S1 + sD = 1.0; // to prevent possible division by 0.0 later + tN = e; + tD = c; + } else + { + // get the closest points on the infinite lines + sN = (b*e - c*d); + tN = (a*e - b*d); + if (sN < 0.0) + { + // sc < 0 => the s=0 edge is visible + sN = 0.0; + tN = e; + tD = c; + } else if (sN > sD) + { // sc > 1 => the s=1 edge is visible + sN = sD; + tN = e + b; + tD = c; + } + } + + if (tN < 0.0) + { + // tc < 0 => the t=0 edge is visible + tN = 0.0; + // recompute sc for this edge + if (-d < 0.0) + { + sN = 0.0; + }else if (-d > a) + { + sN = sD; + }else + { + sN = -d; + sD = a; + } + }else if (tN > tD) + { + // tc > 1 => the t=1 edge is visible + tN = tD; + // recompute sc for this edge + if ((-d + b) < 0.0) + { + sN = 0; + }else if ((-d + b) > a) + { + sN = sD; + }else + { + sN = (-d + b); + sD = a; + } + } + // finally do the division to get sc and tc + sc = (abs(sN) == 0 ? 0.0 : sN / sD); + tc = (abs(tN) == 0 ? 0.0 : tN / tD); + + // get the difference of the two closest points + P1 = S1.source() + sc*(S1.target()-S1.source()); + P2 = S2.source() + sc*(S2.target()-S2.source()); + Vector_3 dP = w + (sc * u) - (tc * v); // = S1(sc) - S2(tc) + assert(dP == (P1-P2)); + + dst = dP.squared_length(); // return the closest distance + return parallel; +} diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.h b/src/igl/copyleft/cgal/segment_segment_squared_distance.h new file mode 100644 index 000000000..5bfbfdcab --- /dev/null +++ b/src/igl/copyleft/cgal/segment_segment_squared_distance.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_SEGMENT_SEGMENT_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_SEGMENT_SEGMENT_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given two segments S1 and S2 find the points on each of closest + // approach and the squared distance thereof. + // + // Inputs: + // S1 first segment + // S2 second segment + // Outputs: + // P1 point on S1 closest to S2 + // P2 point on S2 closest to S1 + // d distance betwee P1 and S2 + // Returns true if the closest approach is unique. + template < typename Kernel> + IGL_INLINE bool segment_segment_squared_distance( + const CGAL::Segment_3 & S1, + const CGAL::Segment_3 & S2, + CGAL::Point_3 & P1, + CGAL::Point_3 & P2, + typename Kernel::FT & d + ); + + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "segment_segment_squared_distance.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.cpp b/src/igl/copyleft/cgal/signed_distance_isosurface.cpp new file mode 100644 index 000000000..eeb6c03c5 --- /dev/null +++ b/src/igl/copyleft/cgal/signed_distance_isosurface.cpp @@ -0,0 +1,138 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "signed_distance_isosurface.h" +#include "point_mesh_squared_distance.h" +#include "complex_to_mesh.h" + +#include "../../AABB.h" +#include "../../per_face_normals.h" +#include "../../per_edge_normals.h" +#include "../../per_vertex_normals.h" +#include "../../centroid.h" +#include "../../WindingNumberAABB.h" + +#include +#include +#include +#include +#include +// Axis-aligned bounding box tree for tet tri intersection +#include +#include +#include +#include + +IGL_INLINE bool igl::copyleft::cgal::signed_distance_isosurface( + const Eigen::MatrixXd & IV, + const Eigen::MatrixXi & IF, + const double level, + const double angle_bound, + const double radius_bound, + const double distance_bound, + const SignedDistanceType sign_type, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F) +{ + using namespace std; + using namespace Eigen; + + // default triangulation for Surface_mesher + typedef CGAL::Surface_mesh_default_triangulation_3 Tr; + // c2t3 + typedef CGAL::Complex_2_in_triangulation_3 C2t3; + typedef Tr::Geom_traits GT;//Kernel + typedef GT::Sphere_3 Sphere_3; + typedef GT::Point_3 Point_3; + typedef GT::FT FT; + typedef std::function Function; + typedef CGAL::Implicit_surface_3 Surface_3; + + AABB tree; + tree.init(IV,IF); + + Eigen::MatrixXd FN,VN,EN; + Eigen::MatrixXi E; + Eigen::VectorXi EMAP; + WindingNumberAABB< Eigen::Vector3d, Eigen::MatrixXd, Eigen::MatrixXi > hier; + switch(sign_type) + { + default: + assert(false && "Unknown SignedDistanceType"); + case SIGNED_DISTANCE_TYPE_UNSIGNED: + // do nothing + break; + case SIGNED_DISTANCE_TYPE_DEFAULT: + case SIGNED_DISTANCE_TYPE_WINDING_NUMBER: + hier.set_mesh(IV,IF); + hier.grow(); + break; + case SIGNED_DISTANCE_TYPE_PSEUDONORMAL: + // "Signed Distance Computation Using the Angle Weighted Pseudonormal" + // [Bærentzen & Aanæs 2005] + per_face_normals(IV,IF,FN); + per_vertex_normals(IV,IF,PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE,FN,VN); + per_edge_normals( + IV,IF,PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM,FN,EN,E,EMAP); + break; + } + + Tr tr; // 3D-Delaunay triangulation + C2t3 c2t3 (tr); // 2D-complex in 3D-Delaunay triangulation + // defining the surface + const auto & IVmax = IV.colwise().maxCoeff(); + const auto & IVmin = IV.colwise().minCoeff(); + const double bbd = (IVmax-IVmin).norm(); + const double r = bbd/2.; + const auto & IVmid = 0.5*(IVmax + IVmin); + // Supposedly the implict needs to evaluate to <0 at cmid... + // http://doc.cgal.org/latest/Surface_mesher/classCGAL_1_1Implicit__surface__3.html + Point_3 cmid(IVmid(0),IVmid(1),IVmid(2)); + Function fun; + switch(sign_type) + { + default: + assert(false && "Unknown SignedDistanceType"); + case SIGNED_DISTANCE_TYPE_UNSIGNED: + fun = + [&tree,&IV,&IF,&level](const Point_3 & q) -> FT + { + int i; + RowVector3d c; + const double sd = tree.squared_distance( + IV,IF,RowVector3d(q.x(),q.y(),q.z()),i,c); + return sd-level; + }; + case SIGNED_DISTANCE_TYPE_DEFAULT: + case SIGNED_DISTANCE_TYPE_WINDING_NUMBER: + fun = + [&tree,&IV,&IF,&hier,&level](const Point_3 & q) -> FT + { + const double sd = signed_distance_winding_number( + tree,IV,IF,hier,Vector3d(q.x(),q.y(),q.z())); + return sd-level; + }; + break; + case SIGNED_DISTANCE_TYPE_PSEUDONORMAL: + fun = [&tree,&IV,&IF,&FN,&VN,&EN,&EMAP,&level](const Point_3 & q) -> FT + { + const double sd = + igl::signed_distance_pseudonormal( + tree,IV,IF,FN,VN,EN,EMAP,RowVector3d(q.x(),q.y(),q.z())); + return sd- level; + }; + break; + } + Sphere_3 bounding_sphere(cmid, (r+level)*(r+level)); + Surface_3 surface(fun,bounding_sphere); + CGAL::Surface_mesh_default_criteria_3 + criteria(angle_bound,radius_bound,distance_bound); + // meshing surface + CGAL::make_surface_mesh(c2t3, surface, criteria, CGAL::Manifold_tag()); + // complex to (V,F) + return igl::copyleft::cgal::complex_to_mesh(c2t3,V,F); +} diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.h b/src/igl/copyleft/cgal/signed_distance_isosurface.h new file mode 100644 index 000000000..149ea5e3d --- /dev/null +++ b/src/igl/copyleft/cgal/signed_distance_isosurface.h @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_SIGNED_DISTANCE_ISOSURFACE_H +#define IGL_COPYLEFT_CGAL_SIGNED_DISTANCE_ISOSURFACE_H +#include "../../igl_inline.h" +#include "../../signed_distance.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // SIGNED_DISTANCE_ISOSURFACE Compute the contour of an iso-level of the + // signed distance field to a given mesh. + // + // Inputs: + // IV #IV by 3 list of input mesh vertex positions + // IF #IF by 3 list of input triangle indices + // level iso-level to contour in world coords, negative is inside. + // angle_bound lower bound on triangle angles (mesh quality) (e.g. 28) + // radius_bound upper bound on triangle size (mesh density?) (e.g. 0.02) + // distance_bound cgal mysterious parameter (mesh density?) (e.g. 0.01) + // sign_type method for computing distance _sign_ (see + // ../signed_distance.h) + // Outputs: + // V #V by 3 list of input mesh vertex positions + // F #F by 3 list of input triangle indices + // + IGL_INLINE bool signed_distance_isosurface( + const Eigen::MatrixXd & IV, + const Eigen::MatrixXi & IF, + const double level, + const double angle_bound, + const double radius_bound, + const double distance_bound, + const SignedDistanceType sign_type, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "signed_distance_isosurface.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/slice.cpp b/src/igl/copyleft/cgal/slice.cpp new file mode 100644 index 000000000..2e274a2f2 --- /dev/null +++ b/src/igl/copyleft/cgal/slice.cpp @@ -0,0 +1,12 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../slice.h" +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../slice.cpp" +#endif diff --git a/src/igl/copyleft/cgal/slice_mask.cpp b/src/igl/copyleft/cgal/slice_mask.cpp new file mode 100644 index 000000000..ec920e0d1 --- /dev/null +++ b/src/igl/copyleft/cgal/slice_mask.cpp @@ -0,0 +1,15 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../slice_mask.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../slice_mask.cpp" +template void igl::slice_mask, -1, 3, 0, -1, 3>, Eigen::Matrix, -1, 3, 0, -1, 3> >(Eigen::DenseBase, -1, 3, 0, -1, 3> > const&, Eigen::Array const&, int, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&); +#endif diff --git a/src/igl/copyleft/cgal/snap_rounding.cpp b/src/igl/copyleft/cgal/snap_rounding.cpp new file mode 100644 index 000000000..ccebfd1b8 --- /dev/null +++ b/src/igl/copyleft/cgal/snap_rounding.cpp @@ -0,0 +1,206 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "snap_rounding.h" +#include "resolve_intersections.h" +#include "subdivide_segments.h" +#include "../../remove_unreferenced.h" +#include "../../unique.h" +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedE, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::snap_rounding( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J) +{ + using namespace Eigen; + using namespace igl; + using namespace igl::copyleft::cgal; + using namespace std; + // Exact scalar type + typedef CGAL::Epeck Kernel; + typedef Kernel::FT EScalar; + typedef CGAL::Segment_2 Segment_2; + typedef CGAL::Point_2 Point_2; + typedef CGAL::Vector_2 Vector_2; + typedef Matrix MatrixXE; + // Convert vertex positions to exact kernel + + MatrixXE VE; + { + VectorXi IM; + resolve_intersections(V,E,VE,EI,J,IM); + for_each( + EI.data(), + EI.data()+EI.size(), + [&IM](typename DerivedEI::Scalar& i){i=IM(i);}); + VectorXi _; + remove_unreferenced( MatrixXE(VE), DerivedEI(EI), VE,EI,_); + } + + // find all hot pixels + //// southwest and north east corners + //const RowVector2i SW( + // round(VE.col(0).minCoeff()), + // round(VE.col(1).minCoeff())); + //const RowVector2i NE( + // round(VE.col(0).maxCoeff()), + // round(VE.col(1).maxCoeff())); + + // https://github.com/CGAL/cgal/issues/548 + // Round an exact scalar to the nearest integer. A priori can't just round + // double. Suppose e=0.5+ε but double(e)<0.5 + // + // Inputs: + // e exact number + // Outputs: + // i closest integer + const auto & round = [](const EScalar & e)->int + { + const double d = CGAL::to_double(e); + // get an integer that's near the closest int + int i = std::round(d); + EScalar di_sqr = CGAL::square((e-EScalar(i))); + const auto & search = [&i,&di_sqr,&e](const int dir) + { + while(true) + { + const int j = i+dir; + const EScalar dj_sqr = CGAL::square((e-EScalar(j))); + if(dj_sqr < di_sqr) + { + i = j; + di_sqr = dj_sqr; + }else + { + break; + } + } + }; + // Try to increase/decrease int + search(1); + search(-1); + return i; + }; + vector hot; + for(int i = 0;i _1,_2; + igl::unique(vector(hot),hot,_1,_2); + } + + // find all segments intersecting hot pixels + // split edge at closest point to hot pixel center + vector> steiner(EI.rows()); + // initialize each segment with endpoints + for(int i = 0;i hits; + for(int j = 0;j<4;j++) + { + const Segment_2 & sj = wall[j]; + if(CGAL::do_intersect(si,sj)) + { + CGAL::Object result = CGAL::intersection(si,sj); + if(const Point_2 * p = CGAL::object_cast(&result)) + { + hits.push_back(*p); + }else if(const Segment_2 * s = CGAL::object_cast(&result)) + { + // add both endpoints + hits.push_back(s->vertex(0)); + hits.push_back(s->vertex(1)); + } + } + } + if(hits.size() == 0) + { + continue; + } + // centroid of hits + Vector_2 cen(0,0); + for(const Point_2 & hit : hits) + { + cen = Vector_2(cen.x()+hit.x(), cen.y()+hit.y()); + } + cen = Vector_2(cen.x()/EScalar(hits.size()),cen.y()/EScalar(hits.size())); + const Point_2 rcen(round(cen.x()),round(cen.y())); + // after all of that, don't add as a steiner unless it's going to round + // to h + if(rcen == h) + { + steiner[i].emplace_back(cen.x(),cen.y()); + } + } + } + { + DerivedJ prevJ = J; + VectorXi IM; + subdivide_segments(MatrixXE(VE),MatrixXi(EI),steiner,VE,EI,J,IM); + for_each(J.data(),J.data()+J.size(),[&prevJ](typename DerivedJ::Scalar & j){j=prevJ(j);}); + for_each( + EI.data(), + EI.data()+EI.size(), + [&IM](typename DerivedEI::Scalar& i){i=IM(i);}); + VectorXi _; + remove_unreferenced( MatrixXE(VE), DerivedEI(EI), VE,EI,_); + } + + + VI.resizeLike(VE); + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_SNAP_ROUNDING_H +#define IGL_COPYLEFT_CGAL_SNAP_ROUNDING_H + +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // SNAP_ROUNDING Snap a list of possible intersecting segments with + // endpoints in any precision to _the_ integer grid. + // + // Inputs: + // V #V by 2 list of vertex positions + // E #E by 2 list of segment indices into V + // Outputs: + // VI #VI by 2 list of output integer vertex positions, rounded copies + // of V are always the first #V vertices + // EI #EI by 2 list of segment indices into V, #EI ≥ #E + // J #EI list of indices into E revealing "parent segments" + template < + typename DerivedV, + typename DerivedE, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ> + IGL_INLINE void snap_rounding( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "snap_rounding.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp b/src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp new file mode 100644 index 000000000..8ff1a03c4 --- /dev/null +++ b/src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp @@ -0,0 +1,53 @@ +#include "string_to_mesh_boolean_type.h" +#include +#include +#include + +IGL_INLINE bool igl::copyleft::cgal::string_to_mesh_boolean_type( + const std::string & s, + MeshBooleanType & type) +{ + using namespace std; + string eff_s = s; + transform(eff_s.begin(), eff_s.end(), eff_s.begin(), ::tolower); + const auto & find_any = + [](const vector & haystack, const string & needle)->bool + { + return find(haystack.begin(), haystack.end(), needle) != haystack.end(); + }; + if(find_any({"union","unite","u","∪"},eff_s)) + { + type = MESH_BOOLEAN_TYPE_UNION; + }else if(find_any({"intersect","intersection","i","∩"},eff_s)) + { + type = MESH_BOOLEAN_TYPE_INTERSECT; + }else if( + find_any( + {"minus","subtract","difference","relative complement","m","\\"},eff_s)) + { + type = MESH_BOOLEAN_TYPE_MINUS; + }else if(find_any({"xor","symmetric difference","x","∆"},eff_s)) + { + type = MESH_BOOLEAN_TYPE_XOR; + }else if(find_any({"resolve"},eff_s)) + { + type = MESH_BOOLEAN_TYPE_RESOLVE; + }else + { + return false; + } + return true; +} + +IGL_INLINE igl::MeshBooleanType +igl::copyleft::cgal::string_to_mesh_boolean_type( + const std::string & s) +{ + MeshBooleanType type; +#ifndef NDEBUG + const bool ret = +#endif + string_to_mesh_boolean_type(s,type); + assert(ret && "Unknown MeshBooleanType name"); + return type; +} diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.h b/src/igl/copyleft/cgal/string_to_mesh_boolean_type.h new file mode 100644 index 000000000..4781099f1 --- /dev/null +++ b/src/igl/copyleft/cgal/string_to_mesh_boolean_type.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_STRING_TO_MESH_BOOLEAN_H +#define IGL_COPYLEFT_CGAL_STRING_TO_MESH_BOOLEAN_H + +#include "../../igl_inline.h" +#include "../../MeshBooleanType.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Convert string to boolean type + // + // Inputs: + // s string identifying type, one of the following: + // "union","intersect","minus","xor","resolve" + // Outputs: + // type type of boolean operation + // Returns true only on success + // + IGL_INLINE bool string_to_mesh_boolean_type( + const std::string & s, + MeshBooleanType & type); + // Returns type without error handling + IGL_INLINE MeshBooleanType string_to_mesh_boolean_type( + const std::string & s); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "string_to_mesh_boolean_type.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/subdivide_segments.cpp b/src/igl/copyleft/cgal/subdivide_segments.cpp new file mode 100644 index 000000000..6cf7df93c --- /dev/null +++ b/src/igl/copyleft/cgal/subdivide_segments.cpp @@ -0,0 +1,134 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "subdivide_segments.h" +#include "row_to_point.h" +#include "assign_scalar.h" +#include "../../unique.h" +#include "../../list_to_matrix.h" +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedE, + typename Kernel, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ, + typename DerivedIM> +IGL_INLINE void igl::copyleft::cgal::subdivide_segments( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + const std::vector > > & _steiner, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM) +{ + using namespace Eigen; + using namespace igl; + using namespace std; + + // Exact scalar type + typedef Kernel K; + typedef typename Kernel::FT EScalar; + typedef CGAL::Segment_2 Segment_2; + typedef CGAL::Point_2 Point_2; + typedef Matrix MatrixXE; + + // non-const copy + std::vector > > steiner = _steiner; + + // Convert vertex positions to exact kernel + MatrixXE VE(V.rows(),V.cols()); + for(int i = 0;i S; + std::vector > vEI; + std::vector vJ; + for(int i = 0;i(VE,E(i,0)); + std::sort( + steiner[i].begin(), + steiner[i].end(), + [&s](const Point_2 & A, const Point_2 & B)->bool + { + return (A-s).squared_length() < (B-s).squared_length(); + }); + } + // remove duplicates + steiner[i].erase( + std::unique(steiner[i].begin(), steiner[i].end()), + steiner[i].end()); + { + int s = E(i,0); + // legs to each steiner in order + for(int j = 1;j vVES,_1; + for(int i = 0;i(VE,i)); + } + vVES.insert(vVES.end(),S.begin(),S.end()); + std::vector vA,vIM; + igl::unique(vVES,_1,vA,vIM); + // Push indices back into vVES + for_each(vIM.data(),vIM.data()+vIM.size(),[&vA](size_t & i){i=vA[i];}); + list_to_matrix(vIM,IM); + } +} diff --git a/src/igl/copyleft/cgal/subdivide_segments.h b/src/igl/copyleft/cgal/subdivide_segments.h new file mode 100644 index 000000000..cd7d41a00 --- /dev/null +++ b/src/igl/copyleft/cgal/subdivide_segments.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_SUBDIVIDE_SEGMENTS_H +#define IGL_COPYLEFT_CGAL_SUBDIVIDE_SEGMENTS_H +#include "../../igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Insert steiner points to subdivide a given set of line segments + // + // Inputs: + // V #V by 2 list of vertex positions + // E #E by 2 list of segment indices into V + // steiner #E list of lists of unsorted steiner points (including + // endpoints) along the #E original segments + // Outputs: + // VI #VI by 2 list of output vertex positions, copies of V are always + // the first #V vertices + // EI #EI by 2 list of segment indices into V, #EI ≥ #E + // J #EI list of indices into E revealing "parent segments" + // IM #VI list of indices into VV of unique vertices. + template < + typename DerivedV, + typename DerivedE, + typename Kernel, + typename DerivedVI, + typename DerivedEI, + typename DerivedJ, + typename DerivedIM> + IGL_INLINE void subdivide_segments( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & E, + const std::vector > > & steiner, + Eigen::PlainObjectBase & VI, + Eigen::PlainObjectBase & EI, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & IM); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "subdivide_segments.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.cpp b/src/igl/copyleft/cgal/submesh_aabb_tree.cpp new file mode 100644 index 000000000..4351ec0a9 --- /dev/null +++ b/src/igl/copyleft/cgal/submesh_aabb_tree.cpp @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "submesh_aabb_tree.h" +#include + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename Kernel> +IGL_INLINE void igl::copyleft::cgal::submesh_aabb_tree( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + CGAL::AABB_tree< + CGAL::AABB_traits< + Kernel, + CGAL::AABB_triangle_primitive< + Kernel, typename std::vector< + typename Kernel::Triangle_3 >::iterator > > > & tree, + std::vector & triangles, + std::vector & in_I) +{ + in_I.resize(F.rows(), false); + const size_t num_faces = I.rows(); + for (size_t i=0; i, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, CGAL::Epeck>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, CGAL::AABB_tree >::iterator, CGAL::Boolean_tag > > >&, std::vector >&, std::vector >&); +// generated by autoexplicit.sh +template void igl::copyleft::cgal::submesh_aabb_tree, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix, CGAL::Epeck>(Eigen::PlainObjectBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, CGAL::AABB_tree >::iterator, CGAL::Boolean_tag > > >&, std::vector >&, std::vector >&); +template void igl::copyleft::cgal::submesh_aabb_tree, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix, CGAL::Epeck>(Eigen::PlainObjectBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, CGAL::AABB_tree >::iterator, CGAL::Boolean_tag > > >&, std::vector >&, std::vector >&); +#endif diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.h b/src/igl/copyleft/cgal/submesh_aabb_tree.h new file mode 100644 index 000000000..eec9c030f --- /dev/null +++ b/src/igl/copyleft/cgal/submesh_aabb_tree.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLET_CGAL_SUBMESH_AABB_TREE_H +#define IGL_COPYLET_CGAL_SUBMESH_AABB_TREE_H + +#include "../../igl_inline.h" +#include +#include + +#include +#include +#include +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Build an AABB tree for a submesh indicated by a face selection list I + // of a full mesh (V,F) + // + // Inputs: + // V #V by 3 array of vertices. + // F #F by 3 array of faces. + // I #I list of triangle indices to consider. + // Outputs: + // tree aabb containing triangles of (V,F(I,:)) + // triangles #I list of cgal triangles + // in_I #F list of whether in submesh + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename Kernel> + IGL_INLINE void submesh_aabb_tree( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& I, + CGAL::AABB_tree< + CGAL::AABB_traits< + Kernel, + CGAL::AABB_triangle_primitive< + Kernel, typename std::vector< + typename Kernel::Triangle_3 >::iterator > > > & tree, + std::vector & triangles, + std::vector & in_I); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "submesh_aabb_tree.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp b/src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp new file mode 100644 index 000000000..0c512f800 --- /dev/null +++ b/src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp @@ -0,0 +1,114 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "triangle_triangle_squared_distance.h" +#include "point_triangle_squared_distance.h" +#include +#include +#include + +template < typename Kernel> +IGL_INLINE bool igl::copyleft::cgal::triangle_triangle_squared_distance( + const CGAL::Triangle_3 & T1, + const CGAL::Triangle_3 & T2, + CGAL::Point_3 & P1, + CGAL::Point_3 & P2, + typename Kernel::FT & d) +{ + typedef CGAL::Point_3 Point_3; + typedef CGAL::Vector_3 Vector_3; + typedef CGAL::Triangle_3 Triangle_3; + typedef CGAL::Segment_3 Segment_3; + typedef typename Kernel::FT EScalar; + assert(!T1.is_degenerate()); + assert(!T2.is_degenerate()); + + bool unique = true; + if(CGAL::do_intersect(T1,T2)) + { + // intersecting triangles have zero (squared) distance + CGAL::Object result = CGAL::intersection(T1,T2); + // Some point on the intersection result + CGAL::Point_3 Q; + if(const Point_3 * p = CGAL::object_cast(&result)) + { + Q = *p; + }else if(const Segment_3 * s = CGAL::object_cast(&result)) + { + unique = false; + Q = s->source(); + }else if(const Triangle_3 *itri = CGAL::object_cast(&result)) + { + Q = s->vertex(0); + unique = false; + }else if(const std::vector *polyp = + CGAL::object_cast< std::vector >(&result)) + { + assert(polyp->size() > 0 && "intersection poly should not be empty"); + Q = polyp[0]; + unique = false; + }else + { + assert(false && "Unknown intersection result"); + } + P1 = Q; + P2 = Q; + d = 0; + return unique; + } + // triangles do not intersect: the points of closest approach must be on the + // boundary of one of the triangles + d = std::numeric_limits::infinity(); + const auto & vertices_face = [&unique]( + const Triangle_3 & T1, + const Triangle_3 & T2, + Point_3 & P1, + Point_3 & P2, + EScalar & d) + { + for(int i = 0;i<3;i++) + { + const Point_3 vi = T1.vertex(i); + Point_3 P2i; + EScalar di; + point_triangle_squared_distance(vi,T2,P2i,di); + if(di +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_TRIANGLE_TRIANGLE_SQUARED_DISTANCE_H +#define IGL_COPYLEFT_CGAL_TRIANGLE_TRIANGLE_SQUARED_DISTANCE_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Given two triangles T1 and T2 find the points on each of closest + // approach and the squared distance thereof. + // + // Inputs: + // T1 first triangle + // T2 second triangle + // Outputs: + // P1 point on T1 closest to T2 + // P2 point on T2 closest to T1 + // d distance betwee P1 and T2 + // Returns true if the closest approach is unique. + template < typename Kernel> + IGL_INLINE bool triangle_triangle_squared_distance( + const CGAL::Triangle_3 & T1, + const CGAL::Triangle_3 & T2, + CGAL::Point_3 & P1, + CGAL::Point_3 & P2, + typename Kernel::FT & d); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "triangle_triangle_squared_distance.cpp" +#endif + +#endif + diff --git a/src/igl/copyleft/cgal/trim_with_solid.cpp b/src/igl/copyleft/cgal/trim_with_solid.cpp new file mode 100644 index 000000000..1e67da71e --- /dev/null +++ b/src/igl/copyleft/cgal/trim_with_solid.cpp @@ -0,0 +1,105 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "trim_with_solid.h" +#include "assign.h" +#include "intersect_other.h" +#include "point_solid_signed_squared_distance.h" + +#include "../../extract_manifold_patches.h" +#include "../../list_to_matrix.h" +#include "../../remove_unreferenced.h" +#include "../../slice_mask.h" + +#include + +#include + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedV, + typename DerivedF, + typename DerivedD, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::trim_with_solid( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Eigen::PlainObjectBase & Vd, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & J) +{ + // resolve intersections using exact representation + typedef Eigen::Matrix MatrixX3E; + typedef Eigen::Matrix VectorXE; + typedef Eigen::Matrix RowVector3E; + MatrixX3E V; + Eigen::MatrixXi _1; + Eigen::VectorXi _2; + // Intersect A and B meshes and stitch together new faces + igl::copyleft::cgal::intersect_other( + VA,FA,VB,FB,{false,false,true},_1,V,F,J,_2); + // Partition result into manifold patches + Eigen::VectorXi P; + const size_t num_patches = igl::extract_manifold_patches(F,P); + // only keep faces from A + Eigen::Matrix A = J.array()< FA.rows(); + igl::slice_mask(Eigen::MatrixXi(F),A,1,F); + igl::slice_mask(Eigen::VectorXi(P),A,1,P); + igl::slice_mask(Eigen::VectorXi(J),A,1,J); + // Aggregate representative query points for each patch + std::vector flag(num_patches); + std::vector > vQ; + Eigen::VectorXi P2Q(num_patches); + for(int f = 0;f q = { + (V(F(f,0),0)+ V(F(f,1),0)+ V(F(f,2),0))/3., + (V(F(f,0),1)+ V(F(f,1),1)+ V(F(f,2),1))/3., + (V(F(f,0),2)+ V(F(f,1),2)+ V(F(f,2),2))/3.}; + vQ.emplace_back(q); + flag[p] = true; + } + } + MatrixX3E Q; + igl::list_to_matrix(vQ,Q); + VectorXE SP; + point_solid_signed_squared_distance(Q,VB,FB,SP); + Eigen::Matrix DP = SP.array()>0; + // distribute flag to all faces + D.resize(F.rows()); + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix + >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, + Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, + Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/trim_with_solid.h b/src/igl/copyleft/cgal/trim_with_solid.h new file mode 100644 index 000000000..aa45d80f7 --- /dev/null +++ b/src/igl/copyleft/cgal/trim_with_solid.h @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CGAL_TRIM_WITH_SOLID_H +#define IGL_COPYLEFT_CGAL_TRIM_WITH_SOLID_H + +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // TRIM_WITH_SOLID Given an arbitrary mesh (VA,FA) and the boundary mesh + // (VB,FB) of a solid (as defined in [Zhou et al. 2016]), Resolve intersections + // between A and B subdividing faces of A so that intersections with B exists + // only along edges and vertices (and coplanar faces). Then determine whether + // each of these faces is inside or outside of B. This can be used to extract + // the part of A inside or outside of B. + // + // Inputs: + // VA #VA by 3 list of mesh vertex positions of A + // FA #FA by 3 list of mesh triangle indices into VA + // VB #VB by 3 list of mesh vertex positions of B + // FB #FB by 3 list of mesh triangle indices into VB + // Outputs: + // V #V by 3 list of mesh vertex positions of output + // F #F by 3 list of mesh triangle indices into V + // D #F list of bools whether face is inside B + // J #F list of indices into FA revealing birth parent + // + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedV, + typename DerivedF, + typename DerivedD, + typename DerivedJ> + IGL_INLINE void trim_with_solid( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Eigen::PlainObjectBase & Vd, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & J); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "trim_with_solid.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cgal/unique.cpp b/src/igl/copyleft/cgal/unique.cpp new file mode 100644 index 000000000..5e45f82b1 --- /dev/null +++ b/src/igl/copyleft/cgal/unique.cpp @@ -0,0 +1,14 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../unique.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../unique.cpp" +#endif diff --git a/src/igl/copyleft/cgal/unique_rows.cpp b/src/igl/copyleft/cgal/unique_rows.cpp new file mode 100644 index 000000000..e4d946f77 --- /dev/null +++ b/src/igl/copyleft/cgal/unique_rows.cpp @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "../../unique_rows.h" +#include +#include +#ifdef IGL_STATIC_LIBRARY +#undef IGL_STATIC_LIBRARY +#include "../../unique_rows.cpp" +template void igl::unique_rows, -1, -1, 0, -1, -1>, Eigen::Matrix, -1, -1, 0, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, -1, -1, 1, -1, -1>, Eigen::Matrix, -1, -1, 1, -1, -1>, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase, -1, -1, 1, -1, -1> > const&, Eigen::PlainObjectBase, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, -1, 3, 0, -1, 3>, Eigen::Matrix, -1, 3, 0, -1, 3>, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/copyleft/cgal/wire_mesh.cpp b/src/igl/copyleft/cgal/wire_mesh.cpp new file mode 100644 index 000000000..0e9609501 --- /dev/null +++ b/src/igl/copyleft/cgal/wire_mesh.cpp @@ -0,0 +1,215 @@ +#include "wire_mesh.h" + +#include "../../list_to_matrix.h" +#include "../../slice.h" +#include "../../PI.h" +#include "convex_hull.h" +#include "mesh_boolean.h" +#include +#include + +template < + typename DerivedWV, + typename DerivedWE, + typename DerivedV, + typename DerivedF, + typename DerivedJ> +IGL_INLINE void igl::copyleft::cgal::wire_mesh( + const Eigen::MatrixBase & WV, + const Eigen::MatrixBase & WE, + const double th, + const int poly_size, + const bool solid, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & J) +{ + + typedef typename DerivedWV::Scalar Scalar; + // Canonical polygon to place at each endpoint + typedef Eigen::Matrix MatrixX3S; + MatrixX3S PV(poly_size,3); + for(int p =0;p > > A(WV.rows()); + // Inputs: + // e index of edge + // c index of endpoint [0,1] + // p index of polygon vertex + // Returns index of corresponding vertex in V + const auto index = + [&PV,&WV](const int e, const int c, const int p)->int + { + return WV.rows() + e*2*PV.rows() + PV.rows()*c + p; + }; + const auto unindex = + [&PV,&WV](int v, int & e, int & c, int & p) + { + assert(v>=WV.rows()); + v = v-WV.rows(); + e = v/(2*PV.rows()); + v = v-e*(2*PV.rows()); + c = v/(PV.rows()); + v = v-c*(PV.rows()); + p = v; + }; + // loop over all edges + for(int e = 0;e RowVector3S; + const RowVector3S ev = WV.row(WE(e,1))-WV.row(WE(e,0)); + const Scalar len = ev.norm(); + // Unit edge vector + const RowVector3S uv = ev.normalized(); + Eigen::Quaternion q; + q = q.FromTwoVectors(RowVector3S(0,0,1),uv); + // loop over polygon vertices + for(int p = 0;p > vF; + std::vector vJ; + const auto append_hull = + [&V,&vF,&vJ,&unindex,&WV](const Eigen::VectorXi & I, const int j) + { + MatrixX3S Vv; + igl::slice(V,I,1,Vv); + Eigen::MatrixXi Fv; + convex_hull(Vv,Fv); + for(int f = 0;f face(I(Fv(f,0)), I(Fv(f,1)), I(Fv(f,2))); + //const bool on_vertex = (face +IGL_INLINE void igl::copyleft::cgal::wire_mesh( + const Eigen::MatrixBase & WV, + const Eigen::MatrixBase & WE, + const double th, + const int poly_size, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & J) +{ + return wire_mesh(WV,WE,th,poly_size,true,V,F,J); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::copyleft::cgal::wire_mesh, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cgal/wire_mesh.h b/src/igl/copyleft/cgal/wire_mesh.h new file mode 100644 index 000000000..9b798e6fd --- /dev/null +++ b/src/igl/copyleft/cgal/wire_mesh.h @@ -0,0 +1,67 @@ +#ifndef IGL_COPYLEFT_CGAL_WIRE_MESH_H +#define IGL_COPYLEFT_CGAL_WIRE_MESH_H +#include "../../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + namespace cgal + { + // Construct a "wire" or "wireframe" or "strut" surface mesh, given a + // one-dimensional network of straight edges. + // + // Inputs: + // WV #WV by 3 list of vertex positions + // WE #WE by 2 list of edge indices into WV + // th diameter thickness of wire + // poly_size number of sides on each wire (e.g., 4 would produce wires by + // connecting rectangular prisms). + // solid whether to resolve self-intersections to + // create a "solid" output mesh (cf., [Zhou et al. 2016] + // Outputs: + // V #V by 3 list of output vertices + // F #F by 3 list of output triangle indices into V + // J #F list of indices into [0,#WV+#WE) revealing "birth simplex" of + // output faces J(j) < #WV means the face corresponds to the J(j)th + // vertex in WV. J(j) >= #WV means the face corresponds to the + // (J(j)-#WV)th edge in WE. + template < + typename DerivedWV, + typename DerivedWE, + typename DerivedV, + typename DerivedF, + typename DerivedJ> + IGL_INLINE void wire_mesh( + const Eigen::MatrixBase & WV, + const Eigen::MatrixBase & WE, + const double th, + const int poly_size, + const bool solid, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & J); + // Default with solid=true + template < + typename DerivedWV, + typename DerivedWE, + typename DerivedV, + typename DerivedF, + typename DerivedJ> + IGL_INLINE void wire_mesh( + const Eigen::MatrixBase & WV, + const Eigen::MatrixBase & WE, + const double th, + const int poly_size, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & J); + + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "wire_mesh.cpp" +#endif +#endif diff --git a/src/igl/copyleft/comiso/frame_field.cpp b/src/igl/copyleft/comiso/frame_field.cpp new file mode 100644 index 000000000..c3549eeff --- /dev/null +++ b/src/igl/copyleft/comiso/frame_field.cpp @@ -0,0 +1,688 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "frame_field.h" + +#include +#include +#include +#include +#include + +namespace igl +{ +namespace copyleft +{ +namespace comiso +{ + +class FrameInterpolator +{ +public: + // Init + IGL_INLINE FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F); + IGL_INLINE ~FrameInterpolator(); + + // Reset constraints (at least one constraint must be present or solve will fail) + IGL_INLINE void resetConstraints(); + + IGL_INLINE void setConstraint(const int fid, const Eigen::VectorXd& v); + + IGL_INLINE void interpolateSymmetric(); + + // Generate the frame field + IGL_INLINE void solve(); + + // Convert the frame field in the canonical representation + IGL_INLINE void frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S); + + // Convert the canonical representation in a frame field + IGL_INLINE void canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S, Eigen::RowVectorXd& v); + + IGL_INLINE Eigen::MatrixXd getFieldPerFace(); + + IGL_INLINE void PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P); + + // Symmetric + Eigen::MatrixXd S; + std::vector S_c; + + // ------------------------------------------------- + + // Face Topology + Eigen::MatrixXi TT, TTi; + + // Two faces are consistent if their representative vector are taken modulo PI + std::vector edge_consistency; + Eigen::MatrixXi edge_consistency_TT; + +private: + IGL_INLINE double mod2pi(double d); + IGL_INLINE double modpi2(double d); + IGL_INLINE double modpi(double d); + + // Convert a direction on the tangent space into an angle + IGL_INLINE double vector2theta(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v); + + // Convert an angle in a vector in the tangent space + IGL_INLINE Eigen::RowVectorXd theta2vector(const Eigen::MatrixXd& TP, const double theta); + + // Interpolate the cross field (theta) + IGL_INLINE void interpolateCross(); + + // Compute difference between reference frames + IGL_INLINE void computek(); + + // Compute edge consistency + IGL_INLINE void compute_edge_consistency(); + + // Cross field direction + Eigen::VectorXd thetas; + std::vector thetas_c; + + // Edge Topology + Eigen::MatrixXi EV, FE, EF; + std::vector isBorderEdge; + + // Angle between two reference frames + // R(k) * t0 = t1 + Eigen::VectorXd k; + + // Mesh + Eigen::MatrixXd V; + Eigen::MatrixXi F; + + // Normals per face + Eigen::MatrixXd N; + + // Reference frame per triangle + std::vector TPs; + +}; + +FrameInterpolator::FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F) +{ + using namespace std; + using namespace Eigen; + + V = _V; + F = _F; + + assert(V.rows() > 0); + assert(F.rows() > 0); + + + // Generate topological relations + igl::triangle_triangle_adjacency(F,TT,TTi); + igl::edge_topology(V,F, EV, FE, EF); + + // Flag border edges + isBorderEdge.resize(EV.rows()); + for(unsigned i=0; i 3*(igl::PI/4.0); + + // Copy it into edge_consistency_TT + int i1 = -1; + int i2 = -1; + for (unsigned i=0; i<3; ++i) + { + if (TT(fid0,i) == fid1) + i1 = i; + if (TT(fid1,i) == fid0) + i2 = i; + } + assert(i1 != -1); + assert(i2 != -1); + + edge_consistency_TT(fid0,i1) = edge_consistency[eid]; + edge_consistency_TT(fid1,i2) = edge_consistency[eid]; + } + } +} + +void FrameInterpolator::computek() +{ + using namespace std; + using namespace Eigen; + + k.resize(EF.rows()); + + // For every non-border edge + for (unsigned eid=0; eid(); + + assert(tmp(0) - ref1(0) < (0.000001)); + assert(tmp(1) - ref1(1) < (0.000001)); + + k[eid] = ktemp; + } + } + +} + + + void FrameInterpolator::frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S_v) +{ + using namespace std; + using namespace Eigen; + + RowVectorXd v0 = v.segment<3>(0); + RowVectorXd v1 = v.segment<3>(3); + + // Project onto the tangent plane + Vector2d vp0 = TP * v0.transpose(); + Vector2d vp1 = TP * v1.transpose(); + + // Assemble matrix + MatrixXd M(2,2); + M << vp0, vp1; + + if (M.determinant() < 0) + M.col(1) = -M.col(1); + + assert(M.determinant() > 0); + + // cerr << "M: " << M << endl; + + MatrixXd R,S; + PolarDecomposition(M,R,S); + + // Finally, express the cross field as an angle + theta = atan2(R(1,0),R(0,0)); + + MatrixXd R2(2,2); + R2 << cos(theta), -sin(theta), sin(theta), cos(theta); + + assert((R2-R).norm() < 10e-8); + + // Convert into rotation invariant form + S = R * S * R.inverse(); + + // Copy in vector form + S_v = VectorXd(3); + S_v << S(0,0), S(0,1), S(1,1); +} + + void FrameInterpolator::canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S_v, Eigen::RowVectorXd& v) +{ + using namespace std; + using namespace Eigen; + + assert(S_v.size() == 3); + + MatrixXd S_temp(2,2); + S_temp << S_v(0), S_v(1), S_v(1), S_v(2); + + // Convert angle in vector in the tangent plane + // Vector2d vp(cos(theta),sin(theta)); + + // First reconstruct R + MatrixXd R(2,2); + + R << cos(theta), -sin(theta), sin(theta), cos(theta); + + // Rotation invariant reconstruction + MatrixXd M = S_temp * R; + + Vector2d vp0(M(0,0),M(1,0)); + Vector2d vp1(M(0,1),M(1,1)); + + // Unproject the vectors + RowVectorXd v0 = vp0.transpose() * TP; + RowVectorXd v1 = vp1.transpose() * TP; + + v.resize(6); + v << v0, v1; +} + +void FrameInterpolator::solve() +{ + interpolateCross(); + interpolateSymmetric(); +} + +void FrameInterpolator::interpolateSymmetric() +{ + using namespace std; + using namespace Eigen; + + // Generate uniform Laplacian matrix + typedef Eigen::Triplet triplet; + std::vector triplets; + + // Variables are stacked as x1,y1,z1,x2,y2,z2 + triplets.reserve(3*4*F.rows()); + + MatrixXd b = MatrixXd::Zero(3*F.rows(),1); + + // Build L and b + for (unsigned eid=0; eid L(3*F.rows(),3*F.rows()); + L.setFromTriplets(triplets.begin(), triplets.end()); + + triplets.clear(); + + // Add soft constraints + double w = 100000; + for (unsigned fid=0; fid < F.rows(); ++fid) + { + if (S_c[fid]) + { + for (unsigned i=0;i<3;++i) + { + triplets.push_back(triplet(3*fid + i,3*fid + i,w)); + b(3*fid + i) += w*S(fid,i); + } + } + } + + SparseMatrix soft(3*F.rows(),3*F.rows()); + soft.setFromTriplets(triplets.begin(), triplets.end()); + + SparseMatrix M; + + M = L + soft; + + // Solve Lx = b; + + SparseLU > solver; + + solver.compute(M); + + if(solver.info()!=Success) + { + std::cerr << "LU failed - frame_interpolator.cpp" << std::endl; + assert(0); + } + + MatrixXd x; + x = solver.solve(b); + + if(solver.info()!=Success) + { + std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl; + assert(0); + } + + S = MatrixXd::Zero(F.rows(),3); + + // Copy back the result + for (unsigned i=0;i svd(V,Eigen::ComputeFullU | Eigen::ComputeFullV); + + U = svd.matrixU() * svd.matrixV().transpose(); + P = svd.matrixV() * svd.singularValues().asDiagonal() * svd.matrixV().transpose(); +} + +} +} +} + +IGL_INLINE void igl::copyleft::comiso::frame_field( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc1, + const Eigen::MatrixXd& bc2, + Eigen::MatrixXd& FF1, + Eigen::MatrixXd& FF2 + ) + +{ + using namespace std; + using namespace Eigen; + + assert(b.size() > 0); + + // Init Solver + FrameInterpolator field(V,F); + + for (unsigned i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COMISO_FRAMEFIELD_H +#define IGL_COMISO_FRAMEFIELD_H + +#include +#include +#include +#include + +namespace igl +{ +namespace copyleft +{ +namespace comiso +{ +// Generate a piecewise-constant frame-field field from a sparse set of constraints on faces +// using the algorithm proposed in: +// Frame Fields: Anisotropic and Non-Orthogonal Cross Fields +// Daniele Panozzo, Enrico Puppo, Marco Tarini, Olga Sorkine-Hornung, +// ACM Transactions on Graphics (SIGGRAPH, 2014) +// +// Inputs: +// V #V by 3 list of mesh vertex coordinates +// F #F by 3 list of mesh faces (must be triangles) +// b #B by 1 list of constrained face indices +// bc1 #B by 3 list of the constrained first representative vector of the frame field (up to permutation and sign) +// bc2 #B by 3 list of the constrained second representative vector of the frame field (up to permutation and sign) +// +// Outputs: +// FF1 #F by 3 the first representative vector of the frame field (up to permutation and sign) +// FF2 #F by 3 the second representative vector of the frame field (up to permutation and sign) +// +// TODO: it now supports only soft constraints, should be extended to support both hard and soft constraints +IGL_INLINE void frame_field( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc1, + const Eigen::MatrixXd& bc2, + Eigen::MatrixXd& FF1, + Eigen::MatrixXd& FF2 + ); +} +} +} + +#ifndef IGL_STATIC_LIBRARY +# include "frame_field.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/comiso/miq.cpp b/src/igl/copyleft/comiso/miq.cpp new file mode 100644 index 000000000..ecf3d282b --- /dev/null +++ b/src/igl/copyleft/comiso/miq.cpp @@ -0,0 +1,1534 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti , Kevin Walliman +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "miq.h" +#include "../../local_basis.h" +#include "../../triangle_triangle_adjacency.h" +#include "../../cut_mesh.h" +#include "../../LinSpaced.h" + +// includes for VertexIndexing +#include "../../HalfEdgeIterator.h" +#include "../../is_border_vertex.h" +#include "../../vertex_triangle_adjacency.h" + +// includes for PoissonSolver +#include "../../slice_into.h" +#include "../../grad.h" +#include "../../cotmatrix.h" +#include "../../doublearea.h" +#include +#include +#include +#include + +// +#include "../../cross_field_missmatch.h" +#include "../../comb_frame_field.h" +#include "../../comb_cross_field.h" +#include "../../cut_mesh_from_singularities.h" +#include "../../find_cross_field_singularities.h" +#include "../../compute_frame_field_bisectors.h" +#include "../../rotate_vectors.h" + +#ifndef NDEBUG +#include +#endif +#include +#include "../../matlab_format.h" + +#define DEBUGPRINT 0 + + +namespace igl { +namespace copyleft { +namespace comiso { + struct SeamInfo + { + int v0,v0p; + int integerVar; + unsigned char MMatch; + + IGL_INLINE SeamInfo(int _v0, + int _v0p, + int _MMatch, + int _integerVar); + + IGL_INLINE SeamInfo(const SeamInfo &S1); + }; + + struct MeshSystemInfo + { + ////number of vertices variables + int num_vert_variables; + ///num of integer for cuts + int num_integer_cuts; + ///this are used for drawing purposes + std::vector EdgeSeamInfo; + }; + + + template + class VertexIndexing + { + public: + // Input: + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &Vcut; + const Eigen::PlainObjectBase &Fcut; + const Eigen::PlainObjectBase &TT; + const Eigen::PlainObjectBase &TTi; + + const Eigen::Matrix &Handle_MMatch; + const Eigen::Matrix &Handle_Singular; // bool + const Eigen::Matrix &Handle_Seams; // 3 bool + + + ///this handle for mesh TODO: move with the other global variables + MeshSystemInfo Handle_SystemInfo; + + IGL_INLINE VertexIndexing(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_Vcut, + const Eigen::PlainObjectBase &_Fcut, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const Eigen::Matrix &_Handle_MMatch, + const Eigen::Matrix &_Handle_Singular, + const Eigen::Matrix &_Handle_Seams + ); + + // provide information about every vertex per seam + IGL_INLINE void InitSeamInfo(); + + + private: + struct VertexInfo{ + int v; // vertex index (according to V) + int f0, k0; // face and local edge information of the edge that connects this vertex to the previous vertex (previous in the vector) + int f1, k1; // face and local edge information of the other face corresponding to the same edge + VertexInfo(int _v, int _f0, int _k0, int _f1, int _k1) : + v(_v), f0(_f0), k0(_k0), f1(_f1), k1(_k1){} + bool operator==(VertexInfo const& other){ + return other.v == v; + } + }; + + IGL_INLINE void GetSeamInfo(const int f0, + const int f1, + const int indexE, + int &v0,int &v1, + int &v0p,int &v1p, + unsigned char &_MMatch); + + IGL_INLINE std::vector > GetVerticesPerSeam(); + }; + + + template + class PoissonSolver + { + + public: + IGL_INLINE void SolvePoisson(Eigen::VectorXd Stiffness, + double vector_field_scale=0.1f, + double grid_res=1.f, + bool direct_round=true, + int localIter=0, + bool _integer_rounding=true, + bool _singularity_rounding=true, + std::vector roundVertices = std::vector(), + std::vector > hardFeatures = std::vector >()); + + IGL_INLINE PoissonSolver(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_Vcut, + const Eigen::PlainObjectBase &_Fcut, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const Eigen::PlainObjectBase &_PD1, + const Eigen::PlainObjectBase &_PD2, + const Eigen::Matrix&_Handle_Singular, + const MeshSystemInfo &_Handle_SystemInfo + ); + + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &Vcut; + const Eigen::PlainObjectBase &Fcut; + const Eigen::PlainObjectBase &TT; + const Eigen::PlainObjectBase &TTi; + const Eigen::PlainObjectBase &PD1; + const Eigen::PlainObjectBase &PD2; + const Eigen::Matrix &Handle_Singular; // bool + + const MeshSystemInfo &Handle_SystemInfo; + + // Internal: + Eigen::VectorXd Handle_Stiffness; + std::vector > VF; + std::vector > VFi; + Eigen::MatrixXd UV; // this is probably useless + + // Output: + // per wedge UV coordinates, 6 coordinates (1 face) per row + Eigen::MatrixXd WUV; + // per vertex UV coordinates, Vcut.rows() x 2 + Eigen::MatrixXd UV_out; + + // Matrices + Eigen::SparseMatrix Lhs; + Eigen::SparseMatrix Constraints; + Eigen::VectorXd rhs; + Eigen::VectorXd constraints_rhs; + ///vector of unknowns + std::vector< double > X; + + ////REAL PART + ///number of fixed vertex + unsigned int n_fixed_vars; + + ///the number of REAL variables for vertices + unsigned int n_vert_vars; + + ///total number of variables of the system, + ///do not consider constraints, but consider integer vars + unsigned int num_total_vars; + + //////INTEGER PART + ///the total number of integer variables + unsigned int n_integer_vars; + + ///CONSTRAINT PART + ///number of cuts constraints + unsigned int num_cut_constraint; + + // number of user-defined constraints + unsigned int num_userdefined_constraint; + + ///total number of constraints equations + unsigned int num_constraint_equations; + + ///vector of blocked vertices + std::vector Hard_constraints; + + ///vector of indexes to round + std::vector ids_to_round; + + ///vector of indexes to round + std::vector > userdefined_constraints; + + ///boolean that is true if rounding to integer is needed + bool integer_rounding; + + ///START COMMON MATH FUNCTIONS + ///return the complex encoding the rotation + ///for a given missmatch interval + IGL_INLINE std::complex GetRotationComplex(int interval); + ///END COMMON MATH FUNCTIONS + + ///START FIXING VERTICES + ///set a given vertex as fixed + IGL_INLINE void AddFixedVertex(int v); + + ///find vertex to fix in case we're using + ///a vector field NB: multiple components not handled + IGL_INLINE void FindFixedVertField(); + + ///find hard constraint depending if using or not + ///a vector field + IGL_INLINE void FindFixedVert(); + + IGL_INLINE int GetFirstVertexIndex(int v); + + ///fix the vertices which are flagged as fixed + IGL_INLINE void FixBlockedVertex(); + ///END FIXING VERTICES + + ///HANDLING SINGULARITY + //set the singularity round to integer location + IGL_INLINE void AddSingularityRound(); + + IGL_INLINE void AddToRoundVertices(std::vector ids); + + ///START GENERIC SYSTEM FUNCTIONS + //build the laplacian matrix cyclyng over all rangemaps + //and over all faces + IGL_INLINE void BuildLaplacianMatrix(double vfscale=1); + + ///find different sized of the system + IGL_INLINE void FindSizes(); + + IGL_INLINE void AllocateSystem(); + + ///intitialize the whole matrix + IGL_INLINE void InitMatrix(); + + ///map back coordinates after that + ///the system has been solved + IGL_INLINE void MapCoords(); + ///END GENERIC SYSTEM FUNCTIONS + + ///set the constraints for the inter-range cuts + IGL_INLINE void BuildSeamConstraintsExplicitTranslation(); + + ///set the constraints for the inter-range cuts + IGL_INLINE void BuildUserDefinedConstraints(); + + ///call of the mixed integer solver + IGL_INLINE void MixedIntegerSolve(double cone_grid_res=1, + bool direct_round=true, + int localIter=0); + + IGL_INLINE void clearUserConstraint(); + + IGL_INLINE void addSharpEdgeConstraint(int fid, int vid); + + }; + + template + class MIQ_class + { + private: + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + DerivedV Vcut; + DerivedF Fcut; + Eigen::MatrixXd UV_out; + DerivedF FUV_out; + + // internal + DerivedF TT; + DerivedF TTi; + + // Stiffness per face + Eigen::VectorXd Handle_Stiffness; + DerivedV B1, B2, B3; + + public: + IGL_INLINE MIQ_class(const Eigen::PlainObjectBase &V_, + const Eigen::PlainObjectBase &F_, + const Eigen::PlainObjectBase &PD1_combed, + const Eigen::PlainObjectBase &PD2_combed, + const Eigen::Matrix &Handle_MMatch, + const Eigen::Matrix &Handle_Singular, + const Eigen::Matrix &Handle_Seams, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double GradientSize = 30.0, + double Stiffness = 5.0, + bool DirectRound = false, + int iter = 5, + int localIter = 5, + bool DoRound = true, + bool SingularityRound=true, + std::vector roundVertices = std::vector(), + std::vector > hardFeatures = std::vector >()); + + + IGL_INLINE void extractUV(Eigen::PlainObjectBase &UV_out, + Eigen::PlainObjectBase &FUV_out); + + private: + IGL_INLINE int NumFlips(const Eigen::MatrixXd& WUV); + + IGL_INLINE double Distortion(int f, double h, const Eigen::MatrixXd& WUV); + + IGL_INLINE double LaplaceDistortion(const int f, double h, const Eigen::MatrixXd& WUV); + + IGL_INLINE bool updateStiffeningJacobianDistorsion(double grad_size, const Eigen::MatrixXd& WUV); + + IGL_INLINE bool IsFlipped(const Eigen::Vector2d &uv0, + const Eigen::Vector2d &uv1, + const Eigen::Vector2d &uv2); + + IGL_INLINE bool IsFlipped(const int i, const Eigen::MatrixXd& WUV); + + }; +}; +}; +} + +IGL_INLINE igl::copyleft::comiso::SeamInfo::SeamInfo(int _v0, + int _v0p, + int _MMatch, + int _integerVar) +{ + v0=_v0; + v0p=_v0p; + integerVar=_integerVar; + MMatch=_MMatch; +} + +IGL_INLINE igl::copyleft::comiso::SeamInfo::SeamInfo(const SeamInfo &S1) +{ + v0=S1.v0; + v0p=S1.v0p; + integerVar=S1.integerVar; + MMatch=S1.MMatch; +} + + +template +IGL_INLINE igl::copyleft::comiso::VertexIndexing::VertexIndexing(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_Vcut, + const Eigen::PlainObjectBase &_Fcut, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const Eigen::Matrix &_Handle_MMatch, + const Eigen::Matrix &_Handle_Singular, + const Eigen::Matrix &_Handle_Seams + ): +V(_V), +F(_F), +Vcut(_Vcut), +Fcut(_Fcut), +TT(_TT), +TTi(_TTi), +Handle_MMatch(_Handle_MMatch), +Handle_Singular(_Handle_Singular), +Handle_Seams(_Handle_Seams) +{ + #ifdef DEBUG_PRINT + cerr< +IGL_INLINE void igl::copyleft::comiso::VertexIndexing::GetSeamInfo(const int f0, + const int f1, + const int indexE, + int &v0,int &v1, + int &v0p,int &v1p, + unsigned char &_MMatch) +{ + int edgef0 = indexE; + v0 = Fcut(f0,edgef0); + v1 = Fcut(f0,(edgef0+1)%3); + ////get the index on opposite side + assert(TT(f0,edgef0) == f1); + int edgef1 = TTi(f0,edgef0); + v1p = Fcut(f1,edgef1); + v0p = Fcut(f1,(edgef1+1)%3); + + _MMatch = Handle_MMatch(f0,edgef0); + assert(F(f0,edgef0) == F(f1,((edgef1+1)%3))); + assert(F(f0,((edgef0+1)%3)) == F(f1,edgef1)); +} + +template +IGL_INLINE std::vector::VertexInfo> > igl::copyleft::comiso::VertexIndexing::GetVerticesPerSeam() +{ + // Return value + std::vector >verticesPerSeam; + + // for every vertex, keep track of their adjacent vertices on seams. + // regular vertices have two neighbors on a seam, start- and endvertices may have any other numbers of neighbors (e.g. 1 or 3) + std::vector > VVSeam(V.rows()); + Eigen::MatrixXi F_hit = Eigen::MatrixXi::Zero(F.rows(), 3); + for (unsigned int f=0; f startVertexIndices; + std::vector isStartVertex(V.rows()); + for (unsigned int i=0;i 0 && VVSeam[i].size() != 2 || Handle_Singular(i) == true) + { + startVertexIndices.push_back(i); + isStartVertex[i] = true; + } + } + + // For each startVertex, walk along its seam + for (unsigned int i=0;isize(); + + // explore every seam to which this vertex is a start vertex + // note: a vertex can never be a start vertex and a regular vertex simultaneously + for (unsigned int j=0;j thisSeam; // temporary container + + // Create vertexInfo struct for start vertex + auto startVertex = VertexInfo(startVertexIndices[i], -1, -1, -1, -1);// -1 values are arbitrary (will never be used) + auto currentVertex = startVertex; + // Add start vertex to the seam + thisSeam.push_back(currentVertex); + + // advance on the seam + auto currentVertexNeighbors = startVertexNeighbors; + auto nextVertex = currentVertexNeighbors->front(); + currentVertexNeighbors->pop_front(); + + auto prevVertex = startVertex; // bogus initialization to get the type + while (true) + { + // move to the next vertex + prevVertex = currentVertex; + currentVertex = nextVertex; + currentVertexNeighbors = &VVSeam[nextVertex.v]; + + // add current vertex to this seam + thisSeam.push_back(currentVertex); + + // remove the previous vertex + auto it = std::find(currentVertexNeighbors->begin(), currentVertexNeighbors->end(), prevVertex); + assert(it != currentVertexNeighbors->end()); + currentVertexNeighbors->erase(it); + + if (currentVertexNeighbors->size() == 1 && !isStartVertex[currentVertex.v]) + { + nextVertex = currentVertexNeighbors->front(); + currentVertexNeighbors->pop_front(); + } + else + break; + } + verticesPerSeam.push_back(thisSeam); + } + } + + return verticesPerSeam; +} + +template +IGL_INLINE void igl::copyleft::comiso::VertexIndexing::InitSeamInfo() +{ + auto verticesPerSeam = GetVerticesPerSeam(); + Handle_SystemInfo.EdgeSeamInfo.clear(); + int integerVar = 0; + // Loop over each seam + for(auto seam : verticesPerSeam){ + //choose initial side of the seam such that the start vertex corresponds to Fcut(f, k) and the end vertex corresponds to Fcut(f, (k+1)%3) and not vice versa. + int priorVertexIdx; + if(seam.size() > 2){ + auto v1 = seam[1]; + auto v2 = seam[2]; + if(Fcut(v1.f0, (v1.k0+1) % 3) == Fcut(v2.f0, v2.k0) || Fcut(v1.f0, (v1.k0+1) % 3) == Fcut(v2.f1, v2.k1)){ + priorVertexIdx = Fcut(v1.f0, v1.k0); + } + else{ + priorVertexIdx = Fcut(v1.f1, v1.k1); + assert(Fcut(v1.f1, (v1.k1+1) % 3) == Fcut(v2.f0, v2.k0) || Fcut(v1.f1, (v1.k1+1) % 3) == Fcut(v2.f1, v2.k1)); + } + } + else{ + auto v1 = seam[1]; + priorVertexIdx = Fcut(v1.f0, v1.k0); + } + + // Loop over each vertex of the seam + for(auto it=seam.begin()+1; it != seam.end(); ++it){ + auto vertex = *it; + // choose the correct side of the seam + int f,k,ff,kk; + if(priorVertexIdx == Fcut(vertex.f0, vertex.k0)){ + f = vertex.f0; ff = vertex.f1; + k = vertex.k0; kk = vertex.k1; + } + else{ + f = vertex.f1; ff = vertex.f0; + k = vertex.k1; kk = vertex.k0; + assert(priorVertexIdx == Fcut(vertex.f1, vertex.k1)); + } + + int vtx0,vtx0p,vtx1,vtx1p; + unsigned char MM; + GetSeamInfo(f,ff,k,vtx0,vtx1,vtx0p,vtx1p,MM); + Handle_SystemInfo.EdgeSeamInfo.push_back(SeamInfo(vtx0,vtx0p,MM,integerVar)); + if(it == seam.end() -1){ + Handle_SystemInfo.EdgeSeamInfo.push_back(SeamInfo(vtx1,vtx1p,MM,integerVar)); + } + priorVertexIdx = vtx1; + } + // use the same integer for each seam + integerVar++; + } + Handle_SystemInfo.num_integer_cuts = integerVar; + +#ifndef NDEBUG + int totalNVerticesOnSeams = 0; + for(auto seam : verticesPerSeam){ + totalNVerticesOnSeams += seam.size(); + } + assert(Handle_SystemInfo.EdgeSeamInfo.size() == totalNVerticesOnSeams); +#endif +} + + + +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::SolvePoisson(Eigen::VectorXd Stiffness, + double vector_field_scale, + double grid_res, + bool direct_round, + int localIter, + bool _integer_rounding, + bool _singularity_rounding, + std::vector roundVertices, + std::vector > hardFeatures) +{ + Handle_Stiffness = Stiffness; + + //initialization of flags and data structures + integer_rounding=_integer_rounding; + + ids_to_round.clear(); + + clearUserConstraint(); + // copy the user constraints number + for (size_t i = 0; i < hardFeatures.size(); ++i) + { + addSharpEdgeConstraint(hardFeatures[i][0],hardFeatures[i][1]); + } + + ///Initializing Matrix + + int t0=clock(); + + ///initialize the matrix ALLOCATING SPACE + InitMatrix(); + if (DEBUGPRINT) + printf("\n ALLOCATED THE MATRIX \n"); + + ///build the laplacian system + BuildLaplacianMatrix(vector_field_scale); + + // add seam constraints + BuildSeamConstraintsExplicitTranslation(); + + // add user defined constraints + BuildUserDefinedConstraints(); + + ////add the lagrange multiplier + FixBlockedVertex(); + + if (DEBUGPRINT) + printf("\n BUILT THE MATRIX \n"); + + if (integer_rounding) + AddToRoundVertices(roundVertices); + + if (_singularity_rounding) + AddSingularityRound(); + + int t1=clock(); + if (DEBUGPRINT) printf("\n time:%d \n",t1-t0); + if (DEBUGPRINT) printf("\n SOLVING \n"); + + MixedIntegerSolve(grid_res,direct_round,localIter); + + int t2=clock(); + if (DEBUGPRINT) printf("\n time:%d \n",t2-t1); + if (DEBUGPRINT) printf("\n ASSIGNING COORDS \n"); + + MapCoords(); + + int t3=clock(); + if (DEBUGPRINT) printf("\n time:%d \n",t3-t2); + if (DEBUGPRINT) printf("\n FINISHED \n"); +} + +template +IGL_INLINE igl::copyleft::comiso::PoissonSolver +::PoissonSolver(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_Vcut, + const Eigen::PlainObjectBase &_Fcut, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const Eigen::PlainObjectBase &_PD1, + const Eigen::PlainObjectBase &_PD2, + const Eigen::Matrix&_Handle_Singular, + const MeshSystemInfo &_Handle_SystemInfo +): +V(_V), +F(_F), +Vcut(_Vcut), +Fcut(_Fcut), +TT(_TT), +TTi(_TTi), +PD1(_PD1), +PD2(_PD2), +Handle_Singular(_Handle_Singular), +Handle_SystemInfo(_Handle_SystemInfo) +{ + UV = Eigen::MatrixXd(V.rows(),2); + WUV = Eigen::MatrixXd(F.rows(),6); + UV_out = Eigen::MatrixXd(Vcut.rows(),2); + igl::vertex_triangle_adjacency(V,F,VF,VFi); +} + +///START COMMON MATH FUNCTIONS +///return the complex encoding the rotation +///for a given missmatch interval +template +IGL_INLINE std::complex igl::copyleft::comiso::PoissonSolver::GetRotationComplex(int interval) +{ + assert((interval>=0)&&(interval<4)); + + switch(interval) + { + case 0:return std::complex(1,0); + case 1:return std::complex(0,1); + case 2:return std::complex(-1,0); + default:return std::complex(0,-1); + } +} + +///END COMMON MATH FUNCTIONS + +///START FIXING VERTICES +///set a given vertex as fixed +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::AddFixedVertex(int v) +{ + n_fixed_vars++; + Hard_constraints.push_back(v); +} + +///find vertex to fix in case we're using +///a vector field NB: multiple components not handled +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::FindFixedVertField() +{ + Hard_constraints.clear(); + + n_fixed_vars=0; + //fix the first singularity + for (unsigned int v=0;v +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::FindFixedVert() +{ + Hard_constraints.clear(); + FindFixedVertField(); +} + +template +IGL_INLINE int igl::copyleft::comiso::PoissonSolver::GetFirstVertexIndex(int v) +{ + return Fcut(VF[v][0],VFi[v][0]); +} + +///fix the vertices which are flagged as fixed +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::FixBlockedVertex() +{ + int offset_row = num_cut_constraint*2; + + unsigned int constr_num = 0; + for (unsigned int i=0;ivertex_index[0]; + int index = GetFirstVertexIndex(v); + + ///multiply times 2 because of uv + int indexvert = index*2; + + ///find the first free row to add the constraint + int indexRow = (offset_row+constr_num*2); + int indexCol = indexRow; + + ///add fixing constraint LHS + Constraints.coeffRef(indexRow, indexvert) += 1; + Constraints.coeffRef(indexRow+1,indexvert+1) += 1; + + ///add fixing constraint RHS + constraints_rhs[indexCol] = UV(v,0); + constraints_rhs[indexCol+1] = UV(v,1); + + constr_num++; + } + assert(constr_num==n_fixed_vars); +} +///END FIXING VERTICES + +///HANDLING SINGULARITY +//set the singularity round to integer location +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::AddSingularityRound() +{ + for (unsigned int v=0;v +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::AddToRoundVertices(std::vector ids) +{ + for (size_t i = 0; i < ids.size(); ++i) + { + if (ids[i] < 0 || ids[i] >= V.rows()) + std::cerr << "WARNING: Ignored round vertex constraint, vertex " << ids[i] << " does not exist in the mesh." << std::endl; + int index0 = GetFirstVertexIndex(ids[i]); + ids_to_round.push_back( index0*2 ); + ids_to_round.push_back((index0*2)+1); + } +} + +///START GENERIC SYSTEM FUNCTIONS +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::BuildLaplacianMatrix(double vfscale) +{ + Eigen::VectorXi idx = igl::LinSpaced(Vcut.rows(), 0, 2*Vcut.rows()-2); + Eigen::VectorXi idx2 = igl::LinSpaced(Vcut.rows(), 1, 2*Vcut.rows()-1); + + // get gradient matrix + Eigen::SparseMatrix G(Fcut.rows() * 3, Vcut.rows()); + igl::grad(Vcut, Fcut, G); + + // get triangle weights + Eigen::VectorXd dblA(Fcut.rows()); + igl::doublearea(Vcut, Fcut, dblA); + + // compute intermediate result + Eigen::SparseMatrix G2; + G2 = G.transpose() * dblA.replicate<3,1>().asDiagonal() * Handle_Stiffness.replicate<3,1>().asDiagonal(); + + /// Compute LHS + Eigen::SparseMatrix Cotmatrix; + Cotmatrix = 0.5 * G2 * G; + igl::slice_into(Cotmatrix, idx, idx, Lhs); + igl::slice_into(Cotmatrix, idx2, idx2, Lhs); + + /// Compute RHS + // reshape nrosy vectors + const Eigen::MatrixXd u = Eigen::Map(PD1.data(),Fcut.rows()*3,1); // this mimics a reshape at the cost of a copy. + const Eigen::MatrixXd v = Eigen::Map(PD2.data(),Fcut.rows()*3,1); // this mimics a reshape at the cost of a copy. + + // multiply with weights + Eigen::VectorXd rhs1 = G2 * u * 0.5 * vfscale; + Eigen::VectorXd rhs2 = -G2 * v * 0.5 * vfscale; + igl::slice_into(rhs1, idx, 1, rhs); + igl::slice_into(rhs2, idx2, 1, rhs); +} + +///find different sized of the system +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::FindSizes() +{ + ///find the vertex that need to be fixed + FindFixedVert(); + + ///REAL PART + n_vert_vars = Handle_SystemInfo.num_vert_variables; + + ///INTEGER PART + ///the total number of integer variables + n_integer_vars = Handle_SystemInfo.num_integer_cuts; + + ///CONSTRAINT PART + num_cut_constraint = Handle_SystemInfo.EdgeSeamInfo.size(); + + num_constraint_equations = num_cut_constraint * 2 + n_fixed_vars * 2 + num_userdefined_constraint; + + ///total variable of the system + num_total_vars = (n_vert_vars+n_integer_vars) * 2; + + ///initialize matrix size + + if (DEBUGPRINT) printf("\n*** SYSTEM VARIABLES *** \n"); + if (DEBUGPRINT) printf("* NUM REAL VERTEX VARIABLES %d \n",n_vert_vars); + + if (DEBUGPRINT) printf("\n*** INTEGER VARIABLES *** \n"); + if (DEBUGPRINT) printf("* NUM INTEGER VARIABLES %d \n",(int)n_integer_vars); + + if (DEBUGPRINT) printf("\n*** CONSTRAINTS *** \n "); + if (DEBUGPRINT) printf("* NUM FIXED CONSTRAINTS %d\n",n_fixed_vars); + if (DEBUGPRINT) printf("* NUM CUTS CONSTRAINTS %d\n",num_cut_constraint); + if (DEBUGPRINT) printf("* NUM USER DEFINED CONSTRAINTS %d\n",num_userdefined_constraint); + + if (DEBUGPRINT) printf("\n*** TOTAL SIZE *** \n"); + if (DEBUGPRINT) printf("* TOTAL VARIABLE SIZE (WITH INTEGER TRASL) %d \n",num_total_vars); + if (DEBUGPRINT) printf("* TOTAL CONSTRAINTS %d \n",num_constraint_equations); +} + +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::AllocateSystem() +{ + Lhs.resize(n_vert_vars * 2, n_vert_vars * 2); + Constraints.resize(num_constraint_equations, num_total_vars); + rhs.resize(n_vert_vars * 2); + constraints_rhs.resize(num_constraint_equations); + + printf("\n INITIALIZED SPARSE MATRIX OF %d x %d \n",n_vert_vars*2, n_vert_vars*2); + printf("\n INITIALIZED SPARSE MATRIX OF %d x %d \n",num_constraint_equations, num_total_vars); + printf("\n INITIALIZED VECTOR OF %d x 1 \n",n_vert_vars*2); + printf("\n INITIALIZED VECTOR OF %d x 1 \n",num_constraint_equations); +} + +///intitialize the whole matrix +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::InitMatrix() +{ + FindSizes(); + AllocateSystem(); +} + +///map back coordinates after that +///the system has been solved +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::MapCoords() +{ + ///map coords to faces + for (unsigned int f=0;f +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::BuildSeamConstraintsExplicitTranslation() +{ + ///current constraint row + int constr_row = 0; + + for (unsigned int i=0; i rot = GetRotationComplex(interval); + + ///get the integer variable + int integerVar = n_vert_vars + Handle_SystemInfo.EdgeSeamInfo[i].integerVar; + + if (integer_rounding) + { + ids_to_round.push_back(integerVar*2); + ids_to_round.push_back(integerVar*2+1); + } + + // cross boundary compatibility conditions + Constraints.coeffRef(constr_row, 2*p0) += rot.real(); + Constraints.coeffRef(constr_row, 2*p0+1) += -rot.imag(); + Constraints.coeffRef(constr_row+1, 2*p0) += rot.imag(); + Constraints.coeffRef(constr_row+1, 2*p0+1) += rot.real(); + + Constraints.coeffRef(constr_row, 2*p0p) += -1; + Constraints.coeffRef(constr_row+1, 2*p0p+1) += -1; + + Constraints.coeffRef(constr_row, 2*integerVar) += 1; + Constraints.coeffRef(constr_row+1, 2*integerVar+1) += 1; + + constraints_rhs[constr_row] = 0; + constraints_rhs[constr_row+1] = 0; + + constr_row += 2; + } + +} + +///set the constraints for the inter-range cuts +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::BuildUserDefinedConstraints() +{ + /// the user defined constraints are at the end + int offset_row = num_cut_constraint*2 + n_fixed_vars*2; + + ///current constraint row + int constr_row = offset_row; + + assert(num_userdefined_constraint == userdefined_constraints.size()); + + for (unsigned int i=0; i +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::MixedIntegerSolve(double cone_grid_res, + bool direct_round, + int localIter) +{ + X = std::vector((n_vert_vars+n_integer_vars)*2); + if (DEBUGPRINT) + printf("\n ALLOCATED X \n"); + + ///variables part + int ScalarSize = n_vert_vars*2; + int SizeMatrix = (n_vert_vars+n_integer_vars)*2; + + ///matrix A + gmm::col_matrix< gmm::wsvector< double > > A(SizeMatrix,SizeMatrix); // lhs matrix variables + + ///constraints part + int CsizeX = num_constraint_equations; + int CsizeY = SizeMatrix+1; + gmm::row_matrix< gmm::wsvector< double > > C(CsizeX,CsizeY); // constraints + + if (DEBUGPRINT) + printf("\n ALLOCATED QMM STRUCTURES \n"); + + std::vector B(SizeMatrix,0); // rhs + + if (DEBUGPRINT) + printf("\n ALLOCATED RHS STRUCTURES \n"); + + //// copy LHS + for (int k=0; k < Lhs.outerSize(); ++k){ + for (Eigen::SparseMatrix::InnerIterator it(Lhs,k); it; ++it){ + int row = it.row(); + int col = it.col(); + A(row, col) += it.value(); + } + } + //// copy Constraints + for (int k=0; k < Constraints.outerSize(); ++k){ + for (Eigen::SparseMatrix::InnerIterator it(Constraints,k); it; ++it){ + int row = it.row(); + int col = it.col(); + C(row, col) += it.value(); + } + } + + if (DEBUGPRINT) + printf("\n SET %d INTEGER VALUES \n",n_integer_vars); + + ///add penalization term for integer variables + double penalization = 0.000001; + int offline_index = ScalarSize; + for(unsigned int i = 0; i < (n_integer_vars)*2; ++i) + { + int index=offline_index+i; + A(index,index)=penalization; + } + + if (DEBUGPRINT) + printf("\n SET RHS \n"); + + // copy RHS + for(int i = 0; i < (int)ScalarSize; ++i) + { + B[i] = rhs[i] * cone_grid_res; + } + + // copy constraint RHS + if (DEBUGPRINT) + printf("\n SET %d CONSTRAINTS \n",num_constraint_equations); + + for(unsigned int i = 0; i < num_constraint_equations; ++i) + { + C(i, SizeMatrix) = -constraints_rhs[i] * cone_grid_res; + } + + COMISO::ConstrainedSolver solver; + + solver.misolver().set_local_iters(localIter); + + solver.misolver().set_direct_rounding(direct_round); + + std::sort(ids_to_round.begin(),ids_to_round.end()); + std::vector::iterator new_end=std::unique(ids_to_round.begin(),ids_to_round.end()); + int dist=distance(ids_to_round.begin(),new_end); + ids_to_round.resize(dist); + + solver.solve( C, A, X, B, ids_to_round, 0.0, false, false); +} + +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::clearUserConstraint() +{ + num_userdefined_constraint = 0; + userdefined_constraints.clear(); +} + +template +IGL_INLINE void igl::copyleft::comiso::PoissonSolver::addSharpEdgeConstraint(int fid, int vid) +{ + // prepare constraint + std::vector c(Handle_SystemInfo.num_vert_variables*2 + 1); + + for (size_t i = 0; i < c.size(); ++i) + { + c[i] = 0; + } + + int v1 = Fcut(fid,vid); + int v2 = Fcut(fid,(vid+1)%3); + + Eigen::Matrix e = Vcut.row(v2) - Vcut.row(v1); + e = e.normalized(); + + double d1 = fabs(e.dot(PD1.row(fid).normalized())); + double d2 = fabs(e.dot(PD2.row(fid).normalized())); + + int offset = 0; + + if (d1>d2) + offset = 1; + + ids_to_round.push_back((v1 * 2) + offset); + ids_to_round.push_back((v2 * 2) + offset); + + // add constraint + c[(v1 * 2) + offset] = 1; + c[(v2 * 2) + offset] = -1; + + // add to the user-defined constraints + num_userdefined_constraint++; + userdefined_constraints.push_back(c); + +} + + + +template +IGL_INLINE igl::copyleft::comiso::MIQ_class::MIQ_class(const Eigen::PlainObjectBase &V_, + const Eigen::PlainObjectBase &F_, + const Eigen::PlainObjectBase &PD1_combed, + const Eigen::PlainObjectBase &PD2_combed, + const Eigen::Matrix &Handle_MMatch, + const Eigen::Matrix &Handle_Singular, + const Eigen::Matrix &Handle_Seams, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double GradientSize, + double Stiffness, + bool DirectRound, + int iter, + int localIter, + bool DoRound, + bool SingularityRound, + std::vector roundVertices, + std::vector > hardFeatures): +V(V_), +F(F_) +{ + igl::cut_mesh(V, F, Handle_Seams, Vcut, Fcut); + + igl::local_basis(V,F,B1,B2,B3); + igl::triangle_triangle_adjacency(F,TT,TTi); + + // Prepare indexing for the linear system + VertexIndexing VInd(V, F, Vcut, Fcut, TT, TTi, Handle_MMatch, Handle_Singular, Handle_Seams); + + VInd.InitSeamInfo(); + + // Assemble the system and solve + PoissonSolver PSolver(V, + F, + Vcut, + Fcut, + TT, + TTi, + PD1_combed, + PD2_combed, + Handle_Singular, + VInd.Handle_SystemInfo); + Handle_Stiffness = Eigen::VectorXd::Constant(F.rows(),1); + + + if (iter > 0) // do stiffening + { + for (int i=0;i +IGL_INLINE void igl::copyleft::comiso::MIQ_class::extractUV(Eigen::PlainObjectBase &UV_out, + Eigen::PlainObjectBase &FUV_out) +{ + UV_out = this->UV_out; + FUV_out = this->FUV_out; +} + +template +IGL_INLINE int igl::copyleft::comiso::MIQ_class::NumFlips(const Eigen::MatrixXd& WUV) +{ + int numFl=0; + for (unsigned int i=0;i +IGL_INLINE double igl::copyleft::comiso::MIQ_class::Distortion(int f, double h, const Eigen::MatrixXd& WUV) +{ + assert(h > 0); + + Eigen::Vector2d uv0,uv1,uv2; + + uv0 << WUV(f,0), WUV(f,1); + uv1 << WUV(f,2), WUV(f,3); + uv2 << WUV(f,4), WUV(f,5); + + Eigen::Matrix p0 = Vcut.row(Fcut(f,0)); + Eigen::Matrix p1 = Vcut.row(Fcut(f,1)); + Eigen::Matrix p2 = Vcut.row(Fcut(f,2)); + + Eigen::Matrix norm = (p1 - p0).cross(p2 - p0); + double area2 = norm.norm(); + double area2_inv = 1.0 / area2; + norm *= area2_inv; + + if (area2 > 0) + { + // Singular values of the Jacobian + Eigen::Matrix neg_t0 = norm.cross(p2 - p1); + Eigen::Matrix neg_t1 = norm.cross(p0 - p2); + Eigen::Matrix neg_t2 = norm.cross(p1 - p0); + + Eigen::Matrix diffu = (neg_t0 * uv0(0) +neg_t1 *uv1(0) + neg_t2 * uv2(0) )*area2_inv; + Eigen::Matrix diffv = (neg_t0 * uv0(1) + neg_t1*uv1(1) + neg_t2*uv2(1) )*area2_inv; + + // first fundamental form + double I00 = diffu.dot(diffu); // guaranteed non-neg + double I01 = diffu.dot(diffv); // I01 = I10 + double I11 = diffv.dot(diffv); // guaranteed non-neg + + // eigenvalues of a 2x2 matrix + // [a00 a01] + // [a10 a11] + // 1/2 * [ (a00 + a11) +/- sqrt((a00 - a11)^2 + 4 a01 a10) ] + double trI = I00 + I11; // guaranteed non-neg + double diffDiag = I00 - I11; // guaranteed non-neg + double sqrtDet = sqrt(std::max(0.0, diffDiag*diffDiag + + 4 * I01 * I01)); // guaranteed non-neg + double sig1 = 0.5 * (trI + sqrtDet); // higher singular value + double sig2 = 0.5 * (trI - sqrtDet); // lower singular value + + // Avoid sig2 < 0 due to numerical error + if (fabs(sig2) < 1.0e-8) + sig2 = 0; + + assert(sig1 >= 0); + assert(sig2 >= 0); + + if (sig2 < 0) { + printf("Distortion will be NaN! sig1^2 is negative (%lg)\n", + sig2); + } + + // The singular values of the Jacobian are the sqrts of the + // eigenvalues of the first fundamental form. + sig1 = sqrt(sig1); + sig2 = sqrt(sig2); + + // distortion + double tao = IsFlipped(f,WUV) ? -1 : 1; + double factor = tao / h; + double lam = fabs(factor * sig1 - 1) + fabs(factor * sig2 - 1); + return lam; + } + else { + return 10; // something "large" + } +} + +//////////////////////////////////////////////////////////////////////////// +// Approximate the distortion laplacian using a uniform laplacian on +// the dual mesh: +// ___________ +// \-1 / \-1 / +// \ / 3 \ / +// \-----/ +// \-1 / +// \ / +// +// @param[in] f facet on which to compute distortion laplacian +// @param[in] h scaling factor applied to cross field +// @return distortion laplacian for f +/////////////////////////////////////////////////////////////////////////// +template +IGL_INLINE double igl::copyleft::comiso::MIQ_class::LaplaceDistortion(const int f, double h, const Eigen::MatrixXd& WUV) +{ + double mydist = Distortion(f, h, WUV); + double lapl=0; + for (int i=0;i<3;i++) + { + if (TT(f,i) != -1) + lapl += (mydist - Distortion(TT(f,i), h, WUV)); + } + return lapl; +} + +template +IGL_INLINE bool igl::copyleft::comiso::MIQ_class::updateStiffeningJacobianDistorsion(double grad_size, const Eigen::MatrixXd& WUV) +{ + bool flipped = NumFlips(WUV)>0; + + if (!flipped) + return false; + + double maxL=0; + double maxD=0; + + if (flipped) + { + const double c = 1.0; + const double d = 5.0; + + for (unsigned int i = 0; i < Fcut.rows(); ++i) + { + double dist=Distortion(i,grad_size,WUV); + if (dist > maxD) + maxD=dist; + + double absLap=fabs(LaplaceDistortion(i, grad_size,WUV)); + if (absLap > maxL) + maxL = absLap; + + double stiffDelta = std::min(c * absLap, d); + + Handle_Stiffness[i]+=stiffDelta; + } + } + printf("Maximum Distorsion %4.4f \n",maxD); + printf("Maximum Laplacian %4.4f \n",maxL); + return flipped; +} + +template +IGL_INLINE bool igl::copyleft::comiso::MIQ_class::IsFlipped(const Eigen::Vector2d &uv0, + const Eigen::Vector2d &uv1, + const Eigen::Vector2d &uv2) +{ + Eigen::Vector2d e0 = (uv1-uv0); + Eigen::Vector2d e1 = (uv2-uv0); + + double Area = e0(0)*e1(1) - e0(1)*e1(0); + return (Area<=0); +} + +template +IGL_INLINE bool igl::copyleft::comiso::MIQ_class::IsFlipped( + const int i, const Eigen::MatrixXd& WUV) +{ + Eigen::Vector2d uv0,uv1,uv2; + uv0 << WUV(i,0), WUV(i,1); + uv1 << WUV(i,2), WUV(i,3); + uv2 << WUV(i,4), WUV(i,5); + + return (IsFlipped(uv0,uv1,uv2)); +} + + + + +template +IGL_INLINE void igl::copyleft::comiso::miq( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1_combed, + const Eigen::PlainObjectBase &PD2_combed, + const Eigen::Matrix &Handle_MMatch, + const Eigen::Matrix &Handle_Singular, + const Eigen::Matrix &Handle_Seams, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double GradientSize, + double Stiffness, + bool DirectRound, + int iter, + int localIter, + bool DoRound, + bool SingularityRound, + std::vector roundVertices, + std::vector > hardFeatures) +{ + GradientSize = GradientSize/(V.colwise().maxCoeff()-V.colwise().minCoeff()).norm(); + + igl::copyleft::comiso::MIQ_class miq(V, + F, + PD1_combed, + PD2_combed, + Handle_MMatch, + Handle_Singular, + Handle_Seams, + UV, + FUV, + GradientSize, + Stiffness, + DirectRound, + iter, + localIter, + DoRound, + SingularityRound, + roundVertices, + hardFeatures); + + miq.extractUV(UV,FUV); +} + +template +IGL_INLINE void igl::copyleft::comiso::miq( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double GradientSize, + double Stiffness, + bool DirectRound, + int iter, + int localIter, + bool DoRound, + bool SingularityRound, + std::vector roundVertices, + std::vector > hardFeatures) +{ + + DerivedV BIS1, BIS2; + igl::compute_frame_field_bisectors(V, F, PD1, PD2, BIS1, BIS2); + + DerivedV BIS1_combed, BIS2_combed; + igl::comb_cross_field(V, F, BIS1, BIS2, BIS1_combed, BIS2_combed); + + DerivedF Handle_MMatch; + igl::cross_field_missmatch(V, F, BIS1_combed, BIS2_combed, true, Handle_MMatch); + + Eigen::Matrix isSingularity, singularityIndex; + igl::find_cross_field_singularities(V, F, Handle_MMatch, isSingularity, singularityIndex); + + Eigen::Matrix Handle_Seams; + igl::cut_mesh_from_singularities(V, F, Handle_MMatch, Handle_Seams); + + DerivedV PD1_combed, PD2_combed; + igl::comb_frame_field(V, F, PD1, PD2, BIS1_combed, BIS2_combed, PD1_combed, PD2_combed); + + igl::copyleft::comiso::miq(V, + F, + PD1_combed, + PD2_combed, + Handle_MMatch, + isSingularity, + Handle_Seams, + UV, + FUV, + GradientSize, + Stiffness, + DirectRound, + iter, + localIter, + DoRound, + SingularityRound, + roundVertices, + hardFeatures); + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::copyleft::comiso::miq, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, double, double, bool, int, int, bool, bool, std::vector >, std::vector >, std::allocator > > >); +template void igl::copyleft::comiso::miq, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, double, double, bool, int, int, bool, bool, std::vector >, std::vector >, std::allocator > > >); +template void igl::copyleft::comiso::miq, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, double, double, bool, int, int, bool, bool, std::vector >, std::vector >, std::allocator > > >); +#endif diff --git a/src/igl/copyleft/comiso/miq.h b/src/igl/copyleft/comiso/miq.h new file mode 100644 index 000000000..f4581f65d --- /dev/null +++ b/src/igl/copyleft/comiso/miq.h @@ -0,0 +1,99 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti , Kevin Walliman +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COMISO_MIQ_H +#define IGL_COMISO_MIQ_H +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace comiso + { + // Global seamless parametrization aligned with a given per-face jacobian (PD1,PD2). + // The algorithm is based on + // "Mixed-Integer Quadrangulation" by D. Bommes, H. Zimmer, L. Kobbelt + // ACM SIGGRAPH 2009, Article No. 77 (http://dl.acm.org/citation.cfm?id=1531383) + // We thank Nico Pietroni for providing a reference implementation of MIQ + // on which our code is based. + + // Inputs: + // V #V by 3 list of mesh vertex 3D positions + // F #F by 3 list of faces indices in V + // PD1 #V by 3 first line of the Jacobian per triangle + // PD2 #V by 3 second line of the Jacobian per triangle + // (optional, if empty it will be a vector in the tangent plane orthogonal to PD1) + // scale global scaling for the gradient (controls the quads resolution) + // stiffness weight for the stiffness iterations + // direct_round greedily round all integer variables at once (greatly improves optimization speed but lowers quality) + // iter stiffness iterations (0 = no stiffness) + // local_iter number of local iterations for the integer rounding + // do_round enables the integer rounding (disabling it could be useful for debugging) + // round_vertices id of additional vertices that should be snapped to integer coordinates + // hard_features #H by 2 list of pairs of vertices that belongs to edges that should be snapped to integer coordinates + // + // Output: + // UV #UV by 2 list of vertices in 2D + // FUV #FUV by 3 list of face indices in UV + // + // TODO: rename the parameters name in the cpp consistently + // improve the handling of hard_features, right now it might fail in difficult cases + + template + IGL_INLINE void miq( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double scale = 30.0, + double stiffness = 5.0, + bool direct_round = false, + int iter = 5, + int local_iter = 5, + bool DoRound = true,bool SingularityRound=true, + std::vector round_vertices = std::vector(), + std::vector > hard_features = std::vector >()); + + // Helper function that allows to directly provided pre-combed bisectors for an already cut mesh + // Additional input: + // PD1_combed, PD2_combed : #F by 3 combed jacobian + // BIS1_combed, BIS2_combed: #F by 3 pre combed bi-sectors + // MMatch: #F by 3 list of per-corner integer PI/2 rotations + // Singular: #V list of flag that denotes if a vertex is singular or not + // SingularDegree: #V list of flag that denotes the degree of the singularity + // Seams: #F by 3 list of per-corner flag that denotes seams + + template + IGL_INLINE void miq(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1_combed, + const Eigen::PlainObjectBase &PD2_combed, + const Eigen::Matrix &MMatch, + const Eigen::Matrix &Singular, + const Eigen::Matrix &Seams, + Eigen::PlainObjectBase &UV, + Eigen::PlainObjectBase &FUV, + double GradientSize = 30.0, + double Stiffness = 5.0, + bool DirectRound = false, + int iter = 5, + int localIter = 5, bool DoRound = true,bool SingularityRound=true, + std::vector roundVertices = std::vector(), + std::vector > hardFeatures = std::vector >()); + }; +}; +}; +#ifndef IGL_STATIC_LIBRARY +#include "miq.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/comiso/nrosy.cpp b/src/igl/copyleft/comiso/nrosy.cpp new file mode 100644 index 000000000..0f3ee86bc --- /dev/null +++ b/src/igl/copyleft/comiso/nrosy.cpp @@ -0,0 +1,941 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "nrosy.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace igl +{ +namespace copyleft +{ + +namespace comiso +{ +class NRosyField +{ +public: + // Init + IGL_INLINE NRosyField(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F); + + // Generate the N-rosy field + // N degree of the rosy field + // roundseparately: round the integer variables one at a time, slower but higher quality + IGL_INLINE void solve(const int N = 4); + + // Set a hard constraint on fid + // fid: face id + // v: direction to fix (in 3d) + IGL_INLINE void setConstraintHard(const int fid, const Eigen::Vector3d& v); + + // Set a soft constraint on fid + // fid: face id + // w: weight of the soft constraint, clipped between 0 and 1 + // v: direction to fix (in 3d) + IGL_INLINE void setConstraintSoft(const int fid, const double w, const Eigen::Vector3d& v); + + // Set the ratio between smoothness and soft constraints (0 -> smoothness only, 1 -> soft constr only) + IGL_INLINE void setSoftAlpha(double alpha); + + // Reset constraints (at least one constraint must be present or solve will fail) + IGL_INLINE void resetConstraints(); + + // Return the current field + IGL_INLINE Eigen::MatrixXd getFieldPerFace(); + + // Return the current field (in Ahish's ffield format) + IGL_INLINE Eigen::MatrixXd getFFieldPerFace(); + + // Compute singularity indexes + IGL_INLINE void findCones(int N); + + // Return the singularities + IGL_INLINE Eigen::VectorXd getSingularityIndexPerVertex(); + +private: + + // Compute angle differences between reference frames + IGL_INLINE void computek(); + + // Remove useless matchings + IGL_INLINE void reduceSpace(); + + // Prepare the system matrix + IGL_INLINE void prepareSystemMatrix(const int N); + + // Solve without roundings + IGL_INLINE void solveNoRoundings(); + + // Solve with roundings using CoMIso + IGL_INLINE void solveRoundings(); + + // Round all p to 0 and fix + IGL_INLINE void roundAndFixToZero(); + + // Round all p and fix + IGL_INLINE void roundAndFix(); + + // Convert a vector in 3d to an angle wrt the local reference system + IGL_INLINE double convert3DtoLocal(unsigned fid, const Eigen::Vector3d& v); + + // Convert an angle wrt the local reference system to a 3d vector + IGL_INLINE Eigen::Vector3d convertLocalto3D(unsigned fid, double a); + + // Compute the per vertex angle defect + IGL_INLINE Eigen::VectorXd angleDefect(); + + // Temporary variable for the field + Eigen::VectorXd angles; + + // Hard constraints + Eigen::VectorXd hard; + std::vector isHard; + + // Soft constraints + Eigen::VectorXd soft; + Eigen::VectorXd wSoft; + double softAlpha; + + // Face Topology + Eigen::MatrixXi TT, TTi; + + // Edge Topology + Eigen::MatrixXi EV, FE, EF; + std::vector isBorderEdge; + + // Per Edge information + // Angle between two reference frames + Eigen::VectorXd k; + + // Jumps + Eigen::VectorXi p; + std::vector pFixed; + + // Mesh + Eigen::MatrixXd V; + Eigen::MatrixXi F; + + // Normals per face + Eigen::MatrixXd N; + + // Singularity index + Eigen::VectorXd singularityIndex; + + // Reference frame per triangle + std::vector TPs; + + // System stuff + Eigen::SparseMatrix A; + Eigen::VectorXd b; + Eigen::VectorXi tag_t; + Eigen::VectorXi tag_p; + +}; + +} // NAMESPACE COMISO +} // NAMESPACE COPYLEFT +} // NAMESPACE IGL + +igl::copyleft::comiso::NRosyField::NRosyField(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F) +{ + using namespace std; + using namespace Eigen; + + V = _V; + F = _F; + + assert(V.rows() > 0); + assert(F.rows() > 0); + + + // Generate topological relations + igl::triangle_triangle_adjacency(F,TT,TTi); + igl::edge_topology(V,F, EV, FE, EF); + + // Flag border edges + isBorderEdge.resize(EV.rows()); + for(unsigned i=0; i= 0 && alpha < 1); + softAlpha = alpha; +} + + +void igl::copyleft::comiso::NRosyField::prepareSystemMatrix(const int N) +{ + using namespace std; + using namespace Eigen; + + double Nd = N; + + // Minimize the MIQ energy + // Energy on edge ij is + // (t_i - t_j + kij + pij*(2*pi/N))^2 + // Partial derivatives: + // t_i: 2 ( t_i - t_j + kij + pij*(2*pi/N)) = 0 + // t_j: 2 (-t_i + t_j - kij - pij*(2*pi/N)) = 0 + // pij: 4pi/N ( t_i - t_j + kij + pij*(2*pi/N)) = 0 + // + // t_i t_j pij kij + // t_i [ 2 -2 4pi/N 2 ] + // t_j [ -2 2 -4pi/N -2 ] + // pij [ 4pi/N -4pi/N 2*(2pi/N)^2 4pi/N ] + + // Count and tag the variables + tag_t = VectorXi::Constant(F.rows(),-1); + vector id_t; + int count = 0; + for(unsigned i=0; i id_p; + for(unsigned i=0; i > T; + T.reserve(3 * 4 * count_p); + + for(unsigned r=0; r(row,tag_t[i] , 2 )); + if (isFixed_j) b(row) += 2 * hard[j]; else T.push_back(Eigen::Triplet(row,tag_t[j] ,-2 )); + if (isFixed_p) b(row) += -((4 * igl::PI)/Nd) * p[eid] ; else T.push_back(Eigen::Triplet(row,tag_p[eid],((4 * igl::PI)/Nd))); + b(row) += -2 * k[eid]; + assert(hard[i] == hard[i]); + assert(hard[j] == hard[j]); + assert(p[eid] == p[eid]); + assert(k[eid] == k[eid]); + assert(b(row) == b(row)); + } + // (j)+1 -th row: t_j [ -2 2 -4pi/N -2 ] + if (!isFixed_j) + { + row = tag_t[j]; + if (isFixed_i) b(row) += 2 * hard[i]; else T.push_back(Eigen::Triplet(row,tag_t[i] , -2 )); + if (isFixed_j) b(row) += -2 * hard[j]; else T.push_back(Eigen::Triplet(row,tag_t[j] , 2 )); + if (isFixed_p) b(row) += ((4 * igl::PI)/Nd) * p[eid] ; else T.push_back(Eigen::Triplet(row,tag_p[eid],-((4 * igl::PI)/Nd))); + b(row) += 2 * k[eid]; + assert(k[eid] == k[eid]); + assert(b(row) == b(row)); + } + // (r*3)+2 -th row: pij [ 4pi/N -4pi/N 2*(2pi/N)^2 4pi/N ] + if (!isFixed_p) + { + row = tag_p[eid]; + if (isFixed_i) b(row) += -(4 * igl::PI)/Nd * hard[i]; else T.push_back(Eigen::Triplet(row,tag_t[i] , (4 * igl::PI)/Nd )); + if (isFixed_j) b(row) += (4 * igl::PI)/Nd * hard[j]; else T.push_back(Eigen::Triplet(row,tag_t[j] , -(4 * igl::PI)/Nd )); + if (isFixed_p) b(row) += -(2 * pow(((2*igl::PI)/Nd),2)) * p[eid] ; else T.push_back(Eigen::Triplet(row,tag_p[eid], (2 * pow(((2*igl::PI)/Nd),2)))); + b(row) += - (4 * igl::PI)/Nd * k[eid]; + assert(k[eid] == k[eid]); + assert(b(row) == b(row)); + } + + } + + A = SparseMatrix(count_t + count_p, count_t + count_p); + A.setFromTriplets(T.begin(), T.end()); + + // Soft constraints + bool addSoft = false; + + for(unsigned i=0; i > TSoft; + TSoft.reserve(2 * count_p); + + for(unsigned i=0; i(varid,varid,wSoft[i])); + bSoft[varid] += wSoft[i] * soft[i]; + } + } + SparseMatrix ASoft(count_t + count_p, count_t + count_p); + ASoft.setFromTriplets(TSoft.begin(), TSoft.end()); + +// ofstream s("/Users/daniele/As.txt"); +// for(unsigned i=0; i Atmp (count_t + count_p, count_t + count_p); + SparseMatrix Atmp2(count_t + count_p, count_t + count_p); + SparseMatrix Atmp3(count_t + count_p, count_t + count_p); + + // Merge the two part of the energy + Atmp = (1.0 - softAlpha)*A; + Atmp2 = softAlpha * ASoft; + Atmp3 = Atmp+Atmp2; + + A = Atmp3; + b = b*(1.0 - softAlpha) + bSoft * softAlpha; + } + +// ofstream s("/Users/daniele/A.txt"); +// for (int k=0; k::InnerIterator it(A,k); it; ++it) +// { +// s << it.row() << " " << it.col() << " " << it.value() << endl; +// } +// s.close(); +// +// ofstream s2("/Users/daniele/b.txt"); +// for(unsigned i=0; i > solver; + solver.compute(A); + VectorXd x = solver.solve(b); + + // Copy the result back + for(unsigned i=0; i > gmm_A; + std::vector gmm_b; + std::vector ids_to_round; + std::vector x; + + gmm_A.resize(n,n); + gmm_b.resize(n); + x.resize(n); + + // Copy A + for (int k=0; k::InnerIterator it(A,k); it; ++it) + { + gmm_A(it.row(),it.col()) += it.value(); + } + + // Copy b + for(unsigned i=0; i > gmm_C(0, n); + + COMISO::ConstrainedSolver cs; + //print_miso_settings(cs.misolver()); + cs.solve(gmm_C, gmm_A, x, gmm_b, ids_to_round, 0.0, false, true); + + // Copy the result back + for(unsigned i=0; i(); + + assert(tmp(0) - ref1(0) < 10^10); + assert(tmp(1) - ref1(1) < 10^10); + + k[eid] = ktemp; + } + } + +} + +void igl::copyleft::comiso::NRosyField::reduceSpace() +{ + using namespace std; + using namespace Eigen; + + // All variables are free in the beginning + for(unsigned i=0; i debug; + + // debug +// MatrixXd B(F.rows(),3); +// for(unsigned i=0; i visited(EV.rows()); + for(unsigned i=0; i starting(EV.rows()); + for(unsigned i=0; i q; + for(unsigned i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COMISO_NROSY_H +#define IGL_COMISO_NROSY_H + +#include +#include +#include +#include +#include "../../igl_inline.h" +#include "../../PI.h" + +namespace igl +{ + namespace copyleft + { + namespace comiso + { + // Generate a N-RoSy field from a sparse set of constraints + // + // Inputs: + // V #V by 3 list of mesh vertex coordinates + // F #F by 3 list of mesh faces (must be triangles) + // b #B by 1 list of constrained face indices + // bc #B by 3 list of representative vectors for the constrained + // faces + // b_soft #S by 1 b for soft constraints + // w_soft #S by 1 weight for the soft constraints (0-1) + // bc_soft #S by 3 bc for soft constraints + // N the degree of the N-RoSy vector field + // soft the strength of the soft constraints w.r.t. smoothness + // (0 -> smoothness only, 1->constraints only) + // Outputs: + // R #F by 3 the representative vectors of the interpolated field + // S #V by 1 the singularity index for each vertex (0 = regular) + IGL_INLINE void nrosy( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc, + const Eigen::VectorXi& b_soft, + const Eigen::VectorXd& w_soft, + const Eigen::MatrixXd& bc_soft, + const int N, + const double soft, + Eigen::MatrixXd& R, + Eigen::VectorXd& S + ); + //wrapper for the case without soft constraints + IGL_INLINE void nrosy( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc, + const int N, + Eigen::MatrixXd& R, + Eigen::VectorXd& S + ); + + } +} +} + +#ifndef IGL_STATIC_LIBRARY +# include "nrosy.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cork/from_cork_mesh.cpp b/src/igl/copyleft/cork/from_cork_mesh.cpp new file mode 100644 index 000000000..964263111 --- /dev/null +++ b/src/igl/copyleft/cork/from_cork_mesh.cpp @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "from_cork_mesh.h" + +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::copyleft::cork::from_cork_mesh( + const CorkTriMesh & mesh, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + using namespace std; + F.resize(mesh.n_triangles,3); + V.resize(mesh.n_vertices,3); + for(size_t v = 0;v, Eigen::Matrix >(CorkTriMesh const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cork::from_cork_mesh, Eigen::Matrix >(CorkTriMesh const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/cork/from_cork_mesh.h b/src/igl/copyleft/cork/from_cork_mesh.h new file mode 100644 index 000000000..a2fef28c1 --- /dev/null +++ b/src/igl/copyleft/cork/from_cork_mesh.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CORK_FROM_CORK_MESH_H +#define IGL_COPYLEFT_CORK_FROM_CORK_MESH_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cork + { + // Convert cork's triangle mesh representation to a (V,F) mesh. + // + // Inputs: + // mesh cork representation of mesh + // Outputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE void from_cork_mesh( + const CorkTriMesh & mesh, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "from_cork_mesh.cpp" +#endif +#endif diff --git a/src/igl/copyleft/cork/mesh_boolean.cpp b/src/igl/copyleft/cork/mesh_boolean.cpp new file mode 100644 index 000000000..61ed4a6ad --- /dev/null +++ b/src/igl/copyleft/cork/mesh_boolean.cpp @@ -0,0 +1,99 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mesh_boolean.h" +#include "to_cork_mesh.h" +#include "from_cork_mesh.h" + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC> +IGL_INLINE void igl::copyleft::cork::mesh_boolean( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC) +{ + CorkTriMesh A,B,C; + // pointer to output so it's easy to redirect on degenerate cases + CorkTriMesh *ret = &C; + to_cork_mesh(VA,FA,A); + to_cork_mesh(VB,FB,B); + switch(type) + { + case MESH_BOOLEAN_TYPE_UNION: + if(A.n_triangles == 0) + { + ret = &B; + }else if(B.n_triangles == 0) + { + ret = &A; + }else + { + computeUnion(A,B,ret); + } + break; + case MESH_BOOLEAN_TYPE_INTERSECT: + if(A.n_triangles == 0 || B.n_triangles == 0) + { + ret->n_triangles = 0; + ret->n_vertices = 0; + }else + { + computeIntersection(A,B,ret); + } + break; + case MESH_BOOLEAN_TYPE_MINUS: + if(A.n_triangles == 0) + { + ret->n_triangles = 0; + ret->n_vertices = 0; + }else if(B.n_triangles == 0) + { + ret = &A; + }else + { + computeDifference(A,B,ret); + } + break; + case MESH_BOOLEAN_TYPE_XOR: + if(A.n_triangles == 0) + { + ret = &B; + }else if(B.n_triangles == 0) + { + ret = &A; + }else + { + computeSymmetricDifference(A,B,&C); + } + break; + case MESH_BOOLEAN_TYPE_RESOLVE: + resolveIntersections(A,B,&C); + break; + default: + assert(false && "Unknown type"); + return; + } + from_cork_mesh(*ret,VC,FC); + freeCorkTriMesh(&A); + freeCorkTriMesh(&B); + freeCorkTriMesh(&C); +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::copyleft::cork::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::cork::mesh_boolean, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::MeshBooleanType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/copyleft/cork/mesh_boolean.h b/src/igl/copyleft/cork/mesh_boolean.h new file mode 100644 index 000000000..db2589284 --- /dev/null +++ b/src/igl/copyleft/cork/mesh_boolean.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CORK_MESH_BOOLEAN_H +#define IGL_COPYLEFT_CORK_MESH_BOOLEAN_H +#include "../../MeshBooleanType.h" +#include "../../igl_inline.h" +#include +#include // for consistent uint + +namespace igl +{ + namespace copyleft + { + namespace cork + { + // Compute a boolean operation on two input meshes using the cork library. + // + // Inputs: + // VA #VA by 3 list of vertex positions of first mesh + // FA #FA by 3 list of triangle indices into VA + // VB #VB by 3 list of vertex positions of second mesh + // FB #FB by 3 list of triangle indices into VB + // type of boolean operation see MeshBooleanType.h + // Outputs: + // VC #VC by 3 list of vertex positions of output mesh + // FC #FC by 3 list of triangle indices into VC + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename DerivedVC, + typename DerivedFC> + IGL_INLINE void mesh_boolean( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + const MeshBooleanType & type, + Eigen::PlainObjectBase & VC, + Eigen::PlainObjectBase & FC); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "mesh_boolean.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/cork/to_cork_mesh.cpp b/src/igl/copyleft/cork/to_cork_mesh.cpp new file mode 100644 index 000000000..845f8cb67 --- /dev/null +++ b/src/igl/copyleft/cork/to_cork_mesh.cpp @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "to_cork_mesh.h" +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::copyleft::cork::to_cork_mesh( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + CorkTriMesh & mesh) +{ + using namespace std; + assert((F.cols() == 0 || F.cols() == 3) && "Facets should be triangles."); + assert((V.cols() == 0 || V.cols() == 3) && "Vertices should be in 3D."); + mesh.n_triangles = F.rows(); + mesh.n_vertices = V.rows(); + mesh.vertices = new float[mesh.n_vertices*3]; + mesh.triangles = new uint[mesh.n_triangles*3]; + for(size_t v = 0;v, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, CorkTriMesh&); +template void igl::copyleft::cork::to_cork_mesh, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, CorkTriMesh&); +#endif diff --git a/src/igl/copyleft/cork/to_cork_mesh.h b/src/igl/copyleft/cork/to_cork_mesh.h new file mode 100644 index 000000000..b034e357f --- /dev/null +++ b/src/igl/copyleft/cork/to_cork_mesh.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_CORK_TO_CORK_MESH_H +#define IGL_COPYLEFT_CORK_TO_CORK_MESH_H +#include "../../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + namespace cork + { + // Convert a (V,F) mesh to a cork's triangle mesh representation. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // mesh cork representation of mesh + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE void to_cork_mesh( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + CorkTriMesh & mesh); + } + } +} +#ifndef IGL_STATIC_LIBRARY +# include "to_cork_mesh.cpp" +#endif +#endif diff --git a/src/igl/copyleft/marching_cubes.cpp b/src/igl/copyleft/marching_cubes.cpp new file mode 100644 index 000000000..144a02255 --- /dev/null +++ b/src/igl/copyleft/marching_cubes.cpp @@ -0,0 +1,258 @@ +/*===========================================================================*\ + * * + * IsoEx * + * Copyright (C) 2002 by Computer Graphics Group, RWTH Aachen * + * www.rwth-graphics.de * + * * + *---------------------------------------------------------------------------* + * * + * License * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Library General Public License as published * + * by the Free Software Foundation, version 2. * + * * + * This library 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 * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + \*===========================================================================*/ + +#include "marching_cubes.h" +#include "marching_cubes_tables.h" + +#include + + +extern const int edgeTable[256]; +extern const int triTable[256][2][17]; +extern const int polyTable[8][16]; + +struct EdgeKey +{ + EdgeKey(unsigned i0, unsigned i1) : i0_(i0), i1_(i1) {} + + bool operator==(const EdgeKey& _rhs) const + { + return i0_ == _rhs.i0_ && i1_ == _rhs.i1_; + } + + unsigned i0_, i1_; +}; + +struct EdgeHash +{ + std::size_t operator()(const EdgeKey& key) const { + std::size_t seed = 0; + seed ^= key.i0_ + 0x9e3779b9 + (seed<<6) + (seed>>2); // Copied from boost::hash_combine + seed ^= key.i1_ + 0x9e3779b9 + (seed<<6) + (seed>>2); + return std::hash()(seed); + } +}; + + +template +class MarchingCubes +{ + typedef std::unordered_map MyMap; + typedef typename MyMap::const_iterator MyMapIterator; + +public: + MarchingCubes( + const Eigen::PlainObjectBase &values, + const Eigen::PlainObjectBase &points, + const unsigned x_res, + const unsigned y_res, + const unsigned z_res, + Eigen::PlainObjectBase &vertices, + Eigen::PlainObjectBase &faces) + { + assert(values.cols() == 1); + assert(points.cols() == 3); + + if(x_res <2 || y_res<2 ||z_res<2) + return; + faces.resize(10000,3); + int num_faces = 0; + + vertices.resize(10000,3); + int num_vertices = 0; + + + unsigned n_cubes = (x_res-1) * (y_res-1) * (z_res-1); + assert(unsigned(points.rows()) == x_res * y_res * z_res); + + unsigned int offsets_[8]; + offsets_[0] = 0; + offsets_[1] = 1; + offsets_[2] = 1 + x_res; + offsets_[3] = x_res; + offsets_[4] = x_res*y_res; + offsets_[5] = 1 + x_res*y_res; + offsets_[6] = 1 + x_res + x_res*y_res; + offsets_[7] = x_res + x_res*y_res; + + for (unsigned cube_it =0 ; cube_it < n_cubes; ++cube_it) + { + + unsigned corner[8]; + typename DerivedF::Scalar samples[12]; + unsigned char cubetype(0); + unsigned int i; + + + // get point indices of corner vertices + for (i=0; i<8; ++i) + { + // get cube coordinates + unsigned int _idx = cube_it; + unsigned int X(x_res-1), Y(y_res-1); + unsigned int x = _idx % X; _idx /= X; + unsigned int y = _idx % Y; _idx /= Y; + unsigned int z = _idx; + + // transform to point coordinates + _idx = x + y*x_res + z*x_res*y_res; + + // add offset + corner[i] = _idx + offsets_[i]; + } + + + // determine cube type + for (i=0; i<8; ++i) + if (values(corner[i]) > 0.0) + cubetype |= (1< faces.rows()) + faces.conservativeResize(faces.rows()+10000, Eigen::NoChange); + + faces.row(num_faces-1) << + samples[triTable[cubetype][0][i ]], + samples[triTable[cubetype][0][i+1]], + samples[triTable[cubetype][0][i+2]]; + + } + + } + + vertices.conservativeResize(num_vertices, Eigen::NoChange); + faces.conservativeResize(num_faces, Eigen::NoChange); + + }; + + static typename DerivedF::Scalar add_vertex(const Eigen::PlainObjectBase &values, + const Eigen::PlainObjectBase &points, + unsigned int i0, + unsigned int i1, + Eigen::PlainObjectBase &vertices, + int &num_vertices, + MyMap &edge2vertex) + { + // find vertex if it has been computed already + MyMapIterator it = edge2vertex.find(EdgeKey(i0, i1)); + if (it != edge2vertex.end()) + return it->second; + ; + + // generate new vertex + const Eigen::Matrix & p0 = points.row(i0); + const Eigen::Matrix & p1 = points.row(i1); + + typename Derivedvalues::Scalar s0 = fabs(values(i0)); + typename Derivedvalues::Scalar s1 = fabs(values(i1)); + typename Derivedvalues::Scalar t = s0 / (s0+s1); + + + num_vertices++; + if (num_vertices > vertices.rows()) + vertices.conservativeResize(vertices.rows()+10000, Eigen::NoChange); + + // Linear interpolation based on linearly interpolating values + vertices.row(num_vertices-1) = ((1.0f-t)*p0 + t*p1).template cast(); + edge2vertex[EdgeKey(i0, i1)] = num_vertices-1; + + return num_vertices-1; + } + ; + + // maps an edge to the sample vertex generated on it + MyMap edge2vertex; +}; + + +template +IGL_INLINE void igl::copyleft::marching_cubes( + const Eigen::PlainObjectBase &values, + const Eigen::PlainObjectBase &points, + const unsigned x_res, + const unsigned y_res, + const unsigned z_res, + Eigen::PlainObjectBase &vertices, + Eigen::PlainObjectBase &faces) +{ + MarchingCubes mc(values, + points, + x_res, + y_res, + z_res, + vertices, + faces); +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::marching_cubes, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::marching_cubes, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::marching_cubes, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::marching_cubes, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::copyleft::marching_cubes, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::marching_cubes< Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int, unsigned int, unsigned int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/marching_cubes.h b/src/igl/copyleft/marching_cubes.h new file mode 100644 index 000000000..4c9f888e4 --- /dev/null +++ b/src/igl/copyleft/marching_cubes.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_MARCHINGCUBES_H +#define IGL_COPYLEFT_MARCHINGCUBES_H +#include "../igl_inline.h" + +#include +namespace igl +{ + namespace copyleft + { + // marching_cubes( values, points, x_res, y_res, z_res, vertices, faces ) + // + // performs marching cubes reconstruction on the grid defined by values, and + // points, and generates vertices and faces + // + // Input: + // values #number_of_grid_points x 1 array -- the scalar values of an + // implicit function defined on the grid points (<0 in the inside of the + // surface, 0 on the border, >0 outside) + // points #number_of_grid_points x 3 array -- 3-D positions of the grid + // points, ordered in x,y,z order: + // points[index] = the point at (x,y,z) where : + // x = (index % (xres -1), + // y = (index / (xres-1)) %(yres-1), + // z = index / (xres -1) / (yres -1) ). + // where x,y,z index x, y, z dimensions + // i.e. index = x + y*xres + z*xres*yres + // xres resolutions of the grid in x dimension + // yres resolutions of the grid in y dimension + // zres resolutions of the grid in z dimension + // Output: + // vertices #V by 3 list of mesh vertex positions + // faces #F by 3 list of mesh triangle indices + // + template < + typename Derivedvalues, + typename Derivedpoints, + typename Derivedvertices, + typename DerivedF> + IGL_INLINE void marching_cubes( + const Eigen::PlainObjectBase &values, + const Eigen::PlainObjectBase &points, + const unsigned x_res, + const unsigned y_res, + const unsigned z_res, + Eigen::PlainObjectBase &vertices, + Eigen::PlainObjectBase &faces); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "marching_cubes.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/marching_cubes_tables.h b/src/igl/copyleft/marching_cubes_tables.h new file mode 100644 index 000000000..4bba533c3 --- /dev/null +++ b/src/igl/copyleft/marching_cubes_tables.h @@ -0,0 +1,917 @@ +/*===========================================================================*\ + * * + * IsoEx * + * Copyright (C) 2002 by Computer Graphics Group, RWTH Aachen * + * www.rwth-graphics.de * + * * + *---------------------------------------------------------------------------* + * * + * License * + * * + * This library is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Library General Public License as published * + * by the Free Software Foundation, version 2. * + * * + * This library 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 * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + \*===========================================================================*/ + +//============================================================================= +#ifndef IGL_ISOEX_MC_TABLES_HH +#define IGL_ISOEX_MC_TABLES_HH +//============================================================================= + + +//int edgeTable[256]; +//int triTable[256][2][17]; +//int polyTable[8][16]; + +const int edgeTable[256]= +{ + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 +}; + + +//----------------------------------------------------------------------------- + + +const int triTable[256][2][17] = +{{{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 1, 9, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 2, 10, 9, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 10, 9, 8, 3, 2 , -1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 0, 8, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 10 */ + {{1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 1, 9, 0, 2, 3,11, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 9, 8, 11, 2, 1,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 3, 11,10, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 11, 10, 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 11,10, 9, 0, 3, -1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 15 */ + {{9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 8, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 4, 7, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 7, 3, 1, 9, 4, -1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 20 */ + {{1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 1, 2,10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 3, 0, 4, 7, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 2,10, 9, 0, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1, -1}, + {1, 6, 7, 3, 2,10, 9, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 25 */ + {{11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 2, 0, 4, 7,11,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1}}, + + {{4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 1, 9, 11, 11,9,4,7, -1, -1, -1, -1, -1 ,-1}}, + + {{3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 3, 11,10, 1, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1, -1}, + {1, 6, 1, 0, 4, 7,11,10, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 30 */ + {{4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1, -1}, + {2, 3, 5, 4, 7, 8, 0, 3, 11, 10, 9, -1, -1, -1, -1, -1, -1}}, + + {{4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 4, 7,11,10, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 0, 1, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 35 */ + {{8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3, 1, 5, 4, 8,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 1, 2,10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1}}, + + {{5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 4, 0, 2,10, 5,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 10, 5, 3, 4, 8, 3, 5, -1, -1, -1, -1, -1, -1}}, + + /* 40 */ + {{9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 0, 8, 11, 2, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 0, 1, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1, -1}, + {1, 6, 2, 1, 5, 4, 8,11, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 4, 3, 3,11,10, 1, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}}, + + /* 45 */ + {{4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1, -1}, + {2, 3, 5, 4, 9, 5, 1, 0, 8,11, 10, -1, -1, -1, -1, -1, -1}}, + + {{5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1, -1}, + {1, 6, 5, 4, 0, 3,11, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 5, 4, 8, 11, 10,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 7, 8, 9, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 5, 7, 3, 0, 9,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 50 */ + {{0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 1, 5, 7, 8, 0,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 3, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 7, 8, 9, 5,10, 1, 2, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1, -1}, + { 2, 3, 5,10, 1, 2, 0, 9, 5, 7, 3,-1, -1, -1, -1, -1, -1}}, + + {{8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1, -1}, + {1, 6, 2,10, 5, 7, 8, 0,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 55 */ + {{2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 2,10, 5, 7, 3,-1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 7, 8, 9, 5, 3,11, 2, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1, -1}, + {1, 6, 2, 0, 9, 5, 7,11, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1, -1}, + {2, 3, 5, 2, 3,11, 8, 0, 1, 5, 7, -1, -1, -1, -1, -1, -1}}, + + {{11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5,11, 2, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 60 */ + {{9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1, -1}, + {2, 4, 4, 3,11, 10, 1, 5, 7, 8, 9, -1, -1, -1, -1, -1, -1}}, + + {{5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1, -1}, + {1, 7, 5, 7, 11,10, 1, 0, 9, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1, -1}, + {1, 7, 11,10,5, 7, 8, 0,3, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 4, 5, 7, 11,10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 3,10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 65 */ + {{0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 1, 9, 8, 3, 5,10, 6, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 1, 2, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 1, 2, 6, 5, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}}, + + /* 70 */ + {{9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 0, 2, 6, 5, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1, -1}, + {1, 6, 2, 6, 5, 9, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 2, 3,11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 4, 3, 0, 8, 11, 2, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}}, + + /* 75 */ + {{5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1, -1}, + {2, 3, 5, 5,10, 6, 2, 1, 9, 8,11, -1, -1, -1, -1, -1, -1}}, + + {{6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 5, 1, 3, 11,6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1, -1}, + {1, 6, 5, 1, 0, 8,11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1, -1}, + {2, 4, 4, 3, 11, 6, 0, 5, 9, 0, 6, -1, -1, -1, -1}}, + + {{6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 6, 5, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 80 */ + {{5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 5,10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 4, 7, 3, 0, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 1, 9, 0, 5,10, 6, 8, 4, 7, -1, -1, -1, -1}}, + + {{10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1, -1}, + { 2, 3, 5,10, 6, 5, 9, 4, 7, 3, 1,-1, -1, -1, -1, -1, -1}}, + + {{6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 1, 2, 6, 5, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}}, + + /* 85 */ + {{1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 6, 5, 1, 3, 0, 4, 7, -1, -1, -1, -1, -1, -1}}, + + {{8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1, -1}, + {2, 3, 5, 8, 4, 7, 5, 9, 0, 2, 6, -1, -1, -1, -1, -1, -1}}, + + {{7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1, -1}, + {1, 7, 7, 3, 2, 6, 5, 9, 4,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 3,11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1}}, + + {{5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1, -1}, + {2, 3, 5, 5,10, 6, 7,11, 2, 0, 4, -1, -1, -1, -1, -1, -1}}, + + /* 90 */ + {{0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1}, + {4, 3, 3, 3, 3, 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6}}, + + {{9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1, -1}, + {3, 4, 4, 3, 2, 1, 9,11, 4, 7, 11, 9, 5, 10, 6, -1, -1}}, + + {{8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1, -1}, + {2, 3, 5, 8, 4, 7, 11, 6, 5, 1, 3, -1, -1, -1, -1, -1, -1}}, + + {{5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1, -1}, + {1, 7, 5, 1, 0, 4, 7,11, 6, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1, -1}, + {3, 4, 4, 3, 0, 6, 5, 9, 3, 11, 6, 0, 8, 4, 7, -1, -1}}, + + /* 95 */ + {{6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1, -1}, + {2, 4, 4, 9, 4, 7, 11, 6, 5, 9, 11,-1, -1, -1, -1, -1, -1}}, + + {{10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 4, 4, 9, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 4, 9,10, 6, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 6, 4, 0, 1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1, -1}, + {1, 6, 1,10, 6, 4, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 100 */ + {{1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 2, 6, 4, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1, -1}, + {2, 3, 5, 3, 0, 8, 9, 1, 2, 6, 4, -1, -1, -1, -1, -1, -1}}, + + {{0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 2, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 3, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 4, 3, 10, 6, 4, 9,11, 2, 3, -1, -1, -1, -1, -1, -1, -1}}, + + /* 105 */ + {{0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 11, 8, 0, 10, 6, 4, 9, -1, -1, -1, -1, -1, -1}}, + + {{3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1, -1}, + {2, 3, 5, 3,11, 2, 1, 10,6, 4, 0, -1, -1, -1, -1, -1, -1}}, + + {{6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1, -1}, + {1, 7, 6, 4, 8,11, 2, 1,10, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1, -1}, + {1, 6, 3,11, 6, 4, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1, -1}, + {1, 7, 8,11, 6, 4, 9, 1, 0, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 110 */ + {{3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3,11, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 8, 11, 6, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 9,10, 6, 7,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1, -1}, + {1, 6, 0, 9, 10, 6, 7, 3, -1,-1,-1, -1, -1, -1, -1, -1, -1}}, + + {{10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1, -1}, + { 2, 4, 4, 8, 0, 1, 7, 10, 6, 7, 1,-1, -1, -1, -1, -1, -1}}, + + /* 115 */ + {{10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 10, 6, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1, -1}, + {1, 6, 1, 2, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1, -1}, + {1, 7, 2, 6, 7, 3, 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 7, 8, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 7, 3, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 120 */ + {{2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1, -1}, + {2, 3, 5, 2, 3,11, 6, 7, 8, 9,10, -1, -1, -1, -1, -1, -1}}, + + {{2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1, -1}, + {1, 7, 2, 0, 9,10,6, 7, 11, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1, -1}, + {3, 4, 4, 3, 8, 0, 1, 7, 10, 6, 7, 1, 11, 2, 3, -1, -1}}, + + {{11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1, -1}, + { 2, 4, 4, 11, 2, 1,7, 1, 10, 6, 7,-1, -1, -1, -1, -1, -1}}, + + {{8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1, -1}, + {1, 7, 8, 9, 1, 3, 11, 6, 7,-1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 125 */ + {{0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1, -1}, + {2, 4, 4, 0, 3,11, 6, 7, 8, 0, 6, -1, -1, -1, -1, -1, -1}}, + + {{7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 130 */ + {{0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 1, 9, 8, 3,11, 7, 6, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 3, 3,10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 1, 2,10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1}}, + + {{2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 2, 10, 9, 0, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}}, + + /* 135 */ + {{6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1, -1}, + {2, 3, 5, 6, 11, 7, 3, 2,10, 9, 8, -1, -1, -1, -1, -1, -1}}, + + {{7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 2, 3, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 6, 2, 0, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 2, 3, 7, 6, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1, -1}, + {1, 6, 6, 2, 1, 9, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 140 */ + {{10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 1, 3, 7, 6,10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1, -1}, + { 2, 4, 4, 10, 1, 7, 6, 8, 7, 1, 0,-1, -1, -1, -1, -1, -1}}, + + {{0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1, -1}, + {1, 6,10, 9, 0, 3, 7, 6,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 7, 6, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 6, 11, 8, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 145 */ + {{3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 0, 4, 6,11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 6,11, 8, 4, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1, -1}, + {1, 6, 6,11, 3, 1, 9, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 6, 11, 8, 4, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1, -1}, + {2, 3, 5, 1, 2, 10,11, 3,0,4, 6, -1, -1, -1, -1, -1, -1}}, + + /* 150 */ + {{4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1, -1}, + {2, 4, 4, 4, 6, 11, 8, 2,10, 9, 0, -1, -1, -1, -1, -1, -1}}, + + {{10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1, -1}, + {1, 7, 10,9, 4, 6, 11, 3, 2, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 4, 6, 2, 3, 8,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 4, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1, -1}, + {2, 3, 5, 1, 9, 0, 3, 8, 4, 6, 2, -1, -1, -1, -1, -1, -1}}, + + /* 155 */ + {{1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 1, 9, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1, -1}, + {1, 6, 1, 3, 8, 4, 6,10, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5,10, 1,0,4,6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1, -1}, + {1, 7, 4, 6, 10, 9, 0,3, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 4, 4, 6, 10, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 160 */ + {{4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1}}, + + {{5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 0, 1, 5, 4, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1, -1}, + { 2, 3, 5,11, 7, 6, 4, 8, 3, 1, 5,-1, -1, -1, -1, -1, -1}}, + + {{9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, 3, 3, 9, 5, 4,10, 1, 2, 7, 6, 11, -1, -1, -1, -1}}, + + /* 165 */ + {{6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1, -1}, + {4, 3, 3, 3, 3, 6,11, 7, 1, 2,10, 0, 8, 3, 4, 9, 5}}, + + {{7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1, -1}, + {2, 3, 5, 7, 6, 11, 10, 5, 4, 0, 2,-1, -1, -1, -1, -1, -1}}, + + {{3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1, -1}, + {3, 4, 4, 3, 5, 3, 2,10, 4, 8, 3, 5, 6, 11, 7, 6, -1}}, + + {{7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 2, 3, 7, 6, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1, -1}, + {2, 3, 5, 9, 5, 4, 8, 7, 6, 2, 0, -1, -1, -1, -1, -1, -1}}, + + /* 170 */ + {{3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1, -1}, + {2, 4, 4, 3, 7, 6, 2, 0, 1, 5, 4, -1, -1, -1, -1, -1, -1}}, + + {{6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1, -1}, + {1, 7, 6, 2, 1, 5, 4, 8, 7,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1, -1}, + {2, 3, 5, 9, 5, 4, 6,10, 1, 3, 7,-1, -1, -1, -1, -1, -1}}, + + {{1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1, -1}, + {3, 4, 4, 3, 0, 8, 7, 1, 6, 10, 1, 7, 9, 5, 4, -1, -1}}, + + {{4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1, -1}, + {1, 7, 4, 0, 3, 7, 6, 10, 5, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 175 */ + {{7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1, -1}, + {2, 4, 4, 4, 8, 10, 5, 7, 6,10, 8, -1, -1, -1, -1, -1, -1}}, + + {{6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5,11, 8, 9, 5, 6,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1, -1}, + {2, 4, 4, 0, 9, 5, 6, 6,11, 3, 0, -1, -1, -1, -1, -1, -1}}, + + {{0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1, -1}, + {1, 6, 0, 1, 5, 6,11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 6,11, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /*180 */ + {{1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1, -1}, + {2, 3, 5, 1, 2, 10, 5, 6,11, 8, 9, -1, -1, -1, -1, -1, -1}}, + + {{0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1, -1}, + {3, 4, 4, 3, 11, 3,0, 6, 9, 5, 6, 0, 2, 10, 1, 2, 10}}, + + {{11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1, -1}, + { 1, 7,11, 8, 0, 2,10, 5, 6,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1, -1}, + {2, 4, 4, 6,11, 3, 5, 10, 5, 3, 2, -1, -1, -1, -1, -1, -1}}, + + {{5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1, -1}, + {1, 6, 2, 3, 8, 9, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 185 */ + {{9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 9, 5, 6, 2, 0,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1, -1}, + {1, 7, 1, 5, 6, 2, 3, 8, 0, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 1, 5, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1, -1}, + {1, 7, 1, 3, 8, 9, 5, 6,10, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1, -1}, + { 2, 4, 4, 5, 6, 0, 9, 10, 1, 0, 6, -1, -1, -1, -1, -1, -1}}, + + /* 190 */ + {{0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 3,10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 4, 5,10, 11, 7,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 4, 3, 5,10,11, 7, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}}, + + {{5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + { 2, 4, 3, 5, 10, 11, 7, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}}, + + /* 195 */ + {{10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1, -1}, + { 2, 4, 4, 10, 11, 7, 5, 1, 9, 8, 3, -1, -1, -1, -1, -1, -1}}, + + {{11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 7, 5, 1, 2,11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1, -1}, + {2, 3, 5, 0, 8, 3, 2,11, 7, 5,1, -1, -1, -1, -1, -1, -1}}, + + {{9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1, -1}, + {1, 6, 2,11, 7, 5, 9, 0,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1, -1}, + {1, 7, 7, 5, 9, 8, 3, 2,11,-1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 200 */ + {{2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3, 7, 5,10, 2,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1, -1}, + {1, 6, 5,10, 2, 0, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1, -1}, + {2, 3, 5, 9, 0, 1, 10, 2, 3, 7, 5, -1, -1, -1, -1, -1, -1}}, + + {{9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1, -1}, + {1, 7, 9, 8, 7, 5,10, 2, 1,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 3, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 205 */ + {{0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 0, 8, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 9, 0, 3, 7, 5,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 7, 5, 9, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 10,11, 8, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1, -1}, + {1, 6, 0, 4, 5,10,11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 210 */ + {{0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1, -1}, + {2, 3, 5, 0, 1, 9, 4, 5, 10, 11, 8, -1, -1, -1, -1, -1, -1}}, + + {{10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1, -1}, + { 1, 7,10, 11, 3, 1, 9,4, 5,-1, -1, -1, -1, -1, -1, -1}}, + + {{2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1, -1}, + {1, 6, 2,11, 8, 4, 5, 1,-1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1, -1}, + {1, 7, 0, 4, 5, 1, 2, 11, 3,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1, -1}, + {1, 7, 0, 2,11, 8, 4, 5, 9, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 215 */ + {{9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 3, 5, 10, 4, 5, 3, 8,-1, -1, -1, -1, -1, -1}}, + + {{5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 5,10, 2, 0, 4,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1, -1}, + {3, 4, 4, 3, 3, 5, 10, 2, 8, 4, 5, 3, 0, 1, 9, -1, -1}}, + + {{5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1, -1}, + {1, 6,10, 2, 1, 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 220 */ + {{8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 4, 5, 1, 3,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 0, 4, 5, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1, -1}, + {2, 4, 4, 0, 3, 5, 9, 8, 4, 5, 3, -1, -1, -1, -1, -1, -1}}, + + {{9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 9,10, 11, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 225 */ + {{0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1, -1}, + {2, 3, 5, 0, 8, 3, 7, 4, 9, 10, 11, -1, -1, -1, -1, -1, -1}}, + + {{1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1, -1}, + {1, 6, 1, 10,11, 7, 4, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1, -1}, + {1, 7, 3, 1,10,11, 7, 4, 8, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 11, 9, 1, 4, 9, 11, 7, -1, -1, -1, -1, -1, -1}}, + + {{9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1, -1}, + {3, 4, 4, 3, 1, 2, 11, 9, 7, 4, 9,11, 8, 3, 0, 8, 3}}, + + /* 230 */ + {{11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1, -1}, + { 1, 5, 11, 7, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1, -1}, + { 2, 4, 4, 11, 7, 4, 2, 3, 2, 4, 8,-1, -1, -1, -1, -1, -1}}, + + {{2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1, -1}, + {1, 6, 2, 3, 7, 4, 9,10, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1, -1}, + {1, 7, 9,10, 2, 0, 8, 7, 4,-1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1, -1}, + {1, 7, 3, 7, 4, 0, 1,10, 2, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 235 */ + {{1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 3, 1,10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 4, 9, 1, 3, 7,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1, -1}, + {2, 4, 4, 8, 7, 1, 0, 4, 9, 1, 7, -1, -1, -1, -1, -1, -1}}, + + {{4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 3, 7, 4, 0,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 240 */ + {{9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 8, 9, 10,11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3, 0, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 0, 1, 10,11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 3, 1,10, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 1, 2, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 245 */ + {{3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1, -1}, + {2, 4, 4, 2,11, 9, 1, 3, 0, 9, 11, -1, -1, -1, -1, -1,-1}}, + + {{0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 0, 2,11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 2, 3, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 2, 0, 9,10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + /* 250 */ + {{2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1, -1}, + {2, 4, 4, 2, 3, 8, 10, 1, 10, 8, 0, -1, -1, -1, -1, -1, -1}}, + + {{1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 4, 1, 3, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, + + {{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}} +}; + + +//----------------------------------------------------------------------------- + + +const int polyTable[8][16] = +{ + {-1}, + {-1}, + {-1}, + {0, 1, 2, -1}, + {0, 1, 2, 2, 3, 0, -1}, + {0, 1, 2, 0, 2, 4, 4, 2, 3, -1}, + {0, 1, 2, 2, 3, 4, 4, 5, 0, 0, 2, 4, -1}, + {0, 1, 5, 0, 5, 6, 1, 2, 5, 4, 5, 3, 2, 3, 5, -1} +}; + + +//============================================================================= + + +//============================================================================= +#endif // ISOEX_MC_TABLES_HH defined +//============================================================================= diff --git a/src/igl/copyleft/offset_surface.cpp b/src/igl/copyleft/offset_surface.cpp new file mode 100644 index 000000000..c9e90553f --- /dev/null +++ b/src/igl/copyleft/offset_surface.cpp @@ -0,0 +1,64 @@ +#include "offset_surface.h" +#include "marching_cubes.h" +#include "../voxel_grid.h" +#include "../signed_distance.h" +#include "../flood_fill.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename isolevelType, + typename DerivedSV, + typename DerivedSF, + typename DerivedGV, + typename Derivedside, + typename DerivedS> +void igl::copyleft::offset_surface( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const isolevelType isolevel, + const typename Derivedside::Scalar s, + const SignedDistanceType & signed_distance_type, + Eigen::PlainObjectBase & SV, + Eigen::PlainObjectBase & SF, + Eigen::PlainObjectBase & GV, + Eigen::PlainObjectBase & side, + Eigen::PlainObjectBase & S) +{ + typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedF::Scalar Index; + { + Eigen::AlignedBox box; + typedef Eigen::Matrix RowVector3S; + assert(V.cols() == 3 && "V must contain positions in 3D"); + RowVector3S min_ext = V.colwise().minCoeff().array() - isolevel; + RowVector3S max_ext = V.colwise().maxCoeff().array() + isolevel; + box.extend(min_ext.transpose()); + box.extend(max_ext.transpose()); + igl::voxel_grid(box,s,1,GV,side); + } + + const Scalar h = + (GV.col(0).maxCoeff()-GV.col(0).minCoeff())/((Scalar)(side(0)-1)); + const Scalar lower_bound = isolevel-sqrt(3.0)*h; + const Scalar upper_bound = isolevel+sqrt(3.0)*h; + { + Eigen::Matrix I; + Eigen::Matrix C,N; + igl::signed_distance( + GV,V,F,signed_distance_type,lower_bound,upper_bound,S,I,C,N); + } + igl::flood_fill(side,S); + + DerivedS SS = S.array()-isolevel; + igl::copyleft::marching_cubes(SS,GV,side(0),side(1),side(2),SV,SF); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::copyleft::offset_surface, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::Matrix::Scalar, igl::SignedDistanceType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::offset_surface, Eigen::Matrix, float, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, float, Eigen::Matrix::Scalar, igl::SignedDistanceType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::copyleft::offset_surface, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::Matrix::Scalar, igl::SignedDistanceType const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/offset_surface.h b/src/igl/copyleft/offset_surface.h new file mode 100644 index 000000000..7c6307378 --- /dev/null +++ b/src/igl/copyleft/offset_surface.h @@ -0,0 +1,54 @@ +#ifndef IGL_COPYLEFT_OFFSET_SURFACE_H +#define IGL_COPYLEFT_OFFSET_SURFACE_H +#include "../igl_inline.h" +#include "../signed_distance.h" +#include + +namespace igl +{ + namespace copyleft + { + // Compute a triangulated offset surface using matching cubes on a grid of + // signed distance values from the input triangle mesh. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh triangle indices into V + // isolevel iso level to extract (signed distance: negative inside) + // s number of grid cells along longest side (controls resolution) + // signed_distance_type type of signing to use (see + // ../signed_distance.h) + // Outputs: + // SV #SV by 3 list of output surface mesh vertex positions + // SF #SF by 3 list of output mesh triangle indices into SV + // GV #GV=side(0)*side(1)*side(2) by 3 list of grid cell centers + // side list of number of grid cells in x, y, and z directions + // S #GV by 3 list of signed distance values _near_ `isolevel` ("far" + // from `isolevel` these values are incorrect) + // + template < + typename DerivedV, + typename DerivedF, + typename isolevelType, + typename DerivedSV, + typename DerivedSF, + typename DerivedGV, + typename Derivedside, + typename DerivedS> + void offset_surface( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const isolevelType isolevel, + const typename Derivedside::Scalar s, + const SignedDistanceType & signed_distance_type, + Eigen::PlainObjectBase & SV, + Eigen::PlainObjectBase & SF, + Eigen::PlainObjectBase & GV, + Eigen::PlainObjectBase & side, + Eigen::PlainObjectBase & S); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "offset_surface.cpp" +#endif +#endif diff --git a/src/igl/copyleft/opengl2/render_to_tga.cpp b/src/igl/copyleft/opengl2/render_to_tga.cpp new file mode 100644 index 000000000..6d0f97ea1 --- /dev/null +++ b/src/igl/copyleft/opengl2/render_to_tga.cpp @@ -0,0 +1,87 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "render_to_tga.h" +#include "tga.h" + +#include "../../opengl2/gl.h" + +#include + +IGL_INLINE bool igl::opengl::render_to_tga( + const std::string tga_file, + const int width, + const int height, + const bool alpha) +{ + + size_t components = 3; + GLenum format = GL_BGR; + if(alpha) + { + format = GL_BGRA; + components = 4; + } + GLubyte * cmap = NULL; + + // OpenGL by default tries to read data in multiples of 4, if our data is + // only RGB or BGR and the width is not divible by 4 then we need to alert + // opengl + if((width % 4) != 0 && + (format == GL_RGB || + format == GL_BGR)) + { + glPixelStorei(GL_PACK_ALIGNMENT, 1); + } + GLubyte *pixels; + pixels = (unsigned char *) malloc (width * height * components); + glReadPixels( + 0, + 0, + width, + height, + format, + GL_UNSIGNED_BYTE, + pixels); + + // set up generic image struct + gliGenericImage * genericImage; + genericImage = (gliGenericImage*) malloc(sizeof(gliGenericImage)); + genericImage->width = width; + genericImage->height = height; + genericImage->format = format; + genericImage->components = components; + genericImage->pixels = pixels; + // CMAP is not supported, but we need to put something here + genericImage->cmapEntries = 0; + genericImage->cmapFormat = GL_BGR; + genericImage->cmap = cmap; + + // write pixels to tga file + FILE * imgFile; + // "-" as output file name is code for write to stdout + if(tga_file.compare("-") == 0) + { + imgFile = stdout; + }else{ + imgFile = fopen(tga_file.c_str(),"w"); + if(NULL==imgFile) + { + printf("IOError: %s could not be opened...\n",tga_file.c_str()); + return false; + } + } + + writeTGA(genericImage,imgFile); + + free(genericImage); + return fclose(imgFile) == 0; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/copyleft/opengl2/render_to_tga.h b/src/igl/copyleft/opengl2/render_to_tga.h new file mode 100644 index 000000000..d792c1e31 --- /dev/null +++ b/src/igl/copyleft/opengl2/render_to_tga.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_RENDER_TO_TGA_H +#define IGL_OPENGL_RENDER_TO_TGA_H +#include "../../igl_inline.h" +#include + +namespace igl +{ + namespace opengl + { + // Render current open GL image to .tga file + // Inputs: + // tga_file path to output .tga file + // width width of scene and resulting image + // height height of scene and resulting image + /// alpha whether to include alpha channel + // Returns true only if no errors occurred + // + // See also: png/render_to_png which is slower but writes .png files + IGL_INLINE bool render_to_tga( + const std::string tga_file, + const int width, + const int height, + const bool alpha); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "render_to_tga.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/opengl2/texture_from_tga.cpp b/src/igl/copyleft/opengl2/texture_from_tga.cpp new file mode 100644 index 000000000..717a6734c --- /dev/null +++ b/src/igl/copyleft/opengl2/texture_from_tga.cpp @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "texture_from_tga.h" +#include "tga.h" +#include + +IGL_INLINE bool igl::opengl::texture_from_tga(const std::string tga_file, GLuint & id) +{ + using namespace std; + + // read pixels to tga file + FILE * imgFile; + // "-" as input file name is code for read from stdin + imgFile = fopen(tga_file.c_str(),"r"); + if(NULL==imgFile) + { + printf("IOError: %s could not be opened...",tga_file.c_str()); + return false; + } + + // gliReadTGA annoyingly uses char * instead of const char * + size_t len = tga_file.length(); + char* tga_file_char = new char [ len + 1 ]; + strcpy( tga_file_char, tga_file.c_str() ); + // read image + gliGenericImage* img = gliReadTGA(imgFile, tga_file_char, 0, 0); + // clean up filename buffer + delete[] tga_file_char; + fclose( imgFile ); + + // set up texture mapping parameters and generate texture id + glGenTextures(1,&id); + glBindTexture(GL_TEXTURE_2D, id); + // Texture parameters + float empty[] = {1.0f,1.0f,1.0f,0.0f}; + glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,empty); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + // GL_LINEAR_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + + // OpenGL by default tries to read data in multiples of 4, if our data is + // only RGB or BGR and the width is not divible by 4 then we need to alert + // opengl + if((img->width % 4) != 0 && + (img->format == GL_RGB || + img->format == GL_BGR)) + { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + + // Load texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->width, + img->height, 0, img->format, GL_UNSIGNED_BYTE, + img->pixels); + return id; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/copyleft/opengl2/texture_from_tga.h b/src/igl/copyleft/opengl2/texture_from_tga.h new file mode 100644 index 000000000..8a5f704c7 --- /dev/null +++ b/src/igl/copyleft/opengl2/texture_from_tga.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_TEXTURE_FROM_TGA_H +#define IGL_OPENGL_TEXTURE_FROM_TGA_H +#include "../../igl_inline.h" +#include "../../opengl2/gl.h" +#include + +namespace igl +{ + namespace opengl + { + // Read an image from a .tga file and use it as a texture + // + // Input: + // tga_file path to .tga file + // Output: + // id of generated openGL texture + // Returns true on success, false on failure + IGL_INLINE bool texture_from_tga(const std::string tga_file, GLuint & id); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "texture_from_tga.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/opengl2/tga.cpp b/src/igl/copyleft/opengl2/tga.cpp new file mode 100644 index 000000000..2f1f99022 --- /dev/null +++ b/src/igl/copyleft/opengl2/tga.cpp @@ -0,0 +1,549 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// WARNING +// +// THIS DOES NOT DEAL WITH VERTICALLY FLIPPED DATA CORRECTLY +// +//////////////////////////////////////////////////////////////////////////////// + +/* This file is derived from (actually an earlier version of)... */ + +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * $Id: tga.cpp,v 1.1.2.5 2007-05-10 02:10:07 elif Exp $ + * TrueVision Targa loading and saving file filter for the Gimp. + * Targa code Copyright (C) 1997 Raphael FRANCOIS and Gordon Matzigkeit + * + * The Targa reading and writing code was written from scratch by + * Raphael FRANCOIS and Gordon Matzigkeit + * based on the TrueVision TGA File Format + * Specification, Version 2.0: + * + * + * + * It does not contain any code written for other TGA file loaders. + * Not even the RLE handling. ;) + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "tga.h" +#include "../../opengl2/glext.h" +#include +#include +#include +#include + +static char error[256]; +static unsigned int _verbose = 0; +static int totbytes = 0; + +typedef struct { + unsigned char *statebuf; + int statelen; + int laststate; +} RLEstate; + +IGL_INLINE static int +std_fread(RLEstate * /*rleInfo*/, unsigned char *buf, size_t datasize, size_t nelems, FILE *fp) +{ + if (_verbose > 1) { + totbytes += nelems * datasize; + printf("TGA: std_fread %d (total %d)\n", + (int)(nelems * datasize), totbytes); + } + return fread(buf, datasize, nelems, fp); +} + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +#define RLE_PACKETSIZE 0x80 + +/* Decode a bufferful of file. */ +IGL_INLINE static int +rle_fread(RLEstate *rleInfo, unsigned char *vbuf, size_t datasize, size_t nelems, FILE *fp) +{ + + unsigned char *buf = vbuf; + int j, k; + int buflen, count, bytes, curbytes; + unsigned char *p; + + /* Scale the buffer length. */ + buflen = nelems * datasize; + + j = 0; + curbytes = totbytes; + while (j < buflen) { + if (rleInfo->laststate < rleInfo->statelen) { + /* Copy bytes from our previously decoded buffer. */ + bytes = MIN(buflen - j, rleInfo->statelen - rleInfo->laststate); + memcpy(buf + j, rleInfo->statebuf + rleInfo->laststate, bytes); + j += bytes; + rleInfo->laststate += bytes; + + /* If we used up all of our state bytes, then reset them. */ + if (rleInfo->laststate >= rleInfo->statelen) { + rleInfo->laststate = 0; + rleInfo->statelen = 0; + } + + /* If we filled the buffer, then exit the loop. */ + if (j >= buflen) break; + } + + /* Decode the next packet. */ + count = fgetc(fp); + if (count == EOF) { + if (_verbose) printf("TGA: hit EOF while looking for count\n"); + return j / datasize; + } + + /* Scale the byte length to the size of the data. */ + bytes = ((count & ~RLE_PACKETSIZE) + 1) * datasize; + + if (j + bytes <= buflen) { + /* We can copy directly into the image buffer. */ + p = buf + j; + } else { +#ifdef PROFILE + printf("TGA: needed to use statebuf for %d bytes\n", buflen - j); +#endif + /* Allocate the state buffer if we haven't already. */ + if (!rleInfo->statebuf) { + rleInfo->statebuf = (unsigned char *) malloc(RLE_PACKETSIZE * datasize); + } + p = rleInfo->statebuf; + } + + if (count & RLE_PACKETSIZE) { + /* Fill the buffer with the next value. */ + if (fread(p, datasize, 1, fp) != 1) { + if (_verbose) { + printf("TGA: EOF while reading %d/%d element RLE packet\n", + bytes, (int)datasize); + } + return j / datasize; + } + + /* Optimized case for single-byte encoded data. */ + if (datasize == 1) { + memset(p + 1, *p, bytes - 1); + } else { + for (k = datasize; k < bytes; k += datasize) { + memcpy(p + k, p, datasize); + } + } + } else { + /* Read in the buffer. */ + if (fread(p, bytes, 1, fp) != 1) { + if (_verbose) { + printf("TGA: EOF while reading %d/%d element raw packet\n", + bytes, (int)datasize); + } + return j / datasize; + } + } + + if (_verbose > 1) { + totbytes += bytes; + if (_verbose > 2) { + printf("TGA: %s packet %d/%d\n", + (count & RLE_PACKETSIZE) ? "RLE" : "raw", + bytes, totbytes); + } + } + + /* We may need to copy bytes from the state buffer. */ + if (p == rleInfo->statebuf) { + rleInfo->statelen = bytes; + } else { + j += bytes; + } + } + + if (_verbose > 1) { + printf("TGA: rle_fread %d/%d (total %d)\n", + (int) ( nelems * datasize), totbytes - curbytes, totbytes); + } + return nelems; +} + +IGL_INLINE igl::opengl::gliGenericImage * +igl::opengl::gliReadTGA(FILE *fp, char *name, int /*hflip*/, int vflip) +{ + igl::opengl::TgaHeader tgaHeader; + igl::opengl::TgaFooter tgaFooter; + char horzrev, vertrev; + int width, height, bpp; + int start, end, dir; + int i, j, k; + int pelbytes, wbytes; + GLenum format; + int components; + RLEstate rleRec; + RLEstate *rleInfo; + int rle; + int index, colors, length; + GLubyte *cmap, *pixels, *data; + int (*myfread)(RLEstate *rleInfo, unsigned char*, size_t, size_t, FILE*); + igl::opengl::gliGenericImage *genericImage; + + /* Check the footer. */ + if (fseek(fp, 0L - sizeof(tgaFooter), SEEK_END) + || fread(&tgaFooter, sizeof(tgaFooter), 1, fp) != 1) { + sprintf(error, "TGA: Cannot read footer from \"%s\"", name); + if (_verbose) printf("%s\n", error); + return NULL; + } + + /* Check the signature. */ + if (memcmp(tgaFooter.signature, TGA_SIGNATURE, + sizeof(tgaFooter.signature)) == 0) { + if (_verbose) printf("TGA: found New TGA\n"); + } else { + if (_verbose) printf("TGA: found Original TGA\n"); + } + + if (fseek(fp, 0, SEEK_SET) || + fread(&tgaHeader, sizeof(tgaHeader), 1, fp) != 1) { + sprintf(error, "TGA: Cannot read header from \"%s\"", name); + if (_verbose) printf("%s\n", error); + return NULL; + } + + if (_verbose && tgaHeader.idLength) { + char *idString = (char*) malloc(tgaHeader.idLength); + + if (fread(idString, tgaHeader.idLength, 1, fp) != 1) { + sprintf(error, "TGA: Cannot read ID field in \"%s\"", name); + printf("%s\n", error); + } else { + printf("TGA: ID field: \"%*s\"\n", tgaHeader.idLength, idString); + } + free(idString); + } else { + /* Skip the image ID field. */ + if (tgaHeader.idLength && fseek(fp, tgaHeader.idLength, SEEK_CUR)) { + sprintf(error, "TGA: Cannot skip ID field in \"%s\"", name); + if (_verbose) printf("%s\n", error); + return NULL; + } + } + + /* Reassemble the multi-byte values correctly, regardless of + host endianness. */ + width = (tgaHeader.widthHi << 8) | tgaHeader.widthLo; + height = (tgaHeader.heightHi << 8) | tgaHeader.heightLo; + bpp = tgaHeader.bpp; + if (_verbose) { + printf("TGA: width=%d, height=%d, bpp=%d\n", width, height, bpp); + } + + horzrev = tgaHeader.descriptor & TGA_DESC_HORIZONTAL; + vertrev = tgaHeader.descriptor & TGA_DESC_VERTICAL; + //vertrev=0; + +// // JASON - we can force this stuff if we want +// if( hflip ) +// horzrev = 1; + if( vflip ) + vertrev = 1; + + if (_verbose && horzrev) printf("TGA: horizontal reversed\n"); + if (_verbose && vertrev) printf("TGA: vertical reversed\n"); + + rle = 0; + switch (tgaHeader.imageType) { + case TGA_TYPE_MAPPED_RLE: + rle = 1; + if (_verbose) printf("TGA: run-length encoded\n"); + case TGA_TYPE_MAPPED: + /* Test for alpha channel. */ + format = GL_COLOR_INDEX; + components = 1; + if (_verbose) { + printf("TGA: %d bit indexed image (%d bit palette)\n", + tgaHeader.colorMapSize, bpp); + } + break; + + case TGA_TYPE_GRAY_RLE: + rle = 1; + if (_verbose) printf("TGA: run-length encoded\n"); + case TGA_TYPE_GRAY: + format = GL_LUMINANCE; + components = 1; + if (_verbose) printf("TGA: %d bit grayscale image\n", bpp); + break; + + case TGA_TYPE_COLOR_RLE: + rle = 1; + if (_verbose) printf("TGA: run-length encoded\n"); + case TGA_TYPE_COLOR: + /* Test for alpha channel. */ + if (bpp == 32) { + format = GL_BGRA_EXT; + components = 4; + if (_verbose) { + printf("TGA: %d bit color image with alpha channel\n", bpp); + } + } else { + format = GL_BGR_EXT; + components = 3; + if (_verbose) printf("TGA: %d bit color image\n", bpp); + } + break; + + default: + sprintf(error, + "TGA: unrecognized image type %d\n", tgaHeader.imageType); + if (_verbose) printf("%s\n", error); + return NULL; + } + + if ((format == GL_BGRA_EXT && bpp != 32) || + (format == GL_BGR_EXT && bpp != 24) || + ((format == GL_LUMINANCE || format == GL_COLOR_INDEX) && bpp != 8)) { + /* FIXME: We haven't implemented bit-packed fields yet. */ + fprintf(stderr, "bpp %d, format %x\n", bpp, (unsigned int)format); + sprintf(error, "TGA: channel sizes other than 8 bits are unimplemented"); + if (_verbose) printf("%s\n", error); + return NULL; + } + + /* Check that we have a color map only when we need it. */ + if (format == GL_COLOR_INDEX) { + if (tgaHeader.colorMapType != 1) { + sprintf(error, "TGA: indexed image has invalid color map type %d\n", + tgaHeader.colorMapType); + if (_verbose) printf("%s\n", error); + return NULL; + } + } else if (tgaHeader.colorMapType != 0) { + sprintf(error, "TGA: non-indexed image has invalid color map type %d\n", + tgaHeader.colorMapType); + if (_verbose) printf("%s\n", error); + return NULL; + } + + if (tgaHeader.colorMapType == 1) { + /* We need to read in the colormap. */ + index = (tgaHeader.colorMapIndexHi << 8) | tgaHeader.colorMapIndexLo; + length = (tgaHeader.colorMapLengthHi << 8) | tgaHeader.colorMapLengthLo; + + if (_verbose) { + printf("TGA: reading color map (%d + %d) * (%d / 8)\n", + index, length, tgaHeader.colorMapSize); + } + if (length == 0) { + sprintf(error, "TGA: invalid color map length %d", length); + if (_verbose) printf("%s\n", error); + return NULL; + } + if (tgaHeader.colorMapSize != 24) { + /* We haven't implemented bit-packed fields yet. */ + sprintf(error, "TGA: channel sizes other than 8 bits are unimplemented"); + if (_verbose) printf("%s\n", error); + return NULL; + } + + pelbytes = tgaHeader.colorMapSize / 8; + colors = length + index; + cmap = (GLubyte*)malloc (colors * pelbytes); + + /* Zero the entries up to the beginning of the map. */ + memset(cmap, 0, index * pelbytes); + + /* Read in the rest of the colormap. */ + if (fread(cmap, pelbytes, length, fp) != (size_t) length) { + sprintf(error, "TGA: error reading colormap (ftell == %ld)\n", + ftell (fp)); + if (_verbose) printf("%s\n", error); + return NULL; + } + + if (pelbytes >= 3) { + /* Rearrange the colors from BGR to RGB. */ + int tmp; + for (j = index; j < length * pelbytes; j += pelbytes) { + tmp = cmap[j]; + cmap[j] = cmap[j + 2]; + cmap[j + 2] = tmp; + } + } + } else { + colors = 0; + cmap = NULL; + } + + /* Allocate the data. */ + pelbytes = bpp / 8; + pixels = (unsigned char *) malloc (width * height * pelbytes); + + if (rle) { + rleRec.statebuf = 0; + rleRec.statelen = 0; + rleRec.laststate = 0; + rleInfo = &rleRec; + myfread = rle_fread; + } else { + rleInfo = NULL; + myfread = std_fread; + } + + wbytes = width * pelbytes; + + if (vertrev) { + start = 0; + end = height; + dir = 1; + } else { + /* We need to reverse loading order of rows. */ + start = height-1; + end = -1; + dir = -1; + } + + for (i = start; i != end; i += dir) { + data = pixels + i*wbytes; + + /* Suck in the data one row at a time. */ + if (myfread(rleInfo, data, pelbytes, width, fp) != width) { + /* Probably premature end of file. */ + if (_verbose) { + printf ("TGA: error reading (ftell == %ld, width=%d)\n", + ftell(fp), width); + } + return NULL; + } + + if (horzrev) { + /* We need to mirror row horizontally. */ + for (j = 0; j < width/2; j++) { + GLubyte tmp; + + for (k = 0; k < pelbytes; k++) { + tmp = data[j*pelbytes+k]; + data[j*pelbytes+k] = data[(width-j-1)*pelbytes+k]; + data[(width-j-1)*pelbytes+k] = tmp; + } + } + } + } + + if (rle) { + free(rleInfo->statebuf); + } + + if (fgetc (fp) != EOF) { + if (_verbose) printf ("TGA: too much input data, ignoring extra...\n"); + } + + genericImage = (igl::opengl::gliGenericImage*) malloc(sizeof(igl::opengl::gliGenericImage)); + genericImage->width = width; + genericImage->height = height; + genericImage->format = format; + genericImage->components = components; + genericImage->cmapEntries = colors; + genericImage->cmapFormat = GL_BGR_EXT; // XXX fix me + genericImage->cmap = cmap; + genericImage->pixels = pixels; + + return genericImage; +} + +IGL_INLINE int igl::opengl::gli_verbose(int new_verbose) +{ + _verbose = new_verbose; + return _verbose; +} + + + +// added 10/2005, Denis Zorin +// a very simple TGA output, supporting +// uncompressed luminance RGB and RGBA +// G22.2270 students: this is C (no C++) +// so this is not the style I would encourage +// you to use; I used it for consistency +// with the rest of the code in this file + + +// fixed header values for the subset of TGA we use for writing +unsigned char TGAHeaderColor[12] = + { 0,// 0 ID length = no id + 0,// 1 color map type = no color map + 2,// 2 image type = uncompressed true color + 0, 0, 0, 0, 0,// color map spec = empty + 0, 0, // x origin of image + 0, 0 // y origin of image + }; + +unsigned char TGAHeaderBW[12] = + { 0,// 0 ID length = no id + 0,// 1 color map type = no color map + 3,// 3 image type = uncompressed black and white + 0, 0, 0, 0, 0,// color map spec = empty + 0, 0, // x origin of image + 0, 0 // y origin of image + }; + +// this makes sure that +// image size is written in correct format +// and byte order (least first) +IGL_INLINE void write16bit(int n, FILE* fp) { + unsigned char bytes[] = { static_cast(n % 256), static_cast(n / 256) }; + fwrite(bytes, 2, sizeof(unsigned char),fp); +} + + + +IGL_INLINE void igl::opengl::writeTGA( igl::opengl::gliGenericImage* image, FILE *fp) { + + assert(!image->cmap); // we do not deal with color map images + + if(image->components == 3 || image->components == 4) + fwrite(TGAHeaderColor, 12, sizeof(unsigned char),fp); + else { + if(image->components == 1 ) + fwrite(TGAHeaderBW, 12, sizeof(unsigned char),fp); + else { fprintf(stderr,"Supported component number: 1,3 or 4\n"); exit(1); } + } + + write16bit(image->width,fp); + write16bit(image->height,fp); + switch (image->components ) { + case 1: + putc(8,fp); + break; + case 3: + putc(24,fp); + break; + case 4: + putc(32,fp); + break; + default: fprintf(stderr,"Supported component number: 1,3 or 4\n"); exit(1); + }; + + if(image-> components == 4) + putc(0x04,fp); // bottom left image (0x00) + 8 bit alpha (0x4) + else + putc(0x00,fp); + + fwrite(image->pixels, image->height*image->width*image->components, sizeof(char),fp); +} + diff --git a/src/igl/copyleft/opengl2/tga.h b/src/igl/copyleft/opengl2/tga.h new file mode 100644 index 000000000..b69f35496 --- /dev/null +++ b/src/igl/copyleft/opengl2/tga.h @@ -0,0 +1,106 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_TGA_H +#define IGL_OPENGL_TGA_H +#include "../../igl_inline.h" + +#include "../../opengl2/gl.h" +// See license in tga.cpp +/* tga.h - interface for TrueVision (TGA) image file loader */ +#include +#ifdef _WIN32 +#include +#endif + +namespace igl +{ +namespace opengl +{ + +typedef struct { + + GLsizei width; + GLsizei height; + GLint components; + GLenum format; + + GLsizei cmapEntries; + GLenum cmapFormat; + GLubyte *cmap; + + GLubyte *pixels; + +} gliGenericImage; + +typedef struct { + unsigned char idLength; + unsigned char colorMapType; + + /* The image type. */ +#define TGA_TYPE_MAPPED 1 +#define TGA_TYPE_COLOR 2 +#define TGA_TYPE_GRAY 3 +#define TGA_TYPE_MAPPED_RLE 9 +#define TGA_TYPE_COLOR_RLE 10 +#define TGA_TYPE_GRAY_RLE 11 + unsigned char imageType; + + /* Color Map Specification. */ + /* We need to separately specify high and low bytes to avoid endianness + and alignment problems. */ + unsigned char colorMapIndexLo, colorMapIndexHi; + unsigned char colorMapLengthLo, colorMapLengthHi; + unsigned char colorMapSize; + + /* Image Specification. */ + unsigned char xOriginLo, xOriginHi; + unsigned char yOriginLo, yOriginHi; + + unsigned char widthLo, widthHi; + unsigned char heightLo, heightHi; + + unsigned char bpp; + + /* Image descriptor. + 3-0: attribute bpp + 4: left-to-right ordering + 5: top-to-bottom ordering + 7-6: zero + */ +#define TGA_DESC_ABITS 0x0f +#define TGA_DESC_HORIZONTAL 0x10 +#define TGA_DESC_VERTICAL 0x20 + unsigned char descriptor; + +} TgaHeader; + +typedef struct { + unsigned int extensionAreaOffset; + unsigned int developerDirectoryOffset; +#define TGA_SIGNATURE "TRUEVISION-XFILE" + char signature[16]; + char dot; + char null; +} TgaFooter; + +IGL_INLINE extern gliGenericImage *gliReadTGA(FILE *fp, char *name, int hflip, int vflip); +IGL_INLINE int gli_verbose(int new_verbose); +IGL_INLINE extern int gliVerbose(int newVerbose); + +IGL_INLINE void writeTGA( gliGenericImage* image, FILE *fp); + + + +} // end of igl namespace +} + +#ifndef IGL_STATIC_LIBRARY +# include "tga.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/progressive_hulls.cpp b/src/igl/copyleft/progressive_hulls.cpp new file mode 100644 index 000000000..44738c8b2 --- /dev/null +++ b/src/igl/copyleft/progressive_hulls.cpp @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "progressive_hulls.h" +#include "progressive_hulls_cost_and_placement.h" +#include "../decimate.h" +#include "../max_faces_stopping_condition.h" +IGL_INLINE bool igl::copyleft::progressive_hulls( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J) +{ + int m = F.rows(); + Eigen::VectorXi I; + return decimate( + V, + F, + progressive_hulls_cost_and_placement, + max_faces_stopping_condition(m,(const int)m,max_m), + U, + G, + J, + I); +} diff --git a/src/igl/copyleft/progressive_hulls.h b/src/igl/copyleft/progressive_hulls.h new file mode 100644 index 000000000..54e0580a0 --- /dev/null +++ b/src/igl/copyleft/progressive_hulls.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_PROGRESSIVE_HULLS_H +#define IGL_COPYLEFT_PROGRESSIVE_HULLS_H +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace copyleft + { + // Assumes (V,F) is a closed manifold mesh + // Collapses edges until desired number of faces is achieved but ensures + // that new vertices are placed outside all previous meshes as per + // "progressive hulls" in "Silhouette clipping" [Sander et al. 2000]. + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3 list of face indices into V. + // max_m desired number of output faces + // Outputs: + // U #U by dim list of output vertex posistions (can be same ref as V) + // G #G by 3 list of output face indices into U (can be same ref as G) + // J #G list of indices into F of birth faces + // Returns true if m was reached (otherwise #G > m) + IGL_INLINE bool progressive_hulls( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "progressive_hulls.cpp" +#endif +#endif diff --git a/src/igl/copyleft/progressive_hulls_cost_and_placement.cpp b/src/igl/copyleft/progressive_hulls_cost_and_placement.cpp new file mode 100644 index 000000000..c9ab79ced --- /dev/null +++ b/src/igl/copyleft/progressive_hulls_cost_and_placement.cpp @@ -0,0 +1,107 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "progressive_hulls_cost_and_placement.h" +#include "quadprog.h" +#include "../unique.h" +#include "../circulation.h" +#include +#include +#include + +IGL_INLINE void igl::copyleft::progressive_hulls_cost_and_placement( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + double & cost, + Eigen::RowVectorXd & p) +{ + using namespace Eigen; + using namespace std; + // Controls the amount of quadratic energy to add (too small will introduce + // instabilities and flaps) + const double w = 0.1; + + assert(V.cols() == 3 && "V.cols() should be 3"); + // Gather list of unique face neighbors + vector Nall = circulation(e, true,F,E,EMAP,EF,EI); + vector Nother= circulation(e,false,F,E,EMAP,EF,EI); + Nall.insert(Nall.end(),Nother.begin(),Nother.end()); + vector N; + igl::unique(Nall,N); + // Gather: + // A #N by 3 normals scaled by area, + // D #N determinants of matrix formed by points as columns + // B #N point on plane dot normal + MatrixXd A(N.size(),3); + VectorXd D(N.size()); + VectorXd B(N.size()); + //cout<<"N=["; + for(int i = 0;i= B + // A x - B >=0 + // This is annoyingly necessary. Seems the solver is letting some garbage + // slip by. + success = success && ((A*x-B).minCoeff()>-1e-10); + if(success) + { + p = x.transpose(); + //assert(cost>=0 && "Cost should be positive"); + }else + { + cost = std::numeric_limits::infinity(); + //VectorXi NM; + //igl::list_to_matrix(N,NM); + //cout< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_PROGRESSIVE_HULLS_COST_AND_PLACEMENT_H +#define IGL_COPYLEFT_PROGRESSIVE_HULLS_COST_AND_PLACEMENT_H +#include +#include "../igl_inline.h" +namespace igl +{ + namespace copyleft + { + // A "cost and placement" compatible with `igl::decimate` implementing the + // "progressive hulls" algorithm in "Silhouette clipping" [Sander et al. + // 2000]. This implementation fixes an issue that the original linear + // program becomes unstable for flat patches by introducing a small + // quadratic energy term pulling the collapsed edge toward its midpoint. + // This function is not really meant to be called directly but rather + // passed to `igl::decimate` as a handle. + // + // Inputs: + // e index of edge to be collapsed + // V #V by 3 list of vertex positions + // F #F by 3 list of faces indices into V + // E #E by 3 list of edges indices into V + // EMAP #F*3 list of indices into E, mapping each directed edge to unique + // unique edge in E + // EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // Outputs: + // cost cost of collapsing edge e + // p position to place collapsed vertex + // + IGL_INLINE void progressive_hulls_cost_and_placement( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + double & cost, + Eigen::RowVectorXd & p); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "progressive_hulls_cost_and_placement.cpp" +#endif +#endif diff --git a/src/igl/copyleft/quadprog.cpp b/src/igl/copyleft/quadprog.cpp new file mode 100644 index 000000000..4c565a0c3 --- /dev/null +++ b/src/igl/copyleft/quadprog.cpp @@ -0,0 +1,599 @@ +#include "quadprog.h" +#include +/* + FILE eiquadprog.hh + + NOTE: this is a modified of uQuadProg++ package, working with Eigen data structures. + uQuadProg++ is itself a port made by Angelo Furfaro of QuadProg++ originally developed by + Luca Di Gaspero, working with ublas data structures. + + The quadprog_solve() function implements the algorithm of Goldfarb and Idnani + for the solution of a (convex) Quadratic Programming problem +by means of a dual method. + +The problem is in the form: + +min 0.5 * x G x + g0 x +s.t. + CE^T x + ce0 = 0 + CI^T x + ci0 >= 0 + + The matrix and vectors dimensions are as follows: + G: n * n + g0: n + + CE: n * p + ce0: p + + CI: n * m + ci0: m + + x: n + + The function will return the cost of the solution written in the x vector or + std::numeric_limits::infinity() if the problem is infeasible. In the latter case + the value of the x vector is not correct. + + References: D. Goldfarb, A. Idnani. A numerically stable dual method for solving + strictly convex quadratic programs. Mathematical Programming 27 (1983) pp. 1-33. + + Notes: + 1. pay attention in setting up the vectors ce0 and ci0. + If the constraints of your problem are specified in the form + A^T x = b and C^T x >= d, then you should set ce0 = -b and ci0 = -d. + 2. The matrix G is modified within the function since it is used to compute + the G = L^T L cholesky factorization for further computations inside the function. + If you need the original matrix G you should make a copy of it and pass the copy + to the function. + + + The author will be grateful if the researchers using this software will + acknowledge the contribution of this modified function and of Di Gaspero's + original version in their research papers. + + +LICENSE + +Copyright (2010) Gael Guennebaud +Copyright (2008) Angelo Furfaro +Copyright (2006) Luca Di Gaspero + + +This file is a porting of QuadProg++ routine, originally developed +by Luca Di Gaspero, exploiting uBlas data structures for vectors and +matrices instead of native C++ array. + +uquadprog 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 2 of the License, or +(at your option) any later version. + +uquadprog 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 uquadprog; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include + +IGL_INLINE bool igl::copyleft::quadprog( + const Eigen::MatrixXd & G, + const Eigen::VectorXd & g0, + const Eigen::MatrixXd & CE, + const Eigen::VectorXd & ce0, + const Eigen::MatrixXd & CI, + const Eigen::VectorXd & ci0, + Eigen::VectorXd& x) +{ + using namespace Eigen; + typedef double Scalar; + const auto distance = [](Scalar a, Scalar b)->Scalar + { + Scalar a1, b1, t; + a1 = std::abs(a); + b1 = std::abs(b); + if (a1 > b1) + { + t = (b1 / a1); + return a1 * std::sqrt(1.0 + t * t); + } + else + if (b1 > a1) + { + t = (a1 / b1); + return b1 * std::sqrt(1.0 + t * t); + } + return a1 * std::sqrt(2.0); + }; + const auto compute_d = [](VectorXd &d, const MatrixXd& J, const VectorXd& np) + { + d = J.adjoint() * np; + }; + + const auto update_z = + [](VectorXd& z, const MatrixXd& J, const VectorXd& d, int iq) + { + z = J.rightCols(z.size()-iq) * d.tail(d.size()-iq); + }; + + const auto update_r = + [](const MatrixXd& R, VectorXd& r, const VectorXd& d, int iq) + { + r.head(iq) = + R.topLeftCorner(iq,iq).triangularView().solve(d.head(iq)); + }; + + const auto add_constraint = [&distance]( + MatrixXd& R, + MatrixXd& J, + VectorXd& d, + int& iq, + double& R_norm)->bool + { + int n=J.rows(); +#ifdef TRACE_SOLVER + std::cerr << "Add constraint " << iq << '/'; +#endif + int i, j, k; + double cc, ss, h, t1, t2, xny; + + /* we have to find the Givens rotation which will reduce the element + d(j) to zero. + if it is already zero we don't have to do anything, except of + decreasing j */ + for (j = n - 1; j >= iq + 1; j--) + { + /* The Givens rotation is done with the matrix (cc cs, cs -cc). + If cc is one, then element (j) of d is zero compared with element + (j - 1). Hence we don't have to do anything. + If cc is zero, then we just have to switch column (j) and column (j - 1) + of J. Since we only switch columns in J, we have to be careful how we + update d depending on the sign of gs. + Otherwise we have to apply the Givens rotation to these columns. + The i - 1 element of d has to be updated to h. */ + cc = d(j - 1); + ss = d(j); + h = distance(cc, ss); + if (h == 0.0) + continue; + d(j) = 0.0; + ss = ss / h; + cc = cc / h; + if (cc < 0.0) + { + cc = -cc; + ss = -ss; + d(j - 1) = -h; + } + else + d(j - 1) = h; + xny = ss / (1.0 + cc); + for (k = 0; k < n; k++) + { + t1 = J(k,j - 1); + t2 = J(k,j); + J(k,j - 1) = t1 * cc + t2 * ss; + J(k,j) = xny * (t1 + J(k,j - 1)) - t2; + } + } + /* update the number of constraints added*/ + iq++; + /* To update R we have to put the iq components of the d vector + into column iq - 1 of R + */ + R.col(iq-1).head(iq) = d.head(iq); +#ifdef TRACE_SOLVER + std::cerr << iq << std::endl; +#endif + + if (std::abs(d(iq - 1)) <= std::numeric_limits::epsilon() * R_norm) + { + // problem degenerate + return false; + } + R_norm = std::max(R_norm, std::abs(d(iq - 1))); + return true; + }; + + const auto delete_constraint = [&distance]( + MatrixXd& R, + MatrixXd& J, + VectorXi& A, + VectorXd& u, + int p, + int& iq, + int l) + { + int n = R.rows(); +#ifdef TRACE_SOLVER + std::cerr << "Delete constraint " << l << ' ' << iq; +#endif + int i, j, k, qq; + double cc, ss, h, xny, t1, t2; + + /* Find the index qq for active constraint l to be removed */ + for (i = p; i < iq; i++) + if (A(i) == l) + { + qq = i; + break; + } + + /* remove the constraint from the active set and the duals */ + for (i = qq; i < iq - 1; i++) + { + A(i) = A(i + 1); + u(i) = u(i + 1); + R.col(i) = R.col(i+1); + } + + A(iq - 1) = A(iq); + u(iq - 1) = u(iq); + A(iq) = 0; + u(iq) = 0.0; + for (j = 0; j < iq; j++) + R(j,iq - 1) = 0.0; + /* constraint has been fully removed */ + iq--; +#ifdef TRACE_SOLVER + std::cerr << '/' << iq << std::endl; +#endif + + if (iq == 0) + return; + + for (j = qq; j < iq; j++) + { + cc = R(j,j); + ss = R(j + 1,j); + h = distance(cc, ss); + if (h == 0.0) + continue; + cc = cc / h; + ss = ss / h; + R(j + 1,j) = 0.0; + if (cc < 0.0) + { + R(j,j) = -h; + cc = -cc; + ss = -ss; + } + else + R(j,j) = h; + + xny = ss / (1.0 + cc); + for (k = j + 1; k < iq; k++) + { + t1 = R(j,k); + t2 = R(j + 1,k); + R(j,k) = t1 * cc + t2 * ss; + R(j + 1,k) = xny * (t1 + R(j,k)) - t2; + } + for (k = 0; k < n; k++) + { + t1 = J(k,j); + t2 = J(k,j + 1); + J(k,j) = t1 * cc + t2 * ss; + J(k,j + 1) = xny * (J(k,j) + t1) - t2; + } + } + }; + + int i, j, k, l; /* indices */ + int ip, me, mi; + int n=g0.size(); int p=ce0.size(); int m=ci0.size(); + MatrixXd R(G.rows(),G.cols()), J(G.rows(),G.cols()); + + LLT chol(G.cols()); + + VectorXd s(m+p), z(n), r(m + p), d(n), np(n), u(m + p); + VectorXd x_old(n), u_old(m + p); + double f_value, psi, c1, c2, sum, ss, R_norm; + const double inf = std::numeric_limits::infinity(); + double t, t1, t2; /* t is the step length, which is the minimum of the partial step length t1 + * and the full step length t2 */ + VectorXi A(m + p), A_old(m + p), iai(m + p); + int q; + int iq, iter = 0; + std::vector iaexcl(m + p); + + me = p; /* number of equality constraints */ + mi = m; /* number of inequality constraints */ + q = 0; /* size of the active set A (containing the indices of the active constraints) */ + + /* + * Preprocessing phase + */ + + /* compute the trace of the original matrix G */ + c1 = G.trace(); + + /* decompose the matrix G in the form LL^T */ + chol.compute(G); + + /* initialize the matrix R */ + d.setZero(); + R.setZero(); + R_norm = 1.0; /* this variable will hold the norm of the matrix R */ + + /* compute the inverse of the factorized matrix G^-1, this is the initial value for H */ + // J = L^-T + J.setIdentity(); + J = chol.matrixU().solve(J); + c2 = J.trace(); +#ifdef TRACE_SOLVER + print_matrix("J", J, n); +#endif + + /* c1 * c2 is an estimate for cond(G) */ + + /* + * Find the unconstrained minimizer of the quadratic form 0.5 * x G x + g0 x + * this is a feasible point in the dual space + * x = G^-1 * g0 + */ + x = chol.solve(g0); + x = -x; + /* and compute the current solution value */ + f_value = 0.5 * g0.dot(x); +#ifdef TRACE_SOLVER + std::cerr << "Unconstrained solution: " << f_value << std::endl; + print_vector("x", x, n); +#endif + + /* Add equality constraints to the working set A */ + iq = 0; + for (i = 0; i < me; i++) + { + np = CE.col(i); + compute_d(d, J, np); + update_z(z, J, d, iq); + update_r(R, r, d, iq); +#ifdef TRACE_SOLVER + print_matrix("R", R, iq); + print_vector("z", z, n); + print_vector("r", r, iq); + print_vector("d", d, n); +#endif + + /* compute full step length t2: i.e., the minimum step in primal space s.t. the contraint + becomes feasible */ + t2 = 0.0; + if (std::abs(z.dot(z)) > std::numeric_limits::epsilon()) // i.e. z != 0 + t2 = (-np.dot(x) - ce0(i)) / z.dot(np); + + x += t2 * z; + + /* set u = u+ */ + u(iq) = t2; + u.head(iq) -= t2 * r.head(iq); + + /* compute the new solution value */ + f_value += 0.5 * (t2 * t2) * z.dot(np); + A(i) = -i - 1; + + if (!add_constraint(R, J, d, iq, R_norm)) + { + // FIXME: it should raise an error + // Equality constraints are linearly dependent + return false; + } + } + + /* set iai = K \ A */ + for (i = 0; i < mi; i++) + iai(i) = i; + +l1: iter++; +#ifdef TRACE_SOLVER + print_vector("x", x, n); +#endif + /* step 1: choose a violated constraint */ + for (i = me; i < iq; i++) + { + ip = A(i); + iai(ip) = -1; + } + + /* compute s(x) = ci^T * x + ci0 for all elements of K \ A */ + ss = 0.0; + psi = 0.0; /* this value will contain the sum of all infeasibilities */ + ip = 0; /* ip will be the index of the chosen violated constraint */ + for (i = 0; i < mi; i++) + { + iaexcl[i] = true; + sum = CI.col(i).dot(x) + ci0(i); + s(i) = sum; + psi += std::min(0.0, sum); + } +#ifdef TRACE_SOLVER + print_vector("s", s, mi); +#endif + + + if (std::abs(psi) <= mi * std::numeric_limits::epsilon() * c1 * c2* 100.0) + { + /* numerically there are not infeasibilities anymore */ + q = iq; + return true; + } + + /* save old values for u, x and A */ + u_old.head(iq) = u.head(iq); + A_old.head(iq) = A.head(iq); + x_old = x; + +l2: /* Step 2: check for feasibility and determine a new S-pair */ + for (i = 0; i < mi; i++) + { + if (s(i) < ss && iai(i) != -1 && iaexcl[i]) + { + ss = s(i); + ip = i; + } + } + if (ss >= 0.0) + { + q = iq; + return true; + } + + /* set np = n(ip) */ + np = CI.col(ip); + /* set u = (u 0)^T */ + u(iq) = 0.0; + /* add ip to the active set A */ + A(iq) = ip; + +#ifdef TRACE_SOLVER + std::cerr << "Trying with constraint " << ip << std::endl; + print_vector("np", np, n); +#endif + +l2a:/* Step 2a: determine step direction */ + /* compute z = H np: the step direction in the primal space (through J, see the paper) */ + compute_d(d, J, np); + update_z(z, J, d, iq); + /* compute N* np (if q > 0): the negative of the step direction in the dual space */ + update_r(R, r, d, iq); +#ifdef TRACE_SOLVER + std::cerr << "Step direction z" << std::endl; + print_vector("z", z, n); + print_vector("r", r, iq + 1); + print_vector("u", u, iq + 1); + print_vector("d", d, n); + print_ivector("A", A, iq + 1); +#endif + + /* Step 2b: compute step length */ + l = 0; + /* Compute t1: partial step length (maximum step in dual space without violating dual feasibility */ + t1 = inf; /* +inf */ + /* find the index l s.t. it reaches the minimum of u+(x) / r */ + for (k = me; k < iq; k++) + { + double tmp; + if (r(k) > 0.0 && ((tmp = u(k) / r(k)) < t1) ) + { + t1 = tmp; + l = A(k); + } + } + /* Compute t2: full step length (minimum step in primal space such that the constraint ip becomes feasible */ + if (std::abs(z.dot(z)) > std::numeric_limits::epsilon()) // i.e. z != 0 + t2 = -s(ip) / z.dot(np); + else + t2 = inf; /* +inf */ + + /* the step is chosen as the minimum of t1 and t2 */ + t = std::min(t1, t2); +#ifdef TRACE_SOLVER + std::cerr << "Step sizes: " << t << " (t1 = " << t1 << ", t2 = " << t2 << ") "; +#endif + + /* Step 2c: determine new S-pair and take step: */ + + /* case (i): no step in primal or dual space */ + if (t >= inf) + { + /* QPP is infeasible */ + // FIXME: unbounded to raise + q = iq; + return false; + } + /* case (ii): step in dual space */ + if (t2 >= inf) + { + /* set u = u + t * [-r 1) and drop constraint l from the active set A */ + u.head(iq) -= t * r.head(iq); + u(iq) += t; + iai(l) = l; + delete_constraint(R, J, A, u, p, iq, l); +#ifdef TRACE_SOLVER + std::cerr << " in dual space: " + << f_value << std::endl; + print_vector("x", x, n); + print_vector("z", z, n); + print_ivector("A", A, iq + 1); +#endif + goto l2a; + } + + /* case (iii): step in primal and dual space */ + + x += t * z; + /* update the solution value */ + f_value += t * z.dot(np) * (0.5 * t + u(iq)); + + u.head(iq) -= t * r.head(iq); + u(iq) += t; +#ifdef TRACE_SOLVER + std::cerr << " in both spaces: " + << f_value << std::endl; + print_vector("x", x, n); + print_vector("u", u, iq + 1); + print_vector("r", r, iq + 1); + print_ivector("A", A, iq + 1); +#endif + + if (t == t2) + { +#ifdef TRACE_SOLVER + std::cerr << "Full step has taken " << t << std::endl; + print_vector("x", x, n); +#endif + /* full step has taken */ + /* add constraint ip to the active set*/ + if (!add_constraint(R, J, d, iq, R_norm)) + { + iaexcl[ip] = false; + delete_constraint(R, J, A, u, p, iq, ip); +#ifdef TRACE_SOLVER + print_matrix("R", R, n); + print_ivector("A", A, iq); +#endif + for (i = 0; i < m; i++) + iai(i) = i; + for (i = 0; i < iq; i++) + { + A(i) = A_old(i); + iai(A(i)) = -1; + u(i) = u_old(i); + } + x = x_old; + goto l2; /* go to step 2 */ + } + else + iai(ip) = -1; +#ifdef TRACE_SOLVER + print_matrix("R", R, n); + print_ivector("A", A, iq); +#endif + goto l1; + } + + /* a patial step has taken */ +#ifdef TRACE_SOLVER + std::cerr << "Partial step has taken " << t << std::endl; + print_vector("x", x, n); +#endif + /* drop constraint l */ + iai(l) = l; + delete_constraint(R, J, A, u, p, iq, l); +#ifdef TRACE_SOLVER + print_matrix("R", R, n); + print_ivector("A", A, iq); +#endif + + s(ip) = CI.col(ip).dot(x) + ci0(ip); + +#ifdef TRACE_SOLVER + print_vector("s", s, mi); +#endif + goto l2a; +} diff --git a/src/igl/copyleft/quadprog.h b/src/igl/copyleft/quadprog.h new file mode 100644 index 000000000..f054bbdba --- /dev/null +++ b/src/igl/copyleft/quadprog.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_QUADPROG_H +#define IGL_COPYLEFT_QUADPROG_H + +#include "../igl_inline.h" +#include +namespace igl +{ + namespace copyleft + { + // Solve a (dense) quadratric program of the form: + // + // min 0.5 x G x + g0 x + // s.t. CE' x + ce0 = 0 + // and CI' x + ci0 >= 0 + // + // Inputs: + // G #x by #x matrix of quadratic coefficients + // g0 #x vector of linear coefficients + // CE #x by #CE list of linear equality coefficients + // ce0 #CE list of linear equality right-hand sides + // CI #x by #CI list of linear equality coefficients + // ci0 #CI list of linear equality right-hand sides + // Outputs: + // x #x vector of solution values + // Returns true iff success + IGL_INLINE bool quadprog( + const Eigen::MatrixXd & G, + const Eigen::VectorXd & g0, + const Eigen::MatrixXd & CE, + const Eigen::VectorXd & ce0, + const Eigen::MatrixXd & CI, + const Eigen::VectorXd & ci0, + Eigen::VectorXd& x); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "quadprog.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/swept_volume.cpp b/src/igl/copyleft/swept_volume.cpp new file mode 100644 index 000000000..f21e3aba5 --- /dev/null +++ b/src/igl/copyleft/swept_volume.cpp @@ -0,0 +1,49 @@ +#include "swept_volume.h" +#include "../swept_volume_bounding_box.h" +#include "../swept_volume_signed_distance.h" +#include "../voxel_grid.h" +#include "marching_cubes.h" +#include + +IGL_INLINE void igl::copyleft::swept_volume( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & transform, + const size_t steps, + const size_t grid_res, + const size_t isolevel_grid, + Eigen::MatrixXd & SV, + Eigen::MatrixXi & SF) +{ + using namespace std; + using namespace Eigen; + using namespace igl; + using namespace igl::copyleft; + + const auto & Vtransform = + [&V,&transform](const size_t vi,const double t)->RowVector3d + { + Vector3d Vvi = V.row(vi).transpose(); + return (transform(t)*Vvi).transpose(); + }; + AlignedBox3d Mbox; + swept_volume_bounding_box(V.rows(),Vtransform,steps,Mbox); + + // Amount of padding: pad*h should be >= isolevel + const int pad = isolevel_grid+1; + // number of vertices on the largest side + const int s = grid_res+2*pad; + const double h = Mbox.diagonal().maxCoeff()/(double)(s-2.*pad-1.); + const double isolevel = isolevel_grid*h; + + // create grid + RowVector3i res; + MatrixXd GV; + voxel_grid(Mbox,s,pad,GV,res); + + // compute values + VectorXd S; + swept_volume_signed_distance(V,F,transform,steps,GV,res,h,isolevel,S); + S.array()-=isolevel; + marching_cubes(S,GV,res(0),res(1),res(2),SV,SF); +} diff --git a/src/igl/copyleft/swept_volume.h b/src/igl/copyleft/swept_volume.h new file mode 100644 index 000000000..cc178f78a --- /dev/null +++ b/src/igl/copyleft/swept_volume.h @@ -0,0 +1,41 @@ +#ifndef IGL_COPYLEFT_SWEPT_VOLUME_H +#define IGL_COPYLEFT_SWEPT_VOLUME_H +#include "../igl_inline.h" +#include +#include +namespace igl +{ + namespace copyleft + { + // Compute the surface of the swept volume of a solid object with surface + // (V,F) mesh under going rigid motion. + // + // Inputs: + // V #V by 3 list of mesh positions in reference pose + // F #F by 3 list of mesh indices into V + // transform function handle so that transform(t) returns the rigid + // transformation at time t∈[0,1] + // steps number of time steps: steps=3 --> t∈{0,0.5,1} + // grid_res number of grid cells on the longest side containing the + // motion (isolevel+1 cells will also be added on each side as padding) + // isolevel distance level to be contoured as swept volume + // Outputs: + // SV #SV by 3 list of mesh positions of the swept surface + // SF #SF by 3 list of mesh faces into SV + IGL_INLINE void swept_volume( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & transform, + const size_t steps, + const size_t grid_res, + const size_t isolevel, + Eigen::MatrixXd & SV, + Eigen::MatrixXi & SF); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "swept_volume.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/tetgen/README b/src/igl/copyleft/tetgen/README new file mode 100644 index 000000000..0315b6c11 --- /dev/null +++ b/src/igl/copyleft/tetgen/README @@ -0,0 +1,7 @@ +IGL interface to tetgen library + +Dependencies: + tetgen + +Travel to $IGL/external/tetgen and issue: + make -f Makefile.igl tetlib diff --git a/src/igl/copyleft/tetgen/cdt.cpp b/src/igl/copyleft/tetgen/cdt.cpp new file mode 100644 index 000000000..f2602dd66 --- /dev/null +++ b/src/igl/copyleft/tetgen/cdt.cpp @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cdt.h" +#include "../../bounding_box.h" +#include "tetrahedralize.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedTV, + typename DerivedTT, + typename DerivedTF> +IGL_INLINE bool igl::copyleft::tetgen::cdt( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const CDTParam & param, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF) +{ + using namespace Eigen; + using namespace std; + // Effective input mesh + DerivedV U; + DerivedF G; + if(param.use_bounding_box) + { + // Construct bounding box mesh + DerivedV BV; + DerivedF BF; + bounding_box(V,BV,BF); + // scale bounding box + const RowVector3d mid = + (BV.colwise().minCoeff() + BV.colwise().maxCoeff()).eval()*0.5; + BV.rowwise() -= mid; + assert(param.bounding_box_scale >= 1.); + BV.array() *= param.bounding_box_scale; + BV.rowwise() += mid; + // Append bounding box to mesh + U.resize(V.rows()+BV.rows(),V.cols()); + U<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::copyleft::tetgen::CDTParam const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/tetgen/cdt.h b/src/igl/copyleft/tetgen/cdt.h new file mode 100644 index 000000000..e59a28029 --- /dev/null +++ b/src/igl/copyleft/tetgen/cdt.h @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_CDT_H +#define IGL_COPYLEFT_TETGEN_CDT_H +#include "../../igl_inline.h" + +#include +#include +#ifndef TETLIBRARY +# define TETLIBRARY +#endif +#include "tetgen.h" // Defined REAL + +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + struct CDTParam + { + // Tetgen can compute mesh of convex hull of input (i.e. "c") but often + // chokes. One workaround is to force it to mesh the entire bounding box. + // {false} + bool use_bounding_box = false; + // Scale the bounding box a bit so that vertices near it do not give tetgen + // problems. {1.01} + double bounding_box_scale = 1.01; + // Flags to tetgen. Do not include the "c" flag here! {"Y"} + std::string flags = "Y"; + }; + // Create a constrained delaunay tessellation containing convex hull of the + // given **non-selfintersecting** mesh. + // + // Inputs: + // V #V by 3 list of input mesh vertices + // F #F by 3 list of input mesh facets + // param see above + // TV #TV by 3 list of output mesh vertices (V come first) + // TT #TT by 3 list of tetrahedra indices into TV. + // TF #TF by 3 list of facets from F potentially subdivided. + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedTV, + typename DerivedTT, + typename DerivedTF> + IGL_INLINE bool cdt( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const CDTParam & param, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "cdt.cpp" +#endif + +#endif + + diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp b/src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp new file mode 100644 index 000000000..78eaebb5c --- /dev/null +++ b/src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mesh_to_tetgenio.h" + +// IGL includes +#include "../../matrix_to_list.h" + +// STL includes +#include + +IGL_INLINE bool igl::copyleft::tetgen::mesh_to_tetgenio( + const std::vector > & V, + const std::vector > & F, + tetgenio & in) +{ + using namespace std; + // all indices start from 0 + in.firstnumber = 0; + + in.numberofpoints = V.size(); + in.pointlist = new REAL[in.numberofpoints * 3]; + // loop over points + for(int i = 0; i < (int)V.size(); i++) + { + assert(V[i].size() == 3); + in.pointlist[i*3+0] = V[i][0]; + in.pointlist[i*3+1] = V[i][1]; + in.pointlist[i*3+2] = V[i][2]; + } + + in.numberoffacets = F.size(); + in.facetlist = new tetgenio::facet[in.numberoffacets]; + in.facetmarkerlist = new int[in.numberoffacets]; + + // loop over face + for(int i = 0;i < (int)F.size(); i++) + { + in.facetmarkerlist[i] = 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 = F[i].size(); + p->vertexlist = new int[p->numberofvertices]; + // loop around face + for(int j = 0;j < (int)F[i].size(); j++) + { + p->vertexlist[j] = F[i][j]; + } + } + return true; +} + +template +IGL_INLINE bool igl::copyleft::tetgen::mesh_to_tetgenio( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + tetgenio & in) +{ + using namespace std; + vector > vV; + vector > vF; + matrix_to_list(V,vV); + matrix_to_list(F,vF); + return mesh_to_tetgenio(vV,vF,in); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::copyleft::tetgen::mesh_to_tetgenio, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, tetgenio&); +#endif diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.h b/src/igl/copyleft/tetgen/mesh_to_tetgenio.h new file mode 100644 index 000000000..3295294fe --- /dev/null +++ b/src/igl/copyleft/tetgen/mesh_to_tetgenio.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_MESH_TO_TETGENIO_H +#define IGL_COPYLEFT_TETGEN_MESH_TO_TETGENIO_H +#include "../../igl_inline.h" + +#ifndef TETLIBRARY +# define TETLIBRARY +#endif +#include "tetgen.h" // Defined tetgenio, REAL +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + // Load a vertex list and face list into a tetgenio object + // Inputs: + // V #V by 3 vertex position list + // F #F list of polygon face indices into V (0-indexed) + // Outputs: + // in tetgenio input object + // Returns true on success, false on error + IGL_INLINE bool mesh_to_tetgenio( + const std::vector > & V, + const std::vector > & F, + tetgenio & in); + + // Wrapper with Eigen types + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedF integer-value: i.e. from MatrixXi + template + IGL_INLINE bool mesh_to_tetgenio( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + tetgenio & in); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "mesh_to_tetgenio.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/tetgen/mesh_with_skeleton.cpp b/src/igl/copyleft/tetgen/mesh_with_skeleton.cpp new file mode 100644 index 000000000..5c920b36d --- /dev/null +++ b/src/igl/copyleft/tetgen/mesh_with_skeleton.cpp @@ -0,0 +1,102 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mesh_with_skeleton.h" +#include "tetrahedralize.h" + +#include "../../sample_edges.h" +#include "../../cat.h" + +#include +// Default settings pq2Y tell tetgen to mesh interior of triangle mesh and +// to produce a graded tet mesh +const static std::string DEFAULT_TETGEN_FLAGS = "pq2Y"; + +IGL_INLINE bool igl::copyleft::tetgen::mesh_with_skeleton( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & /*P*/, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + const int samples_per_bone, + const std::string & tetgen_flags, + Eigen::MatrixXd & VV, + Eigen::MatrixXi & TT, + Eigen::MatrixXi & FF) +{ + using namespace Eigen; + using namespace std; + const string eff_tetgen_flags = + (tetgen_flags.length() == 0?DEFAULT_TETGEN_FLAGS:tetgen_flags); + // Collect all edges that need samples: + MatrixXi BECE = cat(1,BE,CE); + MatrixXd S; + // Sample each edge with 10 samples. (Choice of 10 doesn't seem to matter so + // much, but could under some circumstances) + sample_edges(C,BECE,samples_per_bone,S); + // Vertices we'll constrain tet mesh to meet + MatrixXd VS = cat(1,V,S); + // Use tetgen to mesh the interior of surface, this assumes surface: + // * has no holes + // * has no non-manifold edges or vertices + // * has consistent orientation + // * has no self-intersections + // * has no 0-volume pieces + cerr<<"tetgen begin()"< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_MESH_WITH_SKELETON_H +#define IGL_COPYLEFT_TETGEN_MESH_WITH_SKELETON_H +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + // Mesh the interior of a given surface with tetrahedra which are graded + // (tend to be small near the surface and large inside) and conform to the + // given handles and samplings thereof. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of triangle indices + // C #C by 3 list of vertex positions + // P #P list of point handle indices + // BE #BE by 2 list of bone-edge indices + // CE #CE by 2 list of cage-edge indices + // samples_per_bone #samples to add per bone + // tetgen_flags flags to pass to tetgen {""-->"pq2Y"} otherwise you're on + // your own and it's your funeral if you pass nonsense flags + // Outputs: + // VV #VV by 3 list of tet-mesh vertex positions + // TT #TT by 4 list of tetrahedra indices + // FF #FF by 3 list of surface triangle indices + // Returns true only on success + IGL_INLINE bool mesh_with_skeleton( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & /*P*/, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + const int samples_per_bone, + const std::string & tetgen_flags, + Eigen::MatrixXd & VV, + Eigen::MatrixXi & TT, + Eigen::MatrixXi & FF); + // Wrapper using default tetgen_flags + IGL_INLINE bool mesh_with_skeleton( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & /*P*/, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + const int samples_per_bone, + Eigen::MatrixXd & VV, + Eigen::MatrixXi & TT, + Eigen::MatrixXi & FF); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "mesh_with_skeleton.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/tetgen/read_into_tetgenio.cpp b/src/igl/copyleft/tetgen/read_into_tetgenio.cpp new file mode 100644 index 000000000..506c5e4ba --- /dev/null +++ b/src/igl/copyleft/tetgen/read_into_tetgenio.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "read_into_tetgenio.h" +#include "mesh_to_tetgenio.h" + +// IGL includes +#include "../../pathinfo.h" +#ifndef IGL_NO_EIGEN +# define IGL_NO_EIGEN_WAS_NOT_ALREADY_DEFINED +# define IGL_NO_EIGEN +#endif +// Include igl headers without including Eigen +#include "../../readOBJ.h" +#ifdef IGL_NO_EIGEN_WAS_NOT_ALREADY_DEFINED +# undef IGL_NO_EIGEN +#endif + +// STL includes +#include +#include +#include + +IGL_INLINE bool igl::copyleft::tetgen::read_into_tetgenio( + const std::string & path, + tetgenio & in) +{ + using namespace std; + // get file extension + string dirname,basename,ext,filename; + pathinfo(path,dirname,basename,ext,filename); + // convert to lower case for easy comparison + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + bool success = false; + + char basename_char[1024]; + strcpy(basename_char,basename.c_str()); + + if(ext == "obj") + { + // read obj into vertex list and face list + vector > V,TC,N; + vector > F,FTC,FN; + success = readOBJ(path,V,TC,N,F,FTC,FN); + success &= mesh_to_tetgenio(V,F,in); + }else if(ext == "off") + { + success = in.load_off(basename_char); + }else if(ext == "node") + { + success = in.load_node(basename_char); + }else + { + if(ext.length() > 0) + { + cerr<<"^read_into_tetgenio Warning: Unsupported extension ("< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_READ_INTO_TETGENIO_H +#define IGL_COPYLEFT_TETGEN_READ_INTO_TETGENIO_H +#include "../../igl_inline.h" + +#include +#ifndef TETLIBRARY +#define TETLIBRARY +#endif +#include "tetgen.h" // Defined tetgenio, REAL + +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + // Read a mesh or point set into tetgenio (input object for calling + // tetgen). Many file formats are already supported by tetgen: + // .off + // .ply + // .node + // .ply + // .medit + // .vtk + // etc. + // Notably it does not support .obj which is loaded by hand here (also + // demonstrating how to load points/faces programmatically) + // + // If the file extension is not recognized the filename is assumed to be + // the basename of a collection describe a tetmesh, (of which at least + // the .node file must exist): + // [filename].node + // [filename].ele + // [filename].face + // [filename].edge + // [filename].vol + // + // Inputs: + // path path to file or basename to files + // Outputs: + // in tetgenio input object + // Returns true on success, false on error + IGL_INLINE bool read_into_tetgenio( + const std::string & path, + tetgenio & in); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "read_into_tetgenio.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp b/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp new file mode 100644 index 000000000..d61153053 --- /dev/null +++ b/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp @@ -0,0 +1,146 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "tetgenio_to_tetmesh.h" + +// IGL includes +#include "../../list_to_matrix.h" + +// STL includes +#include + +IGL_INLINE bool igl::copyleft::tetgen::tetgenio_to_tetmesh( + const tetgenio & out, + std::vector > & V, + std::vector > & T, + std::vector > & F) +{ + using namespace std; + // process points + if(out.pointlist == NULL) + { + cerr<<"^tetgenio_to_tetmesh Error: point list is NULL\n"<(3)); + // loop over points + for(int i = 0;i < out.numberofpoints; i++) + { + V[i][0] = out.pointlist[i*3+0]; + V[i][1] = out.pointlist[i*3+1]; + V[i][2] = out.pointlist[i*3+2]; + } + + + // process tets + if(out.tetrahedronlist == NULL) + { + cerr<<"^tetgenio_to_tetmesh Error: tet list is NULL\n"<(out.numberofcorners)); + int min_index = 1e7; + int max_index = -1e7; + // loop over tetrahedra + for(int i = 0; i < out.numberoftetrahedra; i++) + { + for(int j = 0; j index ? index : min_index); + max_index = (max_index < index ? index : max_index); + } + } + assert(min_index >= 0); + assert(max_index >= 0); + assert(max_index < (int)V.size()); + + cout<=0) + { + vector face(3); + for(int j = 0; j<3; j++) + { + face[j] = out.trifacelist[i * 3 + j]; + } + F.push_back(face); + } + } + + return true; +} + +IGL_INLINE bool igl::copyleft::tetgen::tetgenio_to_tetmesh( + const tetgenio & out, + std::vector > & V, + std::vector > & T) +{ + std::vector > F; + return tetgenio_to_tetmesh(out,V,T,F); +} + +template +IGL_INLINE bool igl::copyleft::tetgen::tetgenio_to_tetmesh( + const tetgenio & out, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F) +{ + using namespace std; + vector > vV; + vector > vT; + vector > vF; + bool success = tetgenio_to_tetmesh(out,vV,vT,vF); + if(!success) + { + return false; + } + bool V_rect = list_to_matrix(vV,V); + if(!V_rect) + { + // igl::list_to_matrix(vV,V) already printed error message to std err + return false; + } + bool T_rect = list_to_matrix(vT,T); + if(!T_rect) + { + // igl::list_to_matrix(vT,T) already printed error message to std err + return false; + } + bool F_rect = list_to_matrix(vF,F); + if(!F_rect) + { + return false; + } + + return true; +} + +template +IGL_INLINE bool igl::copyleft::tetgen::tetgenio_to_tetmesh( + const tetgenio & out, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T) +{ + Eigen::Matrix F; + return tetgenio_to_tetmesh(out,V,T,F); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::copyleft::tetgen::tetgenio_to_tetmesh, Eigen::Matrix >(tetgenio const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h b/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h new file mode 100644 index 000000000..2a5575886 --- /dev/null +++ b/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_TETGENIO_TO_TETMESH_H +#define IGL_COPYLEFT_TETGEN_TETGENIO_TO_TETMESH_H +#include "../../igl_inline.h" + +#ifndef TETLIBRARY +#define TETLIBRARY +#endif +#include "tetgen.h" // Defined tetgenio, REAL +#include +#include +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + // Extract a tetrahedral mesh from a tetgenio object + // Inputs: + // out tetgenio output object + // Outputs: + // V #V by 3 vertex position list + // T #T by 4 list of tetrahedra indices into V + // F #F by 3 list of marked facets + // Returns true on success, false on error + IGL_INLINE bool tetgenio_to_tetmesh( + const tetgenio & out, + std::vector > & V, + std::vector > & T, + std::vector > & F); + IGL_INLINE bool tetgenio_to_tetmesh( + const tetgenio & out, + std::vector > & V, + std::vector > & T); + + // Wrapper with Eigen types + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedT integer-value: i.e. from MatrixXi + template + IGL_INLINE bool tetgenio_to_tetmesh( + const tetgenio & out, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F); + template + IGL_INLINE bool tetgenio_to_tetmesh( + const tetgenio & out, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "tetgenio_to_tetmesh.cpp" +#endif + +#endif diff --git a/src/igl/copyleft/tetgen/tetrahedralize.cpp b/src/igl/copyleft/tetgen/tetrahedralize.cpp new file mode 100644 index 000000000..fa6111410 --- /dev/null +++ b/src/igl/copyleft/tetgen/tetrahedralize.cpp @@ -0,0 +1,220 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "tetrahedralize.h" +#include "mesh_to_tetgenio.h" +#include "tetgenio_to_tetmesh.h" + +// IGL includes +#include "../../matrix_to_list.h" +#include "../../list_to_matrix.h" +#include "../../boundary_facets.h" + +// STL includes +#include +#include + +IGL_INLINE int igl::copyleft::tetgen::tetrahedralize( + const std::vector > & V, + const std::vector > & F, + const std::string switches, + std::vector > & TV, + std::vector > & TT, + std::vector > & TF) +{ + using namespace std; + tetgenio in,out; + bool success; + success = mesh_to_tetgenio(V,F,in); + if(!success) + { + return -1; + } + try + { + char * cswitches = new char[switches.size() + 1]; + std::strcpy(cswitches,switches.c_str()); + ::tetrahedralize(cswitches,&in, &out); + delete[] cswitches; + }catch(int e) + { + cerr<<"^"<<__FUNCTION__<<": TETGEN CRASHED... KABOOOM!!!"< +IGL_INLINE int igl::copyleft::tetgen::tetrahedralize( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const std::string switches, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF) +{ + using namespace std; + vector > vV,vTV; + vector > vF,vTT,vTF; + matrix_to_list(V,vV); + matrix_to_list(F,vF); + int e = tetrahedralize(vV,vF,switches,vTV,vTT,vTF); + if(e == 0) + { + bool TV_rect = list_to_matrix(vTV,TV); + if(!TV_rect) + { + return 3; + } + bool TT_rect = list_to_matrix(vTT,TT); + if(!TT_rect) + { + return 3; + } + bool TF_rect = list_to_matrix(vTF,TF); + if(!TF_rect) + { + return 3; + } + } + return e; +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVM, + typename DerivedFM, + typename DerivedTV, + typename DerivedTT, + typename DerivedTF, + typename DerivedTM> +IGL_INLINE int igl::copyleft::tetgen::tetrahedralize( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& VM, + const Eigen::PlainObjectBase& FM, + const std::string switches, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF, + Eigen::PlainObjectBase& TM) +{ + using namespace std; + vector > vV,vTV; + vector > vF,vTT,vTF; + vector vTM; + + matrix_to_list(V,vV); + matrix_to_list(F,vF); + vector vVM = matrix_to_list(VM); + vector vFM = matrix_to_list(FM); + int e = tetrahedralize(vV,vF,vVM,vFM,switches,vTV,vTT,vTF,vTM); + if(e == 0) + { + bool TV_rect = list_to_matrix(vTV,TV); + if(!TV_rect) + { + return false; + } + bool TT_rect = list_to_matrix(vTT,TT); + if(!TT_rect) + { + return false; + } + bool TF_rect = list_to_matrix(vTF,TF); + if(!TF_rect) + { + return false; + } + bool TM_rect = list_to_matrix(vTM,TM); + if(!TM_rect) + { + return false; + } + } + return e; +} +IGL_INLINE int igl::copyleft::tetgen::tetrahedralize( + const std::vector > & V, + const std::vector > & F, + const std::vector & VM, + const std::vector & FM, + const std::string switches, + std::vector > & TV, + std::vector > & TT, + std::vector > & TF, + std::vector & TM) +{ + using namespace std; + tetgenio in,out; + bool success; + success = mesh_to_tetgenio(V,F,in); + if(!success) + { + return -1; + } + in.pointmarkerlist = new int[VM.size()]; + for (int i = 0; i < VM.size(); ++i) { + in.pointmarkerlist[i] = VM[i]; + } + // These have already been created in mesh_to_tetgenio. + // Reset them here. + for (int i = 0; i < FM.size(); ++i) { + in.facetmarkerlist[i] = FM[i]; + } + try + { + char * cswitches = new char[switches.size() + 1]; + std::strcpy(cswitches,switches.c_str()); + ::tetrahedralize(cswitches,&in, &out); + delete[] cswitches; + }catch(int e) + { + cerr<<"^"<<__FUNCTION__<<": TETGEN CRASHED... KABOOOM!!!"<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template int igl::copyleft::tetgen::tetrahedralize,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(const Eigen::PlainObjectBase > &,const Eigen::PlainObjectBase > &,const Eigen::PlainObjectBase > &,const Eigen::PlainObjectBase > &,const std::basic_string, std::allocator >,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &); +template int igl::copyleft::tetgen::tetrahedralize, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/copyleft/tetgen/tetrahedralize.h b/src/igl/copyleft/tetgen/tetrahedralize.h new file mode 100644 index 000000000..170a3f985 --- /dev/null +++ b/src/igl/copyleft/tetgen/tetrahedralize.h @@ -0,0 +1,136 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COPYLEFT_TETGEN_TETRAHEDRALIZE_H +#define IGL_COPYLEFT_TETGEN_TETRAHEDRALIZE_H +#include "../../igl_inline.h" + +#include +#include +#include +#ifndef TETLIBRARY +#define TETLIBRARY +#endif +#include "tetgen.h" // Defined REAL + +namespace igl +{ + namespace copyleft + { + namespace tetgen + { + // Mesh the interior of a surface mesh (V,F) using tetgen + // + // Inputs: + // V #V by 3 vertex position list + // F #F list of polygon face indices into V (0-indexed) + // switches string of tetgen options (See tetgen documentation) e.g. + // "pq1.414a0.01" tries to mesh the interior of a given surface with + // quality and area constraints + // "" will mesh the convex hull constrained to pass through V (ignores F) + // Outputs: + // TV #V by 3 vertex position list + // TT #T by 4 list of tet face indices + // TF #F by 3 list of triangle face indices + // Returns status: + // 0 success + // 1 tetgen threw exception + // 2 tetgen did not crash but could not create any tets (probably there are + // holes, duplicate faces etc.) + // -1 other error + IGL_INLINE int tetrahedralize( + const std::vector > & V, + const std::vector > & F, + const std::string switches, + std::vector > & TV, + std::vector > & TT, + std::vector > & TF); + + // Wrapper with Eigen types + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedF integer-value: i.e. from MatrixXi + template < + typename DerivedV, + typename DerivedF, + typename DerivedTV, + typename DerivedTT, + typename DerivedTF> + IGL_INLINE int tetrahedralize( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const std::string switches, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF); + + // Mesh the interior of a surface mesh (V,F) using tetgen + // + // Inputs: + // V #V by 3 vertex position list + // F #F list of polygon face indices into V (0-indexed) + // M #V list of markers for vertices + // switches string of tetgen options (See tetgen documentation) e.g. + // "pq1.414a0.01" tries to mesh the interior of a given surface with + // quality and area constraints + // "" will mesh the convex hull constrained to pass through V (ignores F) + // Outputs: + // TV #V by 3 vertex position list + // TT #T by 4 list of tet face indices + // TF #F by 3 list of triangle face indices + // TM #V list of markers for vertices + // Returns status: + // 0 success + // 1 tetgen threw exception + // 2 tetgen did not crash but could not create any tets (probably there are + // holes, duplicate faces etc.) + // -1 other error + IGL_INLINE int tetrahedralize( + const std::vector > & V, + const std::vector > & F, + const std::vector & VM, + const std::vector & FM, + const std::string switches, + std::vector > & TV, + std::vector > & TT, + std::vector > & TF, + std::vector & TM); + + // Wrapper with Eigen types + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedF integer-value: i.e. from MatrixXi + template < + typename DerivedV, + typename DerivedF, + typename DerivedVM, + typename DerivedFM, + typename DerivedTV, + typename DerivedTT, + typename DerivedTF, + typename DerivedTM> + IGL_INLINE int tetrahedralize( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& VM, + const Eigen::PlainObjectBase& FM, + const std::string switches, + Eigen::PlainObjectBase& TV, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TF, + Eigen::PlainObjectBase& TM); + } + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "tetrahedralize.cpp" +#endif + +#endif + diff --git a/src/igl/cotmatrix.cpp b/src/igl/cotmatrix.cpp new file mode 100644 index 000000000..b1fd658ac --- /dev/null +++ b/src/igl/cotmatrix.cpp @@ -0,0 +1,88 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cotmatrix.h" +#include + +// For error printing +#include +#include "cotmatrix_entries.h" + +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include + +template +IGL_INLINE void igl::cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& L) +{ + using namespace Eigen; + using namespace std; + + L.resize(V.rows(),V.rows()); + Matrix edges; + int simplex_size = F.cols(); + // 3 for triangles, 4 for tets + assert(simplex_size == 3 || simplex_size == 4); + if(simplex_size == 3) + { + // This is important! it could decrease the comptuation time by a factor of 2 + // Laplacian for a closed 2d manifold mesh will have on average 7 entries per + // row + L.reserve(10*V.rows()); + edges.resize(3,2); + edges << + 1,2, + 2,0, + 0,1; + }else if(simplex_size == 4) + { + L.reserve(17*V.rows()); + edges.resize(6,2); + edges << + 1,2, + 2,0, + 0,1, + 3,0, + 3,1, + 3,2; + }else + { + return; + } + // Gather cotangents + Matrix C; + cotmatrix_entries(V,F,C); + + vector > IJV; + IJV.reserve(F.rows()*edges.rows()*4); + // Loop over triangles + for(int i = 0; i < F.rows(); i++) + { + // loop over edges of element + for(int e = 0;e(source,dest,C(i,e))); + IJV.push_back(Triplet(dest,source,C(i,e))); + IJV.push_back(Triplet(source,source,-C(i,e))); + IJV.push_back(Triplet(dest,dest,-C(i,e))); + } + } + L.setFromTriplets(IJV.begin(),IJV.end()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::cotmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::cotmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::cotmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::cotmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/cotmatrix.h b/src/igl/cotmatrix.h new file mode 100644 index 000000000..bb8232f5a --- /dev/null +++ b/src/igl/cotmatrix.h @@ -0,0 +1,57 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COTMATRIX_H +#define IGL_COTMATRIX_H +#include "igl_inline.h" + +#include +#include + +// History: +// Used const references rather than copying the entire mesh +// Alec 9 October 2011 +// removed cotan (uniform weights) optional parameter it was building a buggy +// half of the uniform laplacian, please see adjacency_matrix instead +// Alec 9 October 2011 + +namespace igl +{ + // Constructs the cotangent stiffness matrix (discrete laplacian) for a given + // mesh (V,F). + // + // Templates: + // DerivedV derived type of eigen matrix for V (e.g. derived from + // MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. derived from + // MatrixXi) + // Scalar scalar type for eigen sparse matrix (e.g. double) + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by simplex_size list of mesh faces (must be triangles) + // Outputs: + // L #V by #V cotangent matrix, each row i corresponding to V(i,:) + // + // See also: adjacency_matrix + // + // Note: This Laplacian uses the convention that diagonal entries are + // **minus** the sum of off-diagonal entries. The diagonal entries are + // therefore in general negative and the matrix is **negative** semi-definite + // (immediately, -L is **positive** semi-definite) + // + template + IGL_INLINE void cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& L); +} + +#ifndef IGL_STATIC_LIBRARY +# include "cotmatrix.cpp" +#endif + +#endif diff --git a/src/igl/cotmatrix_entries.cpp b/src/igl/cotmatrix_entries.cpp new file mode 100644 index 000000000..6f7dfbbd3 --- /dev/null +++ b/src/igl/cotmatrix_entries.cpp @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cotmatrix_entries.h" +#include "doublearea.h" +#include "squared_edge_lengths.h" +#include "edge_lengths.h" +#include "face_areas.h" +#include "volume.h" +#include "dihedral_angles.h" + +#include "verbose.h" + + +template +IGL_INLINE void igl::cotmatrix_entries( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& C) +{ + using namespace std; + using namespace Eigen; + // simplex size (3: triangles, 4: tetrahedra) + int simplex_size = F.cols(); + // Number of elements + int m = F.rows(); + + // Law of cosines + law of sines + switch(simplex_size) + { + case 3: + { + // Triangles + //Compute Squared Edge lengths + Matrix l2; + igl::squared_edge_lengths(V,F,l2); + //Compute Edge lengths + Matrix l; + l = l2.array().sqrt(); + + // double area + Matrix dblA; + doublearea(l,0.,dblA); + // cotangents and diagonal entries for element matrices + // correctly divided by 4 (alec 2010) + C.resize(m,3); + for(int i = 0;i l; + edge_lengths(V,F,l); + Matrix s; + face_areas(l,s); + Matrix cos_theta,theta; + dihedral_angles_intrinsic(l,s,theta,cos_theta); + + // volume + Matrix vol; + volume(l,vol); + + + // Law of sines + // http://mathworld.wolfram.com/Tetrahedron.html + Matrix sin_theta(m,6); + sin_theta.col(0) = vol.array() / ((2./(3.*l.col(0).array())).array() * s.col(1).array() * s.col(2).array()); + sin_theta.col(1) = vol.array() / ((2./(3.*l.col(1).array())).array() * s.col(2).array() * s.col(0).array()); + sin_theta.col(2) = vol.array() / ((2./(3.*l.col(2).array())).array() * s.col(0).array() * s.col(1).array()); + sin_theta.col(3) = vol.array() / ((2./(3.*l.col(3).array())).array() * s.col(3).array() * s.col(0).array()); + sin_theta.col(4) = vol.array() / ((2./(3.*l.col(4).array())).array() * s.col(3).array() * s.col(1).array()); + sin_theta.col(5) = vol.array() / ((2./(3.*l.col(5).array())).array() * s.col(3).array() * s.col(2).array()); + + + // http://arxiv.org/pdf/1208.0354.pdf Page 18 + C = (1./6.) * l.array() * cos_theta.array() / sin_theta.array(); + + break; + } + default: + { + fprintf(stderr, + "cotmatrix_entries.h: Error: Simplex size (%d) not supported\n", simplex_size); + assert(false); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::cotmatrix_entries, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::cotmatrix_entries, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::cotmatrix_entries, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::cotmatrix_entries, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/cotmatrix_entries.h b/src/igl/cotmatrix_entries.h new file mode 100644 index 000000000..6368c02e8 --- /dev/null +++ b/src/igl/cotmatrix_entries.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COTMATRIX_ENTRIES_H +#define IGL_COTMATRIX_ENTRIES_H +#include "igl_inline.h" +#include +namespace igl +{ + // COTMATRIX_ENTRIES compute the cotangents of each angle in mesh (V,F) + // + // Inputs: + // V #V by dim list of rest domain positions + // F #F by {3|4} list of {triangle|tetrahedra} indices into V + // Outputs: + // C #F by 3 list of 1/2*cotangents corresponding angles + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // OR + // C #F by 6 list of 1/6*cotangents of dihedral angles*edge lengths + // for tets, columns along edges [1,2],[2,0],[0,1],[3,0],[3,1],[3,2] + // + template + IGL_INLINE void cotmatrix_entries( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "cotmatrix_entries.cpp" +#endif + +#endif diff --git a/src/igl/count.cpp b/src/igl/count.cpp new file mode 100644 index 000000000..bdde79ca7 --- /dev/null +++ b/src/igl/count.cpp @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "count.h" +#include "redux.h" + +template +IGL_INLINE void igl::count( + const Eigen::SparseMatrix& X, + const int dim, + Eigen::SparseVector& S) +{ + // dim must be 2 or 1 + assert(dim == 1 || dim == 2); + // Get size of input + int m = X.rows(); + int n = X.cols(); + // resize output + if(dim==1) + { + S = Eigen::SparseVector(n); + }else + { + S = Eigen::SparseVector(m); + } + + // Iterate over outside + for(int k=0; k::InnerIterator it (X,k); it; ++it) + { + if(dim == 1) + { + S.coeffRef(it.col()) += (it.value() == 0? 0: 1); + }else + { + S.coeffRef(it.row()) += (it.value() == 0? 0: 1); + } + } + } + +} + +template +IGL_INLINE void igl::count( + const Eigen::SparseMatrix& A, + const int dim, + Eigen::PlainObjectBase& B) +{ + typedef typename DerivedB::Scalar Scalar; + igl::redux(A,dim,[](Scalar a, Scalar b){ return a+(b==0?0:1);},B); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::count >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::count >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/count.h b/src/igl/count.h new file mode 100644 index 000000000..61aeb94e8 --- /dev/null +++ b/src/igl/count.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COUNT_H +#define IGL_COUNT_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Note: If your looking for dense matrix matlab like sum for eigen matrics + // just use: + // M.colwise().count() or M.rowwise().count() + // + + // Count the number of non-zeros in the columns or rows of a sparse matrix + // + // Inputs: + // X m by n sparse matrix + // dim dimension along which to sum (1 or 2) + // Output: + // S n-long sparse vector (if dim == 1) + // or + // S m-long sparse vector (if dim == 2) + template + IGL_INLINE void count( + const Eigen::SparseMatrix& X, + const int dim, + Eigen::SparseVector& S); + template + IGL_INLINE void count( + const Eigen::SparseMatrix& X, + const int dim, + Eigen::PlainObjectBase& S); +} + +#ifndef IGL_STATIC_LIBRARY +# include "count.cpp" +#endif + +#endif + diff --git a/src/igl/covariance_scatter_matrix.cpp b/src/igl/covariance_scatter_matrix.cpp new file mode 100644 index 000000000..24fe9f760 --- /dev/null +++ b/src/igl/covariance_scatter_matrix.cpp @@ -0,0 +1,76 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "covariance_scatter_matrix.h" +#include "arap_linear_block.h" +#include "cotmatrix.h" +#include "diag.h" +#include "sum.h" +#include "edges.h" +#include "verbose.h" +#include "cat.h" +#include "PI.h" + +IGL_INLINE void igl::covariance_scatter_matrix( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const ARAPEnergyType energy, + Eigen::SparseMatrix& CSM) +{ + using namespace Eigen; + // number of mesh vertices + int n = V.rows(); + assert(n > F.maxCoeff()); + // dimension of mesh + int dim = V.cols(); + // Number of mesh elements + int m = F.rows(); + + // number of rotations + int nr; + switch(energy) + { + case ARAP_ENERGY_TYPE_SPOKES: + nr = n; + break; + case ARAP_ENERGY_TYPE_SPOKES_AND_RIMS: + nr = n; + break; + case ARAP_ENERGY_TYPE_ELEMENTS: + nr = m; + break; + default: + fprintf( + stderr, + "covariance_scatter_matrix.h: Error: Unsupported arap energy %d\n", + energy); + return; + } + + SparseMatrix KX,KY,KZ; + arap_linear_block(V,F,0,energy,KX); + arap_linear_block(V,F,1,energy,KY); + SparseMatrix Z(n,nr); + if(dim == 2) + { + CSM = cat(1,cat(2,KX,Z),cat(2,Z,KY)).transpose(); + }else if(dim == 3) + { + arap_linear_block(V,F,2,energy,KZ); + SparseMatrixZZ(n,nr*2); + CSM = + cat(1,cat(1,cat(2,KX,ZZ),cat(2,cat(2,Z,KY),Z)),cat(2,ZZ,KZ)).transpose(); + }else + { + fprintf( + stderr, + "covariance_scatter_matrix.h: Error: Unsupported dimension %d\n", + dim); + return; + } + +} diff --git a/src/igl/covariance_scatter_matrix.h b/src/igl/covariance_scatter_matrix.h new file mode 100644 index 000000000..e70c77ab1 --- /dev/null +++ b/src/igl/covariance_scatter_matrix.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_COVARIANCE_SCATTER_MATRIX_H +#define IGL_COVARIANCE_SCATTER_MATRIX_H + +#include "igl_inline.h" +#include "ARAPEnergyType.h" +#include +#include + +namespace igl +{ + // Construct the covariance scatter matrix for a given arap energy + // Inputs: + // V #V by Vdim list of initial domain positions + // F #F by 3 list of triangle indices into V + // energy ARAPEnergyType enum value defining which energy is being used. + // See ARAPEnergyType.h for valid options and explanations. + // Outputs: + // CSM dim*#V/#F by dim*#V sparse matrix containing special laplacians along + // the diagonal so that when multiplied by V gives covariance matrix + // elements, can be used to speed up covariance matrix computation + IGL_INLINE void covariance_scatter_matrix( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const ARAPEnergyType energy, + Eigen::SparseMatrix& CSM); +} + +#ifndef IGL_STATIC_LIBRARY +#include "covariance_scatter_matrix.cpp" +#endif +#endif diff --git a/src/igl/cross.cpp b/src/igl/cross.cpp new file mode 100644 index 000000000..04ee5340b --- /dev/null +++ b/src/igl/cross.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cross.h" + +// http://www.antisphere.com/Wiki/tools:anttweakbar +IGL_INLINE void igl::cross( + const double *a, + const double *b, + double *out) +{ + out[0] = a[1]*b[2]-a[2]*b[1]; + out[1] = a[2]*b[0]-a[0]*b[2]; + out[2] = a[0]*b[1]-a[1]*b[0]; +} + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC> +IGL_INLINE void igl::cross( + const Eigen::PlainObjectBase & A, + const Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & C) +{ + assert(A.cols() == 3 && "#cols should be 3"); + assert(B.cols() == 3 && "#cols should be 3"); + assert(A.rows() == B.rows() && "#rows in A and B should be equal"); + C.resize(A.rows(),3); + for(int d = 0;d<3;d++) + { + C.col(d) = + A.col((d+1)%3).array() * B.col((d+2)%3).array() - + A.col((d+2)%3).array() * B.col((d+1)%3).array(); + } +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::cross, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cross, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/cross.h b/src/igl/cross.h new file mode 100644 index 000000000..cfbe563ed --- /dev/null +++ b/src/igl/cross.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CROSS_H +#define IGL_CROSS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes out = cross(a,b) + // Inputs: + // a left 3d vector + // b right 3d vector + // Outputs: + // out result 3d vector + IGL_INLINE void cross( const double *a, const double *b, double *out); + // Computes C = cross(A,B,2); + // + // Inputs: + // A #A by 3 list of row-vectors + // B #A by 3 list of row-vectors + // Outputs: + // C #A by 3 list of row-vectors + template < + typename DerivedA, + typename DerivedB, + typename DerivedC> + IGL_INLINE void cross( + const Eigen::PlainObjectBase & A, + const Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "cross.cpp" +#endif + +#endif diff --git a/src/igl/cross_field_missmatch.cpp b/src/igl/cross_field_missmatch.cpp new file mode 100644 index 000000000..7d3caf0a8 --- /dev/null +++ b/src/igl/cross_field_missmatch.cpp @@ -0,0 +1,142 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "cross_field_missmatch.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace igl { + template + class MissMatchCalculator + { + public: + + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &PD1; + const Eigen::PlainObjectBase &PD2; + + DerivedV N; + + private: + // internal + std::vector V_border; // bool + std::vector > VF; + std::vector > VFi; + + DerivedF TT; + DerivedF TTi; + + + private: + ///compute the mismatch between 2 faces + inline int MissMatchByCross(const int f0, + const int f1) + { + Eigen::Matrix dir0 = PD1.row(f0); + Eigen::Matrix dir1 = PD1.row(f1); + Eigen::Matrix n0 = N.row(f0); + Eigen::Matrix n1 = N.row(f1); + + Eigen::Matrix dir1Rot = igl::rotation_matrix_from_directions(n1,n0)*dir1; + dir1Rot.normalize(); + + // TODO: this should be equivalent to the other code below, to check! + // Compute the angle between the two vectors + // double a0 = atan2(dir0.dot(B2.row(f0)),dir0.dot(B1.row(f0))); + // double a1 = atan2(dir1Rot.dot(B2.row(f0)),dir1Rot.dot(B1.row(f0))); + // + // double angle_diff = a1-a0; //VectToAngle(f0,dir1Rot); + + double angle_diff = atan2(dir1Rot.dot(PD2.row(f0)),dir1Rot.dot(PD1.row(f0))); + + // std::cerr << "Dani: " << dir0(0) << " " << dir0(1) << " " << dir0(2) << " " << dir1Rot(0) << " " << dir1Rot(1) << " " << dir1Rot(2) << " " << angle_diff << std::endl; + + double step=igl::PI/2.0; + int i=(int)std::floor((angle_diff/step)+0.5); + int k=0; + if (i>=0) + k=i%4; + else + k=(-(3*i))%4; + return k; + } + + +public: + inline MissMatchCalculator(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_PD1, + const Eigen::PlainObjectBase &_PD2 + ): + V(_V), + F(_F), + PD1(_PD1), + PD2(_PD2) + { + igl::per_face_normals(V,F,N); + V_border = igl::is_border_vertex(V,F); + igl::vertex_triangle_adjacency(V,F,VF,VFi); + igl::triangle_triangle_adjacency(F,TT,TTi); + } + + inline void calculateMissmatch(Eigen::PlainObjectBase &Handle_MMatch) + { + Handle_MMatch.setConstant(F.rows(),3,-1); + for (size_t i=0;i +IGL_INLINE void igl::cross_field_missmatch(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + const bool isCombed, + Eigen::PlainObjectBase &missmatch) +{ + DerivedV PD1_combed; + DerivedV PD2_combed; + + if (!isCombed) + igl::comb_cross_field(V,F,PD1,PD2,PD1_combed,PD2_combed); + else + { + PD1_combed = PD1; + PD2_combed = PD2; + } + igl::MissMatchCalculator sf(V, F, PD1_combed, PD2_combed); + sf.calculateMissmatch(missmatch); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::cross_field_missmatch, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::cross_field_missmatch, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::cross_field_missmatch, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool, Eigen::PlainObjectBase >&); + +#endif diff --git a/src/igl/cross_field_missmatch.h b/src/igl/cross_field_missmatch.h new file mode 100644 index 000000000..9d7a7599d --- /dev/null +++ b/src/igl/cross_field_missmatch.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_CROSS_FIELD_MISSMATCH_H +#define IGL_CROSS_FIELD_MISSMATCH_H +#include "igl_inline.h" +#include +namespace igl +{ + // Calculates the missmatch (integer), at each face edge, of a cross field defined on the mesh faces. + // The integer missmatch is a multiple of pi/2 that transforms the cross on one side of the edge to + // the cross on the other side. It represents the deviation from a Lie connection across the edge. + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (quad) indices + // PD1 #F by 3 eigen Matrix of the first per face cross field vector + // PD2 #F by 3 eigen Matrix of the second per face cross field vector + // isCombed boolean, specifying whether the field is combed (i.e. matching has been precomputed. + // If not, the field is combed first. + // Output: + // Handle_MMatch #F by 3 eigen Matrix containing the integer missmatch of the cross field + // across all face edges + // + + template + IGL_INLINE void cross_field_missmatch(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + const bool isCombed, + Eigen::PlainObjectBase &missmatch); +} +#ifndef IGL_STATIC_LIBRARY +#include "cross_field_missmatch.cpp" +#endif + +#endif diff --git a/src/igl/crouzeix_raviart_cotmatrix.cpp b/src/igl/crouzeix_raviart_cotmatrix.cpp new file mode 100644 index 000000000..2fc5f6d4a --- /dev/null +++ b/src/igl/crouzeix_raviart_cotmatrix.cpp @@ -0,0 +1,100 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "crouzeix_raviart_cotmatrix.h" +#include "unique_simplices.h" +#include "oriented_facets.h" +#include "is_edge_manifold.h" +#include "cotmatrix_entries.h" + +template +void igl::crouzeix_raviart_cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix & L, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) +{ + // All occurrences of directed "facets" + Eigen::MatrixXi allE; + oriented_facets(F,allE); + Eigen::VectorXi _1; + unique_simplices(allE,E,_1,EMAP); + return crouzeix_raviart_cotmatrix(V,F,E,EMAP,L); +} + +template +void igl::crouzeix_raviart_cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + Eigen::SparseMatrix & L) +{ + // number of rows + const int m = F.rows(); + // Element simplex size + const int ss = F.cols(); + // Mesh should be edge-manifold + assert(F.cols() != 3 || is_edge_manifold(F)); + typedef Eigen::Matrix MatrixXS; + MatrixXS C; + cotmatrix_entries(V,F,C); + Eigen::MatrixXi F2E(m,ss); + { + int k =0; + for(int c = 0;c > LIJV;LIJV.reserve(k*m); + Eigen::VectorXi LI(k),LJ(k),LV(k); + // Compensation factor to match scales in matlab version + double factor = 2.0; + + switch(ss) + { + default: assert(false && "unsupported simplex size"); + case 3: + factor = 4.0; + LI<<0,1,2,1,2,0,0,1,2,1,2,0; + LJ<<1,2,0,0,1,2,0,1,2,1,2,0; + LV<<2,0,1,2,0,1,2,0,1,2,0,1; + break; + case 4: + factor *= -1.0; + LI<<0,3,3,3,1,2,1,0,1,2,2,0,0,3,3,3,1,2,1,0,1,2,2,0; + LJ<<1,0,1,2,2,0,0,3,3,3,1,2,0,3,3,3,1,2,1,0,1,2,2,0; + LV<<2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1; + break; + } + + for(int f=0;f, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/crouzeix_raviart_cotmatrix.h b/src/igl/crouzeix_raviart_cotmatrix.h new file mode 100644 index 000000000..1504ddad0 --- /dev/null +++ b/src/igl/crouzeix_raviart_cotmatrix.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CROUZEIX_RAVIART_COTMATRIX +#define IGL_CROUZEIX_RAVIART_COTMATRIX +#include "igl_inline.h" +#include +#include +namespace igl +{ + // CROUZEIX_RAVIART_COTMATRIX Compute the Crouzeix-Raviart cotangent + // stiffness matrix. + // + // See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, + // Harmon, Zorin, Grinspun 2007] + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3/4 list of triangle/tetrahedron indices + // Outputs: + // L #E by #E edge/face-based diagonal cotangent matrix + // E #E by 2/3 list of edges/faces + // EMAP #F*3/4 list of indices mapping allE to E + // + // See also: crouzeix_raviart_massmatrix + template + void crouzeix_raviart_cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix & L, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); + // wrapper if E and EMAP are already computed (better match!) + template + void crouzeix_raviart_cotmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + Eigen::SparseMatrix & L); +} +#ifndef IGL_STATIC_LIBRARY +# include "crouzeix_raviart_cotmatrix.cpp" +#endif +#endif diff --git a/src/igl/crouzeix_raviart_massmatrix.cpp b/src/igl/crouzeix_raviart_massmatrix.cpp new file mode 100644 index 000000000..0873712d5 --- /dev/null +++ b/src/igl/crouzeix_raviart_massmatrix.cpp @@ -0,0 +1,85 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "crouzeix_raviart_massmatrix.h" +#include "unique_simplices.h" +#include "oriented_facets.h" + +#include "is_edge_manifold.h" +#include "doublearea.h" +#include "volume.h" + +#include +#include + +template +void igl::crouzeix_raviart_massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix & M, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) +{ + // All occurrences of directed "facets" + Eigen::MatrixXi allE; + oriented_facets(F,allE); + Eigen::VectorXi _1; + unique_simplices(allE,E,_1,EMAP); + return crouzeix_raviart_massmatrix(V,F,E,EMAP,M); +} + +template +void igl::crouzeix_raviart_massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + Eigen::SparseMatrix & M) +{ + using namespace Eigen; + using namespace std; + // Mesh should be edge-manifold (TODO: replace `is_edge_manifold` with + // `is_facet_manifold`) + assert(F.cols() != 3 || is_edge_manifold(F)); + // number of elements (triangles) + const int m = F.rows(); + // Get triangle areas/volumes + VectorXd TA; + // Element simplex size + const int ss = F.cols(); + switch(ss) + { + default: + assert(false && "Unsupported simplex size"); + case 3: + doublearea(V,F,TA); + TA *= 0.5; + break; + case 4: + volume(V,F,TA); + break; + } + vector > MIJV(ss*m); + assert(EMAP.size() == m*ss); + for(int f = 0;f(EMAP(f+m*c),EMAP(f+m*c),TA(f)/(double)(ss)); + } + } + M.resize(E.rows(),E.rows()); + M.setFromTriplets(MIJV.begin(),MIJV.end()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::crouzeix_raviart_massmatrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::crouzeix_raviart_massmatrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::crouzeix_raviart_massmatrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/crouzeix_raviart_massmatrix.h b/src/igl/crouzeix_raviart_massmatrix.h new file mode 100644 index 000000000..256aa2067 --- /dev/null +++ b/src/igl/crouzeix_raviart_massmatrix.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef CROUZEIX_RAVIART_MASSMATRIX_H +#define CROUZEIX_RAVIART_MASSMATRIX_H +#include +#include + +namespace igl +{ + // CROUZEIX_RAVIART_MASSMATRIX Compute the Crouzeix-Raviart mass matrix where + // M(e,e) is just the sum of the areas of the triangles on either side of an + // edge e. + // + // See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, + // Harmon, Zorin, Grinspun 2007] + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3/4 list of triangle/tetrahedron indices + // Outputs: + // M #E by #E edge/face-based diagonal mass matrix + // E #E by 2/3 list of edges/faces + // EMAP #F*3/4 list of indices mapping allE to E + // + // See also: crouzeix_raviart_cotmatrix + template + void crouzeix_raviart_massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix & M, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); + // wrapper if E and EMAP are already computed (better match!) + template + void crouzeix_raviart_massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + Eigen::SparseMatrix & M); +} +#ifndef IGL_STATIC_LIBRARY +# include "crouzeix_raviart_massmatrix.cpp" +#endif + +#endif diff --git a/src/igl/cumsum.cpp b/src/igl/cumsum.cpp new file mode 100644 index 000000000..38eb4edd3 --- /dev/null +++ b/src/igl/cumsum.cpp @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cumsum.h" +#include +#include + +template +IGL_INLINE void igl::cumsum( + const Eigen::MatrixBase & X, + const int dim, + Eigen::PlainObjectBase & Y) +{ + using namespace Eigen; + using namespace std; + Y.resizeLike(X); + // get number of columns (or rows) + int num_outer = (dim == 1 ? X.cols() : X.rows() ); + // get number of rows (or columns) + int num_inner = (dim == 1 ? X.rows() : X.cols() ); + // This has been optimized so that dim = 1 or 2 is roughly the same cost. + // (Optimizations assume ColMajor order) + if(dim == 1) + { +#pragma omp parallel for + for(int o = 0;o, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::cumsum, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::cumsum, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::cumsum, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::cumsum, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::cumsum, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::cumsum, class Eigen::Matrix>(class Eigen::MatrixBase> const &, int, class Eigen::PlainObjectBase> &); +template void igl::cumsum, class Eigen::Matrix>(class Eigen::MatrixBase> const &, int, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/cumsum.h b/src/igl/cumsum.h new file mode 100644 index 000000000..c3fa59258 --- /dev/null +++ b/src/igl/cumsum.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CUMSUM_H +#define IGL_CUMSUM_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // Computes a cumulative sum of the columns of X, like matlab's `cumsum`. + // + // Templates: + // DerivedX Type of matrix X + // DerivedY Type of matrix Y + // Inputs: + // X m by n Matrix to be cumulatively summed. + // dim dimension to take cumulative sum (1 or 2) + // Output: + // Y m by n Matrix containing cumulative sum. + // + template + IGL_INLINE void cumsum( + const Eigen::MatrixBase & X, + const int dim, + Eigen::PlainObjectBase & Y); + //template + //IGL_INLINE void cumsum( + // const Eigen::MatrixBase & X, + // Eigen::PlainObjectBase & Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "cumsum.cpp" +#endif + +#endif + diff --git a/src/igl/cut_mesh.cpp b/src/igl/cut_mesh.cpp new file mode 100644 index 000000000..33a25ce93 --- /dev/null +++ b/src/igl/cut_mesh.cpp @@ -0,0 +1,330 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include +#include +#include +#include +#include +#include + +// This file violates many of the libigl style guidelines. + +namespace igl { + + + template + class MeshCutterMini + { + public: + // Input + //mesh + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + // TT is the same type as TTi? This is likely to break at some point + const Eigen::PlainObjectBase &TT; + const Eigen::PlainObjectBase &TTi; + const std::vector >& VF; + const std::vector >& VFi; + const std::vector &V_border; // bool + //edges to cut + const Eigen::PlainObjectBase &Handle_Seams; // 3 bool + + // total number of scalar variables + int num_scalar_variables; + + // per face indexes of vertex in the solver + DerivedF HandleS_Index; + + // per vertex variable indexes + std::vector > HandleV_Integer; + + IGL_INLINE MeshCutterMini( + const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const std::vector > &_VF, + const std::vector > &_VFi, + const std::vector &_V_border, + const Eigen::PlainObjectBase &_Handle_Seams); + + // vertex to variable mapping + // initialize the mapping for a given sampled mesh + IGL_INLINE void InitMappingSeam(); + + private: + + IGL_INLINE void FirstPos(const int v, int &f, int &edge); + + IGL_INLINE int AddNewIndex(const int v0); + + IGL_INLINE bool IsSeam(const int f0, const int f1); + + // find initial position of the pos to + // assing face to vert inxex correctly + IGL_INLINE void FindInitialPos(const int vert, int &edge, int &face); + + + // initialize the mapping given an initial pos + // whih must be initialized with FindInitialPos + IGL_INLINE void MapIndexes(const int vert, const int edge_init, const int f_init); + + // initialize the mapping for a given vertex + IGL_INLINE void InitMappingSeam(const int vert); + + }; +} + + +template +IGL_INLINE igl::MeshCutterMini:: +MeshCutterMini( + const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_TT, + const Eigen::PlainObjectBase &_TTi, + const std::vector > &_VF, + const std::vector > &_VFi, + const std::vector &_V_border, + const Eigen::PlainObjectBase &_Handle_Seams): + V(_V), + F(_F), + TT(_TT), + TTi(_TTi), + VF(_VF), + VFi(_VFi), + V_border(_V_border), + Handle_Seams(_Handle_Seams) +{ + num_scalar_variables=0; + HandleS_Index.setConstant(F.rows(),3,-1); + HandleV_Integer.resize(V.rows()); +} + + +template +IGL_INLINE void igl::MeshCutterMini:: +FirstPos(const int v, int &f, int &edge) +{ + f = VF[v][0]; // f=v->cVFp(); + edge = VFi[v][0]; // edge=v->cVFi(); +} + +template +IGL_INLINE int igl::MeshCutterMini:: +AddNewIndex(const int v0) +{ + num_scalar_variables++; + HandleV_Integer[v0].push_back(num_scalar_variables); + return num_scalar_variables; +} + +template +IGL_INLINE bool igl::MeshCutterMini:: +IsSeam(const int f0, const int f1) +{ + for (int i=0;i<3;i++) + { + int f_clos = TT(f0,i); + + if (f_clos == -1) + continue; ///border + + if (f_clos == f1) + return(Handle_Seams(f0,i)); + } + assert(0); + return false; +} + +///find initial position of the pos to +// assing face to vert inxex correctly +template +IGL_INLINE void igl::MeshCutterMini:: +FindInitialPos(const int vert, + int &edge, + int &face) +{ + int f_init; + int edge_init; + FirstPos(vert,f_init,edge_init); // todo manually the function + igl::HalfEdgeIterator VFI(F,TT,TTi,f_init,edge_init); + + bool vertexB = V_border[vert]; + bool possible_split=false; + bool complete_turn=false; + do + { + int curr_f = VFI.Fi(); + int curr_edge=VFI.Ei(); + VFI.NextFE(); + int next_f=VFI.Fi(); + ///test if I've just crossed a border + bool on_border=(TT(curr_f,curr_edge)==-1); + //bool mismatch=false; + bool seam=false; + + ///or if I've just crossed a seam + ///if I'm on a border I MUST start from the one next t othe border + if (!vertexB) + //seam=curr_f->IsSeam(next_f); + seam=IsSeam(curr_f,next_f); + // if (vertexB) + // assert(!Handle_Singular(vert)); + // ; + //assert(!vert->IsSingular()); + possible_split=((on_border)||(seam)); + complete_turn = next_f == f_init; + } while ((!possible_split)&&(!complete_turn)); + face=VFI.Fi(); + edge=VFI.Ei(); +} + + + +///initialize the mapping given an initial pos +///whih must be initialized with FindInitialPos +template +IGL_INLINE void igl::MeshCutterMini:: +MapIndexes(const int vert, + const int edge_init, + const int f_init) +{ + ///check that is not on border.. + ///in such case maybe it's non manyfold + ///insert an initial index + int curr_index=AddNewIndex(vert); + ///and initialize the jumping pos + igl::HalfEdgeIterator VFI(F,TT,TTi,f_init,edge_init); + bool complete_turn=false; + do + { + int curr_f = VFI.Fi(); + int curr_edge = VFI.Ei(); + ///assing the current index + HandleS_Index(curr_f,curr_edge) = curr_index; + VFI.NextFE(); + int next_f = VFI.Fi(); + ///test if I've finiseh with the face exploration + complete_turn = (next_f==f_init); + ///or if I've just crossed a mismatch + if (!complete_turn) + { + bool seam=false; + //seam=curr_f->IsSeam(next_f); + seam=IsSeam(curr_f,next_f); + if (seam) + { + ///then add a new index + curr_index=AddNewIndex(vert); + } + } + } while (!complete_turn); +} + +///initialize the mapping for a given vertex +template +IGL_INLINE void igl::MeshCutterMini:: +InitMappingSeam(const int vert) +{ + ///first rotate until find the first pos after a mismatch + ///or a border or return to the first position... + int f_init = VF[vert][0]; + int indexE = VFi[vert][0]; + + igl::HalfEdgeIterator VFI(F,TT,TTi,f_init,indexE); + + int edge_init; + int face_init; + FindInitialPos(vert,edge_init,face_init); + MapIndexes(vert,edge_init,face_init); +} + +///vertex to variable mapping +///initialize the mapping for a given sampled mesh +template +IGL_INLINE void igl::MeshCutterMini:: +InitMappingSeam() +{ + num_scalar_variables=-1; + for (unsigned int i=0;i0); +} + + +template +IGL_INLINE void igl::cut_mesh( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const std::vector >& VF, + const std::vector >& VFi, + const Eigen::PlainObjectBase& TT, + const Eigen::PlainObjectBase& TTi, + const std::vector &V_border, + const Eigen::PlainObjectBase &cuts, + Eigen::PlainObjectBase &Vcut, + Eigen::PlainObjectBase &Fcut) +{ + //finding the cuts is done, now we need to actually generate a cut mesh + igl::MeshCutterMini mc(V, F, TT, TTi, VF, VFi, V_border, cuts); + mc.InitMappingSeam(); + + Fcut = mc.HandleS_Index; + //we have the faces, we need the vertices; + int newNumV = Fcut.maxCoeff()+1; + Vcut.setZero(newNumV,3); + for (int vi=0; vi +IGL_INLINE void igl::cut_mesh( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &cuts, + Eigen::PlainObjectBase &Vcut, + Eigen::PlainObjectBase &Fcut) +{ + std::vector > VF, VFi; + igl::vertex_triangle_adjacency(V,F,VF,VFi); + // Alec: Cast? Why? This is likely to break. + Eigen::MatrixXd Vt = V; + Eigen::MatrixXi Ft = F; + Eigen::MatrixXi TT, TTi; + igl::triangle_triangle_adjacency(Ft,TT,TTi); + std::vector V_border = igl::is_border_vertex(V,F); + igl::cut_mesh(V, F, VF, VFi, TT, TTi, V_border, cuts, Vcut, Fcut); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::cut_mesh, Eigen::Matrix, int, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/cut_mesh.h b/src/igl/cut_mesh.h new file mode 100644 index 000000000..e49a3e38b --- /dev/null +++ b/src/igl/cut_mesh.h @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CUT_MESH +#define IGL_CUT_MESH +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Given a mesh and a list of edges that are to be cut, the function + // generates a new disk-topology mesh that has the cuts at its boundary. + // + // Todo: this combinatorial operation should not depend on the vertex + // positions V. + // + // Known issues: Assumes mesh is edge-manifold. + // + // Inputs: + // V #V by 3 list of the vertex positions + // F #F by 3 list of the faces (must be triangles) + // VF #V list of lists of incident faces (adjacency list), e.g. as + // returned by igl::vertex_triangle_adjacency + // VFi #V list of lists of index of incidence within incident faces listed + // in VF, e.g. as returned by igl::vertex_triangle_adjacency + // TT #F by 3 triangle to triangle adjacent matrix (e.g. computed via + // igl:triangle_triangle_adjacency) + // TTi #F by 3 adjacent matrix, the element i,j is the id of edge of the + // triangle TT(i,j) that is adjacent with triangle i (e.g. computed via + // igl:triangle_triangle_adjacency) + // V_border #V by 1 list of booleans, indicating if the corresponging + // vertex is at the mesh boundary, e.g. as returned by + // igl::is_border_vertex + // cuts #F by 3 list of boolean flags, indicating the edges that need to + // be cut (has 1 at the face edges that are to be cut, 0 otherwise) + // Outputs: + // Vcut #V by 3 list of the vertex positions of the cut mesh. This matrix + // will be similar to the original vertices except some rows will be + // duplicated. + // Fcut #F by 3 list of the faces of the cut mesh(must be triangles). This + // matrix will be similar to the original face matrix except some indices + // will be redirected to point to the newly duplicated vertices. + // + template < + typename DerivedV, + typename DerivedF, + typename VFType, + typename DerivedTT, + typename DerivedC> + IGL_INLINE void cut_mesh( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const std::vector >& VF, + const std::vector >& VFi, + const Eigen::PlainObjectBase& TT, + const Eigen::PlainObjectBase& TTi, + const std::vector &V_border, + const Eigen::PlainObjectBase &cuts, + Eigen::PlainObjectBase &Vcut, + Eigen::PlainObjectBase &Fcut); + //Wrapper of the above with only vertices and faces as mesh input + template + IGL_INLINE void cut_mesh( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &cuts, + Eigen::PlainObjectBase &Vcut, + Eigen::PlainObjectBase &Fcut); +}; + + +#ifndef IGL_STATIC_LIBRARY +#include "cut_mesh.cpp" +#endif + + +#endif diff --git a/src/igl/cut_mesh_from_singularities.cpp b/src/igl/cut_mesh_from_singularities.cpp new file mode 100644 index 000000000..382f2e063 --- /dev/null +++ b/src/igl/cut_mesh_from_singularities.cpp @@ -0,0 +1,205 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "cut_mesh_from_singularities.h" + +#include +#include + +#include +#include + + +namespace igl { + template < + typename DerivedV, + typename DerivedF, + typename DerivedM, + typename DerivedO + > + class MeshCutter + { + protected: + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &Handle_MMatch; + + Eigen::VectorXi F_visited; + DerivedF TT; + DerivedF TTi; + + Eigen::MatrixXi E, F2E, E2F; + protected: + + inline bool IsRotSeam(const int f0,const int edge) + { + unsigned char MM = Handle_MMatch(f0,edge); + return (MM!=0); + } + + inline void FloodFill(const int start, Eigen::PlainObjectBase &Handle_Seams) + { + std::deque d; + ///clean the visited flag + F_visited(start) = true; + d.push_back(start); + + while (!d.empty()) + { + int f = d.at(0); d.pop_front(); + for (int s = 0; s<3; s++) + { + int g = TT(f,s); // f->FFp(s); + int j = TTi(f,s); // f->FFi(s); + + if (j == -1) + { + g = f; + j = s; + } + + if ((!(IsRotSeam(f,s))) && (!(IsRotSeam(g,j))) && (!F_visited(g)) ) + { + Handle_Seams(f,s)=false; + Handle_Seams(g,j)=false; + F_visited(g) = true; + d.push_back(g); + } + } + } + } + + inline void Retract(Eigen::PlainObjectBase &Handle_Seams) + { + std::vector e(V.rows(),0); // number of edges per vert + // for (unsigned f=0; fIsD()) + { + for (int s = 0; s<3; s++) + { + if (Handle_Seams(f,s)) + if (!(IsRotSeam(f,s))) // never retract rot seams + { + if (e[ F(f,s) ] == 1) { + // dissolve seam + Handle_Seams(f,s)=false; + if (TT(f,s) != -1) + Handle_Seams(TT(f,s),TTi(f,s))=false; + + e[ F(f,s)] --; + e[ F(f,(s+1)%3) ] --; + over = false; + } + } + } + } + + if (guard++>10000) + over = true; + + } while (!over); + } + + public: + + inline MeshCutter(const Eigen::PlainObjectBase &V_, + const Eigen::PlainObjectBase &F_, + const Eigen::PlainObjectBase &Handle_MMatch_): + V(V_), + F(F_), + Handle_MMatch(Handle_MMatch_) + { + triangle_triangle_adjacency(F,TT,TTi); + edge_topology(V,F,E,F2E,E2F); + }; + + inline void cut(Eigen::PlainObjectBase &Handle_Seams) + { + F_visited.setConstant(F.rows(),0); + Handle_Seams.setConstant(F.rows(),3,1); + + int index=0; + for (unsigned f = 0; f +IGL_INLINE void igl::cut_mesh_from_singularities(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &Handle_MMatch, + Eigen::PlainObjectBase &Handle_Seams) +{ + igl::MeshCutter< DerivedV, DerivedF, DerivedM, DerivedO> mc(V, F, Handle_MMatch); + mc.cut(Handle_Seams); + +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::cut_mesh_from_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/cut_mesh_from_singularities.h b/src/igl/cut_mesh_from_singularities.h new file mode 100644 index 000000000..13b6a98c4 --- /dev/null +++ b/src/igl/cut_mesh_from_singularities.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_CUT_MESH_FROM_SINGULARITIES_H +#define IGL_CUT_MESH_FROM_SINGULARITIES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Given a mesh (V,F) and the integer mismatch of a cross field per edge + // (MMatch), finds the cut_graph connecting the singularities (seams) and the + // degree of the singularities singularity_index + // + // Input: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of faces + // MMatch #F by 3 list of per corner integer mismatch + // Outputs: + // seams #F by 3 list of per corner booleans that denotes if an edge is a + // seam or not + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedM, + typename DerivedO> + IGL_INLINE void cut_mesh_from_singularities( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &MMatch, + Eigen::PlainObjectBase &seams); +} +#ifndef IGL_STATIC_LIBRARY +#include "cut_mesh_from_singularities.cpp" +#endif + +#endif diff --git a/src/igl/cylinder.cpp b/src/igl/cylinder.cpp new file mode 100644 index 000000000..ab0fd4d3d --- /dev/null +++ b/src/igl/cylinder.cpp @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "cylinder.h" +#include "PI.h" +#include +#include + +template +IGL_INLINE void igl::cylinder( + const int axis_devisions, + const int height_devisions, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + V.resize(axis_devisions*height_devisions,3); + F.resize(2*(axis_devisions*(height_devisions-1)),3); + int f = 0; + typedef typename DerivedV::Scalar Scalar; + for(int th = 0;th 0) + { + F(f,0) = ((th+0)%axis_devisions)+(h-1)*axis_devisions; + F(f,1) = ((th+1)%axis_devisions)+(h-1)*axis_devisions; + F(f,2) = ((th+0)%axis_devisions)+(h+0)*axis_devisions; + f++; + F(f,0) = ((th+1)%axis_devisions)+(h-1)*axis_devisions; + F(f,1) = ((th+1)%axis_devisions)+(h+0)*axis_devisions; + F(f,2) = ((th+0)%axis_devisions)+(h+0)*axis_devisions; + f++; + } + } + } + assert(f == F.rows()); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::cylinder, Eigen::Matrix >(int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/cylinder.h b/src/igl/cylinder.h new file mode 100644 index 000000000..4c5ab7e22 --- /dev/null +++ b/src/igl/cylinder.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_CYLINDER_H +#define IGL_CYLINDER_H +#include "igl_inline.h" +#include +namespace igl +{ + // Construct a triangle mesh of a cylinder (without caps) + // + // Inputs: + // axis_devisions number of vertices _around the cylinder_ + // height_devisions number of vertices _up the cylinder_ + // Outputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of triangle indices into V + // + template + IGL_INLINE void cylinder( + const int axis_devisions, + const int height_devisions, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); +} +#ifndef IGL_STATIC_LIBRARY +# include "cylinder.cpp" +#endif +#endif diff --git a/src/igl/dated_copy.cpp b/src/igl/dated_copy.cpp new file mode 100644 index 000000000..735de6fc4 --- /dev/null +++ b/src/igl/dated_copy.cpp @@ -0,0 +1,91 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "dated_copy.h" +#include "dirname.h" +#include "basename.h" + +#include +#include +#include +#include +#include + +#if !defined(_WIN32) +#include + +IGL_INLINE bool igl::dated_copy(const std::string & src_path, const std::string & dir) +{ + using namespace std; + // Get time and date as string + char buffer[80]; + time_t rawtime; + struct tm * timeinfo; + time (&rawtime); + timeinfo = localtime (&rawtime); + // ISO 8601 format with hyphens instead of colons and no timezone offset + strftime (buffer,80,"%Y-%m-%dT%H-%M-%S",timeinfo); + string src_basename = basename(src_path); + string dst_basename = src_basename+"-"+buffer; + string dst_path = dir+"/"+dst_basename; + cerr<<"Saving binary to "< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +// Known issues: This function does not work under windows + +#ifndef IGL_DATED_COPY_H +#define IGL_DATED_COPY_H +#include "igl_inline.h" +#include +namespace igl +{ + // Copy the given file to a new file with the same basename in `dir` + // directory with the current date and time as a suffix. + // + // Inputs: + // src_path path to source file + // dir directory of destination file + // Example: + // dated_copy("/path/to/foo","/bar/"); + // // copies /path/to/foo to /bar/foo-2013-12-12T18-10-56 + IGL_INLINE bool dated_copy(const std::string & src_path, const std::string & dir); + // Wrapper using directory of source file + IGL_INLINE bool dated_copy(const std::string & src_path); +} +#ifndef IGL_STATIC_LIBRARY +# include "dated_copy.cpp" +#endif +#endif + diff --git a/src/igl/decimate.cpp b/src/igl/decimate.cpp new file mode 100644 index 000000000..a531db2c5 --- /dev/null +++ b/src/igl/decimate.cpp @@ -0,0 +1,366 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "decimate.h" +#include "collapse_edge.h" +#include "edge_flaps.h" +#include "is_edge_manifold.h" +#include "remove_unreferenced.h" +#include "slice_mask.h" +#include "slice.h" +#include "connect_boundary_to_infinity.h" +#include "max_faces_stopping_condition.h" +#include "shortest_edge_and_midpoint.h" + +IGL_INLINE bool igl::decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I) +{ + // Original number of faces + const int orig_m = F.rows(); + // Tracking number of faces + int m = F.rows(); + typedef Eigen::MatrixXd DerivedV; + typedef Eigen::MatrixXi DerivedF; + DerivedV VO; + DerivedF FO; + igl::connect_boundary_to_infinity(V,F,VO,FO); + // decimate will not work correctly on non-edge-manifold meshes. By extension + // this includes meshes with non-manifold vertices on the boundary since these + // will create a non-manifold edge when connected to infinity. + if(!is_edge_manifold(FO)) + { + return false; + } + bool ret = decimate( + VO, + FO, + shortest_edge_and_midpoint, + max_faces_stopping_condition(m,orig_m,max_m), + U, + G, + J, + I); + const Eigen::Array keep = (J.array() & cost_and_placement, + const std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I + ) +{ + const auto always_try = []( + const Eigen::MatrixXd & ,/*V*/ + const Eigen::MatrixXi & ,/*F*/ + const Eigen::MatrixXi & ,/*E*/ + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & ,/*EF*/ + const Eigen::MatrixXi & ,/*EI*/ + const std::set > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + ) -> bool { return true;}; + const auto never_care = []( + const Eigen::MatrixXd & , /*V*/ + const Eigen::MatrixXi & , /*F*/ + const Eigen::MatrixXi & , /*E*/ + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & , /*EF*/ + const Eigen::MatrixXi & , /*EI*/ + const std::set > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )-> void { }; + return igl::decimate( + OV,OF,cost_and_placement,stopping_condition,always_try,never_care,U,G,J,I); +} + +IGL_INLINE bool igl::decimate( + const Eigen::MatrixXd & OV, + const Eigen::MatrixXi & OF, + const std::function & cost_and_placement, + const std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I + ) +{ + using namespace Eigen; + using namespace std; + VectorXi EMAP; + MatrixXi E,EF,EI; + edge_flaps(OF,E,EMAP,EF,EI); + return igl::decimate( + OV,OF, + cost_and_placement,stopping_condition,pre_collapse,post_collapse, + E,EMAP,EF,EI, + U,G,J,I); +} + + +IGL_INLINE bool igl::decimate( + const Eigen::MatrixXd & OV, + const Eigen::MatrixXi & OF, + const std::function & cost_and_placement, + const std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + const Eigen::MatrixXi & OE, + const Eigen::VectorXi & OEMAP, + const Eigen::MatrixXi & OEF, + const Eigen::MatrixXi & OEI, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I + ) +{ + + // Decimate 1 + using namespace Eigen; + using namespace std; + // Working copies + Eigen::MatrixXd V = OV; + Eigen::MatrixXi F = OF; + Eigen::MatrixXi E = OE; + Eigen::VectorXi EMAP = OEMAP; + Eigen::MatrixXi EF = OEF; + Eigen::MatrixXi EI = OEI; + typedef std::set > PriorityQueue; + PriorityQueue Q; + std::vector Qit; + Qit.resize(E.rows()); + // If an edge were collapsed, we'd collapse it to these points: + MatrixXd C(E.rows(),V.cols()); + for(int e = 0;e(cost,e)).first; + } + int prev_e = -1; + bool clean_finish = false; + + while(true) + { + if(Q.empty()) + { + break; + } + if(Q.begin()->first == std::numeric_limits::infinity()) + { + // min cost edge is infinite cost + break; + } + int e,e1,e2,f1,f2; + if(collapse_edge( + cost_and_placement, pre_collapse, post_collapse, + V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2)) + { + if(stopping_condition(V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2)) + { + clean_finish = true; + break; + } + }else + { + if(prev_e == e) + { + assert(false && "Edge collapse no progress... bad stopping condition?"); + break; + } + // Edge was not collapsed... must have been invalid. collapse_edge should + // have updated its cost to inf... continue + } + prev_e = e; + } + // remove all IGL_COLLAPSE_EDGE_NULL faces + MatrixXi F2(F.rows(),3); + J.resize(F.rows()); + int m = 0; + for(int f = 0;f +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DECIMATE_H +#define IGL_DECIMATE_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Assumes (V,F) is a manifold mesh (possibly with boundary) Collapses edges + // until desired number of faces is achieved. This uses default edge cost and + // merged vertex placement functions {edge length, edge midpoint}. + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3 list of face indices into V. + // max_m desired number of output faces + // Outputs: + // U #U by dim list of output vertex posistions (can be same ref as V) + // G #G by 3 list of output face indices into U (can be same ref as G) + // J #G list of indices into F of birth face + // I #U list of indices into V of birth vertices + // Returns true if m was reached (otherwise #G > m) + IGL_INLINE bool decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I); + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3 list of face indices into V. + // max_m desired number of output faces + // Outputs: + // U #U by dim list of output vertex posistions (can be same ref as V) + // G #G by 3 list of output face indices into U (can be same ref as G) + // J #G list of indices into F of birth face + // Returns true if m was reached (otherwise #G > m) + IGL_INLINE bool decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J); + // Assumes a **closed** manifold mesh. See igl::connect_boundary_to_infinity + // and igl::decimate in decimate.cpp + // is handling meshes with boundary by connecting all boundary edges with + // dummy facets to infinity **and** modifying the stopping criteria. + // + // Inputs: + // cost_and_placement function computing cost of collapsing an edge and 3d + // position where it should be placed: + // cost_and_placement(V,F,E,EMAP,EF,EI,cost,placement); + // stopping_condition function returning whether to stop collapsing edges + // based on current state. Guaranteed to be called after _successfully_ + // collapsing edge e removing edges (e,e1,e2) and faces (f1,f2): + // bool should_stop = + // stopping_condition(V,F,E,EMAP,EF,EI,Q,Qit,C,e,e1,e2,f1,f2); + IGL_INLINE bool decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int ,/*e*/ + const int ,/*e1*/ + const int ,/*e2*/ + const int ,/*f1*/ + const int /*f2*/ + )> & stopping_condition, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I); + + // Inputs: + // pre_collapse callback called with index of edge whose collapse is about + // to be attempted (see collapse_edge) + // post_collapse callback called with index of edge whose collapse was + // just attempted and a flag revealing whether this was successful (see + // collapse_edge) + IGL_INLINE bool decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int ,/*e*/ + const int ,/*e1*/ + const int ,/*e2*/ + const int ,/*f1*/ + const int /*f2*/ + )> & stopping_condition, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I); + + // Inputs: + // EMAP #F*3 list of indices into E, mapping each directed edge to unique + // unique edge in E + // EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + + IGL_INLINE bool decimate( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & cost_and_placement, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int ,/*e*/ + const int ,/*e1*/ + const int ,/*e2*/ + const int ,/*f1*/ + const int /*f2*/ + )> & stopping_condition, + const std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + const std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "decimate.cpp" +#endif +#endif + diff --git a/src/igl/deform_skeleton.cpp b/src/igl/deform_skeleton.cpp new file mode 100644 index 000000000..b9d120edd --- /dev/null +++ b/src/igl/deform_skeleton.cpp @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "deform_skeleton.h" +void igl::deform_skeleton( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const std::vector< + Eigen::Affine3d,Eigen::aligned_allocator > & vA, + Eigen::MatrixXd & CT, + Eigen::MatrixXi & BET) +{ + using namespace Eigen; + assert(BE.rows() == (int)vA.size()); + CT.resize(2*BE.rows(),C.cols()); + BET.resize(BE.rows(),2); + for(int e = 0;e +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DEFORM_SKELETON_H +#define IGL_DEFORM_SKELETON_H +#include "igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + // Deform a skeleton. + // + // Inputs: + // C #C by 3 list of joint positions + // BE #BE by 2 list of bone edge indices + // vA #BE list of bone transformations + // Outputs + // CT #BE*2 by 3 list of deformed joint positions + // BET #BE by 2 list of bone edge indices (maintains order) + // + IGL_INLINE void deform_skeleton( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const std::vector< + Eigen::Affine3d,Eigen::aligned_allocator > & vA, + Eigen::MatrixXd & CT, + Eigen::MatrixXi & BET); + // Inputs: + // T #BE*4 by 3 list of stacked transformation matrix + IGL_INLINE void deform_skeleton( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXd & T, + Eigen::MatrixXd & CT, + Eigen::MatrixXi & BET); +} + +#ifndef IGL_STATIC_LIBRARY +# include "deform_skeleton.cpp" +#endif +#endif diff --git a/src/igl/delaunay_triangulation.cpp b/src/igl/delaunay_triangulation.cpp new file mode 100644 index 000000000..01669966b --- /dev/null +++ b/src/igl/delaunay_triangulation.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "delaunay_triangulation.h" +#include "flip_edge.h" +#include "lexicographic_triangulation.h" +#include "unique_edge_map.h" + +#include +#include + +template< + typename DerivedV, + typename Orient2D, + typename InCircle, + typename DerivedF> +IGL_INLINE void igl::delaunay_triangulation( + const Eigen::PlainObjectBase& V, + Orient2D orient2D, + InCircle incircle, + Eigen::PlainObjectBase& F) +{ + assert(V.cols() == 2); + typedef typename DerivedF::Scalar Index; + typedef typename DerivedV::Scalar Scalar; + igl::lexicographic_triangulation(V, orient2D, F); + const size_t num_faces = F.rows(); + if (num_faces == 0) { + // Input points are degenerate. No faces will be generated. + return; + } + assert(F.cols() == 3); + + Eigen::MatrixXi E; + Eigen::MatrixXi uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + + auto is_delaunay = [&V,&F,&uE2E,num_faces,&incircle](size_t uei) { + auto& half_edges = uE2E[uei]; + if (half_edges.size() != 2) { + throw "Cannot flip non-manifold or boundary edge"; + } + + const size_t f1 = half_edges[0] % num_faces; + const size_t f2 = half_edges[1] % num_faces; + const size_t c1 = half_edges[0] / num_faces; + const size_t c2 = half_edges[1] / num_faces; + assert(c1 < 3); + assert(c2 < 3); + assert(f1 != f2); + const size_t v1 = F(f1, (c1+1)%3); + const size_t v2 = F(f1, (c1+2)%3); + const size_t v4 = F(f1, c1); + const size_t v3 = F(f2, c2); + const Scalar p1[] = {V(v1, 0), V(v1, 1)}; + const Scalar p2[] = {V(v2, 0), V(v2, 1)}; + const Scalar p3[] = {V(v3, 0), V(v3, 1)}; + const Scalar p4[] = {V(v4, 0), V(v4, 1)}; + auto orientation = incircle(p1, p2, p4, p3); + return orientation <= 0; + }; + + bool all_delaunay = false; + while(!all_delaunay) { + all_delaunay = true; + for (size_t i=0; i, short (*)(double const*, double const*, double const*), short (*)(double const*, double const*, double const*, double const*), Eigen::Matrix >(Eigen::PlainObjectBase > const&, short (*)(double const*, double const*, double const*), short (*)(double const*, double const*, double const*, double const*), Eigen::PlainObjectBase >&); +#endif \ No newline at end of file diff --git a/src/igl/delaunay_triangulation.h b/src/igl/delaunay_triangulation.h new file mode 100644 index 000000000..daeec9fad --- /dev/null +++ b/src/igl/delaunay_triangulation.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_DELAUNAY_TRIANGULATION_H +#define IGL_DELAUNAY_TRIANGULATION_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // Given a set of points in 2D, return a Delaunay triangulation of these + // points. + // + // Inputs: + // V #V by 2 list of vertex positions + // orient2D A functor such that orient2D(pa, pb, pc) returns + // 1 if pa,pb,pc forms a conterclockwise triangle. + // -1 if pa,pb,pc forms a clockwise triangle. + // 0 if pa,pb,pc are collinear. + // where the argument pa,pb,pc are of type Scalar[2]. + // incircle A functor such that incircle(pa, pb, pc, pd) returns + // 1 if pd is on the positive size of circumcirle of (pa,pb,pc) + // -1 if pd is on the positive size of circumcirle of (pa,pb,pc) + // 0 if pd is cocircular with pa, pb, pc. + // Outputs: + // F #F by 3 of faces in Delaunay triangulation. + template< + typename DerivedV, + typename Orient2D, + typename InCircle, + typename DerivedF + > + IGL_INLINE void delaunay_triangulation( + const Eigen::PlainObjectBase& V, + Orient2D orient2D, + InCircle incircle, + Eigen::PlainObjectBase& F); +} + + + + +#ifndef IGL_STATIC_LIBRARY +# include "delaunay_triangulation.cpp" +#endif +#endif diff --git a/src/igl/deprecated.h b/src/igl/deprecated.h new file mode 100644 index 000000000..52c72ba79 --- /dev/null +++ b/src/igl/deprecated.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DEPRECATED_H +#define IGL_DEPRECATED_H +// Macro for marking a function as deprecated. +// +// http://stackoverflow.com/a/295229/148668 +#ifdef __GNUC__ +#define IGL_DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define IGL_DEPRECATED(func) __declspec(deprecated) func +#else +#pragma message("WARNING: You need to implement IGL_DEPRECATED for this compiler") +#define IGL_DEPRECATED(func) func +#endif +// Usage: +// +// template IGL_INLINE void my_func(Arg1 a); +// +// becomes +// +// template IGL_INLINE IGL_DEPRECATED(void my_func(Arg1 a)); +#endif diff --git a/src/igl/dfs.cpp b/src/igl/dfs.cpp new file mode 100644 index 000000000..627fbc26d --- /dev/null +++ b/src/igl/dfs.cpp @@ -0,0 +1,60 @@ +#include "dfs.h" +#include "list_to_matrix.h" +#include + +template < + typename AType, + typename DerivedD, + typename DerivedP, + typename DerivedC> +IGL_INLINE void igl::dfs( + const std::vector > & A, + const size_t s, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & C) +{ + std::vector vD; + std::vector vP; + std::vector vC; + dfs(A,s,vD,vP,vC); + list_to_matrix(vD,D); + list_to_matrix(vP,P); + list_to_matrix(vC,C); +} + +template < + typename AType, + typename DType, + typename PType, + typename CType> +IGL_INLINE void igl::dfs( + const std::vector > & A, + const size_t s, + std::vector & D, + std::vector & P, + std::vector & C) +{ + // number of nodes + int N = s+1; + for(const auto & Ai : A) for(const auto & a : Ai) N = std::max(N,a+1); + std::vector seen(N,false); + P.resize(N,-1); + std::function dfs_helper; + dfs_helper = [&D,&P,&C,&dfs_helper,&seen,&A](const size_t s, const size_t p) + { + if(seen[s]) return; + seen[s] = true; + D.push_back(s); + P[s] = p; + for(const auto n : A[s]) dfs_helper(n,s); + C.push_back(s); + }; + dfs_helper(s,-1); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::dfs, Eigen::Matrix, Eigen::Matrix >(std::vector >, std::allocator > > > const&, const size_t, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/dfs.h b/src/igl/dfs.h new file mode 100644 index 000000000..5f3bf2ce6 --- /dev/null +++ b/src/igl/dfs.h @@ -0,0 +1,49 @@ +#ifndef IGL_DFS_H +#define IGL_DFS_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Traverse a **directed** graph represented by an adjacency list using + // depth first search + // + // Inputs: + // A #V list of adjacency lists + // s starting node (index into A) + // Outputs: + // D #V list of indices into rows of A in the order in which graph nodes + // are discovered. + // P #V list of indices into rows of A of predecessor in resulting + // spanning tree {-1 indicates root/not discovered), order corresponds to + // V **not** D. + // C #V list of indices into rows of A in order that nodes are "closed" + // (all descendants have been discovered) + template < + typename AType, + typename DerivedD, + typename DerivedP, + typename DerivedC> + IGL_INLINE void dfs( + const std::vector > & A, + const size_t s, + Eigen::PlainObjectBase & D, + Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & C); + template < + typename AType, + typename DType, + typename PType, + typename CType> + IGL_INLINE void dfs( + const std::vector > & A, + const size_t s, + std::vector & D, + std::vector & P, + std::vector & C); + +} +#ifndef IGL_STATIC_LIBRARY +# include "dfs.cpp" +#endif +#endif diff --git a/src/igl/diag.cpp b/src/igl/diag.cpp new file mode 100644 index 000000000..d29382695 --- /dev/null +++ b/src/igl/diag.cpp @@ -0,0 +1,108 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "diag.h" + +#include "verbose.h" + +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include + +template +IGL_INLINE void igl::diag( + const Eigen::SparseMatrix& X, + Eigen::SparseVector& V) +{ + assert(false && "Just call X.diagonal().sparseView() directly"); + V = X.diagonal().sparseView(); + //// Get size of input + //int m = X.rows(); + //int n = X.cols(); + //V = Eigen::SparseVector((m>n?n:m)); + //V.reserve(V.size()); + + //// Iterate over outside + //for(int k=0; k::InnerIterator it (X,k); it; ++it) + // { + // if(it.col() == it.row()) + // { + // V.coeffRef(it.col()) += it.value(); + // } + // } + //} +} + +template +IGL_INLINE void igl::diag( + const Eigen::SparseMatrix& X, + Eigen::MatrixBase & V) +{ + assert(false && "Just call X.diagonal() directly"); + V = X.diagonal(); + //// Get size of input + //int m = X.rows(); + //int n = X.cols(); + //V.derived().resize((m>n?n:m),1); + + //// Iterate over outside + //for(int k=0; k::InnerIterator it (X,k); it; ++it) + // { + // if(it.col() == it.row()) + // { + // V(it.col()) = it.value(); + // } + // } + //} +} + +template +IGL_INLINE void igl::diag( + const Eigen::SparseVector& V, + Eigen::SparseMatrix& X) +{ + // clear and resize output + Eigen::DynamicSparseMatrix dyn_X(V.size(),V.size()); + dyn_X.reserve(V.size()); + // loop over non-zeros + for(typename Eigen::SparseVector::InnerIterator it(V); it; ++it) + { + dyn_X.coeffRef(it.index(),it.index()) += it.value(); + } + X = Eigen::SparseMatrix(dyn_X); +} + +template +IGL_INLINE void igl::diag( + const Eigen::MatrixBase & V, + Eigen::SparseMatrix& X) +{ + assert(V.rows() == 1 || V.cols() == 1); + // clear and resize output + Eigen::DynamicSparseMatrix dyn_X(V.size(),V.size()); + dyn_X.reserve(V.size()); + // loop over non-zeros + for(int i = 0;i(dyn_X); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::diag >(Eigen::SparseMatrix const&, Eigen::MatrixBase >&); +template void igl::diag(Eigen::SparseMatrix const&, Eigen::SparseVector&); +template void igl::diag >(Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::diag(Eigen::SparseVector const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/diag.h b/src/igl/diag.h new file mode 100644 index 000000000..8001f01b6 --- /dev/null +++ b/src/igl/diag.h @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DIAG_H +#define IGL_DIAG_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include + +namespace igl +{ + // http://forum.kde.org/viewtopic.php?f=74&t=117476&p=292388#p292388 + // + // This is superceded by + // VectorXd V = X.diagonal() and + // SparseVector V = X.diagonal().sparseView() + // SparseMatrix X = V.asDiagonal().sparseView() + // + // + // Either extracts the main diagonal of a matrix as a vector OR converts a + // vector into a matrix with vector along the main diagonal. Like matlab's + // diag function + + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // X an m by n sparse matrix + // Outputs: + // V a min(m,n) sparse vector + template + IGL_INLINE void diag( + const Eigen::SparseMatrix& X, + Eigen::SparseVector& V); + template + IGL_INLINE void diag( + const Eigen::SparseMatrix& X, + Eigen::MatrixBase& V); + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // V a m sparse vector + // Outputs: + // X a m by m sparse matrix + template + IGL_INLINE void diag( + const Eigen::SparseVector& V, + Eigen::SparseMatrix& X); + template + IGL_INLINE void diag( + const Eigen::MatrixBase& V, + Eigen::SparseMatrix& X); +} + +#ifndef IGL_STATIC_LIBRARY +# include "diag.cpp" +#endif + +#endif diff --git a/src/igl/dihedral_angles.cpp b/src/igl/dihedral_angles.cpp new file mode 100644 index 000000000..a04e6f840 --- /dev/null +++ b/src/igl/dihedral_angles.cpp @@ -0,0 +1,94 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "dihedral_angles.h" +#include + +template < + typename DerivedV, + typename DerivedT, + typename Derivedtheta, + typename Derivedcos_theta> +IGL_INLINE void igl::dihedral_angles( + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& theta, + Eigen::PlainObjectBase& cos_theta) +{ + using namespace Eigen; + assert(T.cols() == 4); + Matrix l; + edge_lengths(V,T,l); + Matrix s; + face_areas(l,s); + return dihedral_angles_intrinsic(l,s,theta,cos_theta); +} + +template < + typename DerivedL, + typename DerivedA, + typename Derivedtheta, + typename Derivedcos_theta> +IGL_INLINE void igl::dihedral_angles_intrinsic( + Eigen::PlainObjectBase& L, + Eigen::PlainObjectBase& A, + Eigen::PlainObjectBase& theta, + Eigen::PlainObjectBase& cos_theta) +{ + using namespace Eigen; + const int m = L.rows(); + assert(m == A.rows()); + // Law of cosines + // http://math.stackexchange.com/a/49340/35376 + Matrix H_sqr(m,6); + H_sqr.col(0) = (1./16.) * (4. * L.col(3).array().square() * L.col(0).array().square() - + ((L.col(1).array().square() + L.col(4).array().square()) - + (L.col(2).array().square() + L.col(5).array().square())).square()); + H_sqr.col(1) = (1./16.) * (4. * L.col(4).array().square() * L.col(1).array().square() - + ((L.col(2).array().square() + L.col(5).array().square()) - + (L.col(3).array().square() + L.col(0).array().square())).square()); + H_sqr.col(2) = (1./16.) * (4. * L.col(5).array().square() * L.col(2).array().square() - + ((L.col(3).array().square() + L.col(0).array().square()) - + (L.col(4).array().square() + L.col(1).array().square())).square()); + H_sqr.col(3) = (1./16.) * (4. * L.col(0).array().square() * L.col(3).array().square() - + ((L.col(4).array().square() + L.col(1).array().square()) - + (L.col(5).array().square() + L.col(2).array().square())).square()); + H_sqr.col(4) = (1./16.) * (4. * L.col(1).array().square() * L.col(4).array().square() - + ((L.col(5).array().square() + L.col(2).array().square()) - + (L.col(0).array().square() + L.col(3).array().square())).square()); + H_sqr.col(5) = (1./16.) * (4. * L.col(2).array().square() * L.col(5).array().square() - + ((L.col(0).array().square() + L.col(3).array().square()) - + (L.col(1).array().square() + L.col(4).array().square())).square()); + cos_theta.resize(m,6); + cos_theta.col(0) = (H_sqr.col(0).array() - + A.col(1).array().square() - A.col(2).array().square()).array() / + (-2.*A.col(1).array() * A.col(2).array()); + cos_theta.col(1) = (H_sqr.col(1).array() - + A.col(2).array().square() - A.col(0).array().square()).array() / + (-2.*A.col(2).array() * A.col(0).array()); + cos_theta.col(2) = (H_sqr.col(2).array() - + A.col(0).array().square() - A.col(1).array().square()).array() / + (-2.*A.col(0).array() * A.col(1).array()); + cos_theta.col(3) = (H_sqr.col(3).array() - + A.col(3).array().square() - A.col(0).array().square()).array() / + (-2.*A.col(3).array() * A.col(0).array()); + cos_theta.col(4) = (H_sqr.col(4).array() - + A.col(3).array().square() - A.col(1).array().square()).array() / + (-2.*A.col(3).array() * A.col(1).array()); + cos_theta.col(5) = (H_sqr.col(5).array() - + A.col(3).array().square() - A.col(2).array().square()).array() / + (-2.*A.col(3).array() * A.col(2).array()); + + theta = cos_theta.array().acos(); + + cos_theta.resize(m,6); + +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::dihedral_angles_intrinsic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/dihedral_angles.h b/src/igl/dihedral_angles.h new file mode 100644 index 000000000..92ac0f862 --- /dev/null +++ b/src/igl/dihedral_angles.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DIHEDRAL_ANGLES_H +#define IGL_DIHEDRAL_ANGLES_H +#include "igl_inline.h" +#include +namespace igl +{ + // DIHEDRAL_ANGLES Compute dihedral angles for all tets of a given tet mesh + // (V,T) + // + // theta = dihedral_angles(V,T) + // theta = dihedral_angles(V,T,'ParameterName',parameter_value,...) + // + // Inputs: + // V #V by dim list of vertex positions + // T #V by 4 list of tet indices + // Outputs: + // theta #T by 6 list of dihedral angles (in radians) + // cos_theta #T by 6 list of cosine of dihedral angles (in radians) + // + template < + typename DerivedV, + typename DerivedT, + typename Derivedtheta, + typename Derivedcos_theta> + IGL_INLINE void dihedral_angles( + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& theta, + Eigen::PlainObjectBase& cos_theta); + template < + typename DerivedL, + typename DerivedA, + typename Derivedtheta, + typename Derivedcos_theta> + IGL_INLINE void dihedral_angles_intrinsic( + Eigen::PlainObjectBase& L, + Eigen::PlainObjectBase& A, + Eigen::PlainObjectBase& theta, + Eigen::PlainObjectBase& cos_theta); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "dihedral_angles.cpp" +#endif + +#endif + diff --git a/src/igl/dijkstra.cpp b/src/igl/dijkstra.cpp new file mode 100644 index 000000000..ad92f1d7f --- /dev/null +++ b/src/igl/dijkstra.cpp @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include + +template +IGL_INLINE int igl::dijkstra_compute_paths(const IndexType &source, + const std::set &targets, + const std::vector >& VV, + Eigen::PlainObjectBase &min_distance, + Eigen::PlainObjectBase &previous) +{ + int numV = VV.size(); + min_distance.setConstant(numV, 1, std::numeric_limits::infinity()); + min_distance[source] = 0; + previous.setConstant(numV, 1, -1); + std::set > vertex_queue; + vertex_queue.insert(std::make_pair(min_distance[source], source)); + + while (!vertex_queue.empty()) + { + typename DerivedD::Scalar dist = vertex_queue.begin()->first; + IndexType u = vertex_queue.begin()->second; + vertex_queue.erase(vertex_queue.begin()); + + if (targets.find(u)!= targets.end()) + return u; + + // Visit each edge exiting u + const std::vector &neighbors = VV[u]; + for (std::vector::const_iterator neighbor_iter = neighbors.begin(); + neighbor_iter != neighbors.end(); + neighbor_iter++) + { + IndexType v = *neighbor_iter; + typename DerivedD::Scalar distance_through_u = dist + 1.; + if (distance_through_u < min_distance[v]) { + vertex_queue.erase(std::make_pair(min_distance[v], v)); + + min_distance[v] = distance_through_u; + previous[v] = u; + vertex_queue.insert(std::make_pair(min_distance[v], v)); + + } + + } + } + //we should never get here + return -1; +} + +template +IGL_INLINE void igl::dijkstra_get_shortest_path_to(const IndexType &vertex, + const Eigen::PlainObjectBase &previous, + std::vector &path) +{ + IndexType source = vertex; + path.clear(); + for ( ; source != -1; source = previous[source]) + path.push_back(source); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template int igl::dijkstra_compute_paths, Eigen::Matrix >(int const&, std::set, std::allocator > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::dijkstra_get_shortest_path_to >(int const&, Eigen::PlainObjectBase > const&, std::vector >&); +#endif diff --git a/src/igl/dijkstra.h b/src/igl/dijkstra.h new file mode 100644 index 000000000..33af160bb --- /dev/null +++ b/src/igl/dijkstra.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_DIJKSTRA +#define IGL_DIJKSTRA +#include "igl_inline.h" + +#include +#include +#include + +namespace igl { + + // Dijstra's algorithm for shortest paths, with multiple targets. + // Adapted from http://rosettacode.org/wiki/Dijkstra%27s_algorithm . + // + // Inputs: + // source index of source vertex + // targets target vector set + // VV #V list of lists of incident vertices (adjacency list), e.g. + // as returned by igl::adjacency_list + // + // Output: + // min_distance #V by 1 list of the minimum distances from source to all vertices + // previous #V by 1 list of the previous visited vertices (for each vertex) - used for backtracking + // + template + IGL_INLINE int dijkstra_compute_paths(const IndexType &source, + const std::set &targets, + const std::vector >& VV, + Eigen::PlainObjectBase &min_distance, + Eigen::PlainObjectBase &previous); + + // Backtracking after Dijstra's algorithm, to find shortest path. + // + // Inputs: + // vertex vertex to which we want the shortest path (from same source as above) + // previous #V by 1 list of the previous visited vertices (for each vertex) - result of Dijkstra's algorithm + // + // Output: + // path #P by 1 list of vertex indices in the shortest path from source to vertex + // + template + IGL_INLINE void dijkstra_get_shortest_path_to(const IndexType &vertex, + const Eigen::PlainObjectBase &previous, + std::vector &path); +}; + + +#ifndef IGL_STATIC_LIBRARY +#include "dijkstra.cpp" +#endif + + +#endif /* defined(IGL_DIJKSTRA) */ diff --git a/src/igl/directed_edge_orientations.cpp b/src/igl/directed_edge_orientations.cpp new file mode 100644 index 000000000..a3308bc98 --- /dev/null +++ b/src/igl/directed_edge_orientations.cpp @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "directed_edge_orientations.h" + +template +IGL_INLINE void igl::directed_edge_orientations( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & E, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & Q) +{ + using namespace Eigen; + Q.resize(E.rows()); + for(int e = 0;e, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector, Eigen::aligned_allocator > >&); +#endif diff --git a/src/igl/directed_edge_orientations.h b/src/igl/directed_edge_orientations.h new file mode 100644 index 000000000..b6f047147 --- /dev/null +++ b/src/igl/directed_edge_orientations.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DIRECTED_EDGE_ORIENTATIONS_H +#define IGL_DIRECTED_EDGE_ORIENTATIONS_H +#include "igl_inline.h" + +#include +#include +#include +#include + +namespace igl +{ + // Determine rotations that take each edge from the x-axis to its given rest + // orientation. + // + // Inputs: + // C #C by 3 list of edge vertex positions + // E #E by 2 list of directed edges + // Outputs: + // Q #E list of quaternions + // + template + IGL_INLINE void directed_edge_orientations( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & E, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & Q); +} + +#ifndef IGL_STATIC_LIBRARY +# include "directed_edge_orientations.cpp" +#endif +#endif diff --git a/src/igl/directed_edge_parents.cpp b/src/igl/directed_edge_parents.cpp new file mode 100644 index 000000000..52e6a41a4 --- /dev/null +++ b/src/igl/directed_edge_parents.cpp @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "directed_edge_parents.h" +#include "slice_into.h" +#include "slice.h" +#include "colon.h" +#include "setdiff.h" +#include + +template +IGL_INLINE void igl::directed_edge_parents( + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & P) +{ + using namespace Eigen; + using namespace std; + VectorXi I = VectorXi::Constant(E.maxCoeff()+1,1,-1); + //I(E.col(1)) = 0:E.rows()-1 + slice_into(colon(0,E.rows()-1),E.col(1).eval(),I); + VectorXi roots,_; + setdiff(E.col(0).eval(),E.col(1).eval(),roots,_); + std::for_each(roots.data(),roots.data()+roots.size(),[&](int r){I(r)=-1;}); + slice(I,E.col(0).eval(),P); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::directed_edge_parents, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/directed_edge_parents.h b/src/igl/directed_edge_parents.h new file mode 100644 index 000000000..bf42ab8f8 --- /dev/null +++ b/src/igl/directed_edge_parents.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DIRECTED_EDGE_PARENTS_H +#define IGL_DIRECTED_EDGE_PARENTS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Recover "parents" (preceding edges) in a tree given just directed edges. + // + // Inputs: + // E #E by 2 list of directed edges + // Outputs: + // P #E list of parent indices into E (-1) means root + // + template + IGL_INLINE void directed_edge_parents( + const Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "directed_edge_parents.cpp" +#endif +#endif diff --git a/src/igl/dirname.cpp b/src/igl/dirname.cpp new file mode 100644 index 000000000..5dc49733a --- /dev/null +++ b/src/igl/dirname.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "dirname.h" + +#include +#include "verbose.h" + +IGL_INLINE std::string igl::dirname(const std::string & path) +{ + if(path == "") + { + return std::string(""); + } +#if defined (WIN32) + char del('\\'); +#else + char del('/'); +#endif + // http://stackoverflow.com/questions/5077693/dirnamephp-similar-function-in-c + std::string::const_reverse_iterator last_slash = + std::find( + path.rbegin(), + path.rend(),del); + if( last_slash == path.rend() ) + { + // No slashes found + return std::string("."); + }else if(1 == (last_slash.base() - path.begin())) + { + // Slash is first char + return std::string(&del); + }else if(path.end() == last_slash.base() ) + { + // Slash is last char + std::string redo = std::string(path.begin(),path.end()-1); + return igl::dirname(redo); + } + return std::string(path.begin(),last_slash.base()-1); +} + + diff --git a/src/igl/dirname.h b/src/igl/dirname.h new file mode 100644 index 000000000..bf5a34a20 --- /dev/null +++ b/src/igl/dirname.h @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DIRNAME_H +#define IGL_DIRNAME_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Function like PHP's dirname: /etc/passwd --> /etc, + // Input: + // path string containing input path + // Returns string containing dirname (see php's dirname) + // + // See also: basename, pathinfo + IGL_INLINE std::string dirname(const std::string & path); +} + +#ifndef IGL_STATIC_LIBRARY +# include "dirname.cpp" +#endif + +#endif diff --git a/src/igl/dot.cpp b/src/igl/dot.cpp new file mode 100644 index 000000000..38f191546 --- /dev/null +++ b/src/igl/dot.cpp @@ -0,0 +1,16 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "dot.h" + +// http://www.antisphere.com/Wiki/tools:anttweakbar +IGL_INLINE double igl::dot( + const double *a, + const double *b) +{ + return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; +} diff --git a/src/igl/dot.h b/src/igl/dot.h new file mode 100644 index 000000000..9494eac7b --- /dev/null +++ b/src/igl/dot.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DOT_H +#define IGL_DOT_H +#include "igl_inline.h" +namespace igl +{ + // Computes out = dot(a,b) + // Inputs: + // a left 3d vector + // b right 3d vector + // Returns scalar dot product + IGL_INLINE double dot( + const double *a, + const double *b); +} + +#ifndef IGL_STATIC_LIBRARY +# include "dot.cpp" +#endif + +#endif diff --git a/src/igl/dot_row.cpp b/src/igl/dot_row.cpp new file mode 100644 index 000000000..6f6d57e06 --- /dev/null +++ b/src/igl/dot_row.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "igl/dot_row.h" + +template +IGL_INLINE DerivedV igl::dot_row( + const Eigen::PlainObjectBase& A, + const Eigen::PlainObjectBase& B + ) +{ + assert(A.rows() == B.rows()); + assert(A.cols() == B.cols()); + + return (A.array() * B.array()).rowwise().sum(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Matrix igl::dot_row >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/dot_row.h b/src/igl/dot_row.h new file mode 100644 index 000000000..83d91aada --- /dev/null +++ b/src/igl/dot_row.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DOT_ROW_H +#define IGL_DOT_ROW_H + +#include "igl/igl_inline.h" +#include + +namespace igl +{ + // Compute the dot product between each row of A and B + // Templates: + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // Inputs: + // A eigen matrix r by c + // B eigen matrix r by c + // Returns: + // d a column vector with r entries that contains the dot product of each corresponding row of A and B + // + template + IGL_INLINE DerivedV dot_row( + const Eigen::PlainObjectBase& A, + const Eigen::PlainObjectBase& B); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "dot_row.cpp" +#endif + +#endif diff --git a/src/igl/doublearea.cpp b/src/igl/doublearea.cpp new file mode 100644 index 000000000..3cfc41b86 --- /dev/null +++ b/src/igl/doublearea.cpp @@ -0,0 +1,263 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "doublearea.h" +#include "edge_lengths.h" +#include "parallel_for.h" +#include "sort.h" +#include +#include +#include + +template +IGL_INLINE void igl::doublearea( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & dblA) +{ + // quads are handled by a specialized function + if (F.cols() == 4) return doublearea_quad(V,F,dblA); + + const int dim = V.cols(); + // Only support triangles + assert(F.cols() == 3); + const size_t m = F.rows(); + // Compute edge lengths + Eigen::Matrix l; + + // Projected area helper + const auto & proj_doublearea = + [&V,&F](const int x, const int y, const int f) + ->typename DerivedV::Scalar + { + auto rx = V(F(f,0),x)-V(F(f,2),x); + auto sx = V(F(f,1),x)-V(F(f,2),x); + auto ry = V(F(f,0),y)-V(F(f,2),y); + auto sy = V(F(f,1),y)-V(F(f,2),y); + return rx*sy - ry*sx; + }; + + switch(dim) + { + case 3: + { + dblA = DeriveddblA::Zero(m,1); + for(size_t f = 0;f +IGL_INLINE void igl::doublearea( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & D) +{ + assert((B.cols() == A.cols()) && "dimensions of A and B should match"); + assert((C.cols() == A.cols()) && "dimensions of A and C should match"); + assert(A.rows() == B.rows() && "corners should have same length"); + assert(A.rows() == C.rows() && "corners should have same length"); + switch(A.cols()) + { + case 2: + { + // For 2d compute signed area + const auto & R = A-C; + const auto & S = B-C; + D = (R.col(0).array()*S.col(1).array() - + R.col(1).array()*S.col(0).array()).template cast< + typename DerivedD::Scalar>(); + break; + } + default: + { + Eigen::Matrix + uL(A.rows(),3); + uL.col(0) = ((B-C).rowwise().norm()).template cast(); + uL.col(1) = ((C-A).rowwise().norm()).template cast(); + uL.col(2) = ((A-B).rowwise().norm()).template cast(); + doublearea(uL,D); + } + } +} + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC> +IGL_INLINE typename DerivedA::Scalar igl::doublearea_single( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C) +{ + assert(A.size() == 2 && "Vertices should be 2D"); + assert(B.size() == 2 && "Vertices should be 2D"); + assert(C.size() == 2 && "Vertices should be 2D"); + auto r = A-C; + auto s = B-C; + return r(0)*s(1) - r(1)*s(0); +} + +template +IGL_INLINE void igl::doublearea( + const Eigen::MatrixBase & ul, + Eigen::PlainObjectBase & dblA) +{ + // Default is to leave NaNs and fire asserts in debug mode + return doublearea( + ul,std::numeric_limits::quiet_NaN(),dblA); +} + +template +IGL_INLINE void igl::doublearea( + const Eigen::MatrixBase & ul, + const typename Derivedl::Scalar nan_replacement, + Eigen::PlainObjectBase & dblA) +{ + using namespace Eigen; + using namespace std; + typedef typename Derivedl::Index Index; + // Only support triangles + assert(ul.cols() == 3); + // Number of triangles + const Index m = ul.rows(); + Eigen::Matrix l; + MatrixXi _; + // + // "Miscalculating Area and Angles of a Needle-like Triangle" + // https://people.eecs.berkeley.edu/~wkahan/Triangle.pdf + igl::sort(ul,2,false,l,_); + // semiperimeters + //Matrix s = l.rowwise().sum()*0.5; + //assert((Index)s.rows() == m); + // resize output + dblA.resize(l.rows(),1); + parallel_for( + m, + [&l,&dblA,&nan_replacement](const int i) + { + // Kahan's Heron's formula + typedef typename Derivedl::Scalar Scalar; + const Scalar arg = + (l(i,0)+(l(i,1)+l(i,2)))* + (l(i,2)-(l(i,0)-l(i,1)))* + (l(i,2)+(l(i,0)-l(i,1)))* + (l(i,0)+(l(i,1)-l(i,2))); + dblA(i) = 2.0*0.25*sqrt(arg); + // Alec: If the input edge lengths were computed from floating point + // vertex positions then there's no guarantee that they fulfill the + // triangle inequality (in their floating point approximations). For + // nearly degenerate triangles the round-off error during side-length + // computation may be larger than (or rather smaller) than the height of + // the triangle. In "Lecture Notes on Geometric Robustness" Shewchuck 09, + // Section 3.1 http://www.cs.berkeley.edu/~jrs/meshpapers/robnotes.pdf, + // he recommends computing the triangle areas for 2D and 3D using 2D + // signed areas computed with determinants. + assert( + (nan_replacement == nan_replacement || + (l(i,2) - (l(i,0)-l(i,1)))>=0) + && "Side lengths do not obey the triangle inequality."); + if(dblA(i) != dblA(i)) + { + dblA(i) = nan_replacement; + } + assert(dblA(i) == dblA(i) && "DOUBLEAREA() PRODUCED NaN"); + }, + 1000l); +} + +template +IGL_INLINE void igl::doublearea_quad( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & dblA) +{ + assert(V.cols() == 3); // Only supports points in 3D + assert(F.cols() == 4); // Only support quads + const size_t m = F.rows(); + + // Split the quads into triangles + Eigen::MatrixXi Ft(F.rows()*2,3); + + for(size_t i=0; i, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::doublearea, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix::Scalar igl::doublearea_single, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template Eigen::Matrix::Scalar igl::doublearea_single, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/doublearea.h b/src/igl/doublearea.h new file mode 100644 index 000000000..95c151508 --- /dev/null +++ b/src/igl/doublearea.h @@ -0,0 +1,104 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DOUBLEAREA_H +#define IGL_DOUBLEAREA_H +#include "igl_inline.h" +#include +namespace igl +{ + // DOUBLEAREA computes twice the area for each input triangle[quad] + // + // Templates: + // DerivedV derived type of eigen matrix for V (e.g. derived from + // MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. derived from + // MatrixXi) + // DeriveddblA derived type of eigen matrix for dblA (e.g. derived from + // MatrixXd) + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by simplex_size list of mesh faces (must be triangles or quads) + // Outputs: + // dblA #F list of triangle[quad] double areas (SIGNED only for 2D input) + // + // Known bug: For dim==3 complexity is O(#V + #F)!! Not just O(#F). This is a big deal + // if you have 1million unreferenced vertices and 1 face + template + IGL_INLINE void doublearea( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & dblA); + // Stream of triangles, computes signed area... + template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedD> + IGL_INLINE void doublearea( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + Eigen::PlainObjectBase & D); + // Single triangle in 2D! + // + // This should handle streams of corners not just single corners + template < + typename DerivedA, + typename DerivedB, + typename DerivedC> + IGL_INLINE typename DerivedA::Scalar doublearea_single( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C); + // Same as above but use instrinsic edge lengths rather than (V,F) mesh. This + // + // Inputs: + // l #F by dim list of edge lengths using + // for triangles, columns correspond to edges 23,31,12 + // nan_replacement what value should be used for triangles whose given + // edge lengths do not obey the triangle inequality. These may be very + // wrong (e.g., [100 1 1]) or may be nearly degenerate triangles whose + // floating point side length computation leads to breach of the triangle + // inequality. One may wish to set this parameter to 0 if side lengths l + // are _known_ to come from a valid embedding (e.g., some mesh (V,F)). In + // that case, the only circumstance the triangle inequality is broken is + // when the triangle is nearly degenerate and floating point error + // dominates: hence replacing with zero is reasonable. + // Outputs: + // dblA #F list of triangle double areas + template + IGL_INLINE void doublearea( + const Eigen::MatrixBase & l, + const typename Derivedl::Scalar nan_replacement, + Eigen::PlainObjectBase & dblA); + // default behavior is to assert on NaNs and leave them in place + template + IGL_INLINE void doublearea( + const Eigen::MatrixBase & l, + Eigen::PlainObjectBase & dblA); + // DOUBLEAREA_QUAD computes twice the area for each input quadrilateral + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by simplex_size list of mesh faces (must be quadrilaterals) + // Outputs: + // dblA #F list of quadrilateral double areas + // + template + IGL_INLINE void doublearea_quad( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & dblA); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "doublearea.cpp" +#endif + +#endif diff --git a/src/igl/dqs.cpp b/src/igl/dqs.cpp new file mode 100644 index 000000000..aab3c6532 --- /dev/null +++ b/src/igl/dqs.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "dqs.h" +#include +template < + typename DerivedV, + typename DerivedW, + typename Q, + typename QAlloc, + typename T, + typename DerivedU> +IGL_INLINE void igl::dqs( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & W, + const std::vector & vQ, + const std::vector & vT, + Eigen::PlainObjectBase & U) +{ + using namespace std; + assert(V.rows() <= W.rows()); + assert(W.cols() == (int)vQ.size()); + assert(W.cols() == (int)vT.size()); + // resize output + U.resizeLike(V); + + // Convert quats + trans into dual parts + vector vD(vQ.size()); + for(int c = 0;c10000) + for(int i = 0;i, Eigen::Matrix, Eigen::Quaternion, Eigen::aligned_allocator >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector, Eigen::aligned_allocator > > const&, std::vector, std::allocator > > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/dqs.h b/src/igl/dqs.h new file mode 100644 index 000000000..9b53ae1c7 --- /dev/null +++ b/src/igl/dqs.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_DQS_H +#define IGL_DQS_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Dual quaternion skinning + // + // Inputs: + // V #V by 3 list of rest positions + // W #W by #C list of weights + // vQ #C list of rotation quaternions + // vT #C list of translation vectors + // Outputs: + // U #V by 3 list of new positions + template < + typename DerivedV, + typename DerivedW, + typename Q, + typename QAlloc, + typename T, + typename DerivedU> + IGL_INLINE void dqs( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & W, + const std::vector & vQ, + const std::vector & vT, + Eigen::PlainObjectBase & U); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "dqs.cpp" +#endif +#endif diff --git a/src/igl/ears.cpp b/src/igl/ears.cpp new file mode 100644 index 000000000..81576c321 --- /dev/null +++ b/src/igl/ears.cpp @@ -0,0 +1,34 @@ +#include "ears.h" +#include "on_boundary.h" +#include "find.h" +#include "slice.h" +#include "mat_min.h" +#include + +template < + typename DerivedF, + typename Derivedear, + typename Derivedear_opp> +IGL_INLINE void igl::ears( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & ear, + Eigen::PlainObjectBase & ear_opp) +{ + assert(F.cols() == 3 && "F should contain triangles"); + Eigen::Array B; + { + Eigen::Array I; + on_boundary(F,I,B); + } + find(B.rowwise().count() == 2,ear); + Eigen::Array Bear; + slice(B,ear,1,Bear); + Eigen::Array M; + mat_min(Bear,2,M,ear_opp); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::ears, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ears.h b/src/igl/ears.h new file mode 100644 index 000000000..b7c51ec66 --- /dev/null +++ b/src/igl/ears.h @@ -0,0 +1,30 @@ +#ifndef IGL_EARS_H +#define IGL_EARS_H +#include "igl_inline.h" +#include +namespace igl +{ + // FIND_EARS Find all ears (faces with two boundary edges) in a given mesh + // + // [ears,ear_opp] = find_ears(F) + // + // Inputs: + // F #F by 3 list of triangle mesh indices + // Outputs: + // ears #ears list of indices into F of ears + // ear_opp #ears list of indices indicating which edge is non-boundary + // (connecting to flops) + // + template < + typename DerivedF, + typename Derivedear, + typename Derivedear_opp> + IGL_INLINE void ears( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & ear, + Eigen::PlainObjectBase & ear_opp); +} +#ifndef IGL_STATIC_LIBRARY +# include "ears.cpp" +#endif +#endif diff --git a/src/igl/edge_collapse_is_valid.cpp b/src/igl/edge_collapse_is_valid.cpp new file mode 100644 index 000000000..0c613b7a3 --- /dev/null +++ b/src/igl/edge_collapse_is_valid.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "edge_collapse_is_valid.h" +#include "collapse_edge.h" +#include "circulation.h" +#include "intersect.h" +#include "unique.h" +#include "list_to_matrix.h" +#include + +IGL_INLINE bool igl::edge_collapse_is_valid( + const int e, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI) +{ + using namespace Eigen; + using namespace std; + // For consistency with collapse_edge.cpp, let's determine edge flipness + // (though not needed to check validity) + const int eflip = E(e,0)>E(e,1); + // source and destination + const int s = eflip?E(e,1):E(e,0); + const int d = eflip?E(e,0):E(e,1); + + if(s == IGL_COLLAPSE_EDGE_NULL && d==IGL_COLLAPSE_EDGE_NULL) + { + return false; + } + // check if edge collapse is valid: intersection of vertex neighbors of s and + // d should be exactly 2+(s,d) = 4 + // http://stackoverflow.com/a/27049418/148668 + { + // all vertex neighbors around edge, including the two vertices of the edge + const auto neighbors = []( + const int e, + const bool ccw, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI) + { + vector N,uN; + vector V2Fe = circulation(e, ccw,F,E,EMAP,EF,EI); + for(auto f : V2Fe) + { + N.push_back(F(f,0)); + N.push_back(F(f,1)); + N.push_back(F(f,2)); + } + vector _1,_2; + igl::unique(N,uN,_1,_2); + VectorXi uNm; + list_to_matrix(uN,uNm); + return uNm; + }; + VectorXi Ns = neighbors(e, eflip,F,E,EMAP,EF,EI); + VectorXi Nd = neighbors(e,!eflip,F,E,EMAP,EF,EI); + VectorXi Nint = igl::intersect(Ns,Nd); + if(Nint.size() != 4) + { + return false; + } + if(Ns.size() == 4 && Nd.size() == 4) + { + VectorXi NsNd(8); + NsNd< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EDGE_COLLAPSE_IS_VALID_H +#define IGL_EDGE_COLLAPSE_IS_VALID_H +#include "igl_inline.h" +#include +namespace igl +{ + // Assumes (V,F) is a closed manifold mesh (except for previouslly collapsed + // faces which should be set to: + // [IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL]. + // Tests whether collapsing exactly two faces and exactly 3 edges from E (e + // and one side of each face gets collapsed to the other) will result in a + // mesh with the same topology. + // + // Inputs: + // e index into E of edge to try to collapse. E(e,:) = [s d] or [d s] so + // that sj) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // Returns true if edge collapse is valid + IGL_INLINE bool edge_collapse_is_valid( + const int e, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI); +} +#ifndef IGL_STATIC_LIBRARY +# include "edge_collapse_is_valid.cpp" +#endif +#endif diff --git a/src/igl/edge_flaps.cpp b/src/igl/edge_flaps.cpp new file mode 100644 index 000000000..88c515864 --- /dev/null +++ b/src/igl/edge_flaps.cpp @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "edge_flaps.h" +#include "unique_edge_map.h" +#include +#include + +IGL_INLINE void igl::edge_flaps( + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI) +{ + // Initialize to boundary value + EF.setConstant(E.rows(),2,-1); + EI.setConstant(E.rows(),2,-1); + // loop over all faces + for(int f = 0;f > uE2E; + igl::unique_edge_map(F,allE,E,EMAP,uE2E); + // Const-ify to call overload + const auto & cE = E; + const auto & cEMAP = EMAP; + return edge_flaps(F,cE,cEMAP,EF,EI); +} diff --git a/src/igl/edge_flaps.h b/src/igl/edge_flaps.h new file mode 100644 index 000000000..03ad6eb3d --- /dev/null +++ b/src/igl/edge_flaps.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EDGE_FLAPS_H +#define IGL_EDGE_FLAPS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine "edge flaps": two faces on either side of a unique edge (assumes + // edge-manifold mesh) + // + // Inputs: + // F #F by 3 list of face indices + // E #E by 2 list of edge indices into V. + // EMAP #F*3 list of indices into E, mapping each directed edge to unique + // unique edge in E + // Outputs: + // EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // + // TODO: This seems to be a duplicate of edge_topology.h + // igl::edge_topology(V,F,etEV,etFE,etEF); + // igl::edge_flaps(F,efE,efEMAP,efEF,efEI); + // [~,I] = sort(efE,2) + // all( efE(sub2ind(size(efE),repmat(1:size(efE,1),2,1)',I)) == etEV ) + // all( efEF(sub2ind(size(efE),repmat(1:size(efE,1),2,1)',I)) == etEF ) + // all(efEMAP(sub2ind(size(F),repmat(1:size(F,1),3,1)',repmat([1 2 3],size(F,1),1))) == etFE(:,[2 3 1])) + IGL_INLINE void edge_flaps( + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI); + // Only faces as input + IGL_INLINE void edge_flaps( + const Eigen::MatrixXi & F, + Eigen::MatrixXi & E, + Eigen::VectorXi & EMAP, + Eigen::MatrixXi & EF, + Eigen::MatrixXi & EI); +} +#ifndef IGL_STATIC_LIBRARY +# include "edge_flaps.cpp" +#endif + +#endif diff --git a/src/igl/edge_lengths.cpp b/src/igl/edge_lengths.cpp new file mode 100644 index 000000000..02dbae768 --- /dev/null +++ b/src/igl/edge_lengths.cpp @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "edge_lengths.h" +#include "squared_edge_lengths.h" + +template +IGL_INLINE void igl::edge_lengths( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& L) + { + igl::squared_edge_lengths(V,F,L); + L=L.array().sqrt().eval(); + } + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/edge_lengths.h b/src/igl/edge_lengths.h new file mode 100644 index 000000000..30dc3474b --- /dev/null +++ b/src/igl/edge_lengths.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EDGE_LENGTHS_H +#define IGL_EDGE_LENGTHS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Constructs a list of lengths of edges opposite each index in a face + // (triangle/tet) list + // + // Templates: + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // DerivedF derived from face indices matrix type: i.e. MatrixXi + // DerivedL derived from edge lengths matrix type: i.e. MatrixXd + // Inputs: + // V eigen matrix #V by 3 + // F #F by 2 list of mesh edges + // or + // F #F by 3 list of mesh faces (must be triangles) + // or + // T #T by 4 list of mesh elements (must be tets) + // Outputs: + // L #F by {1|3|6} list of edge lengths + // for edges, column of lengths + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // for tets, columns correspond to edges + // [3 0],[3 1],[3 2],[1 2],[2 0],[0 1] + // + template + IGL_INLINE void edge_lengths( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& L); +} + +#ifndef IGL_STATIC_LIBRARY +# include "edge_lengths.cpp" +#endif + +#endif + diff --git a/src/igl/edge_topology.cpp b/src/igl/edge_topology.cpp new file mode 100644 index 000000000..78147d056 --- /dev/null +++ b/src/igl/edge_topology.cpp @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "edge_topology.h" +#include "is_edge_manifold.h" +#include + +template +IGL_INLINE void igl::edge_topology( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::MatrixXi& EV, + Eigen::MatrixXi& FE, + Eigen::MatrixXi& EF) +{ + // Only needs to be edge-manifold + if (V.rows() ==0 || F.rows()==0) + { + EV = Eigen::MatrixXi::Constant(0,2,-1); + FE = Eigen::MatrixXi::Constant(0,3,-1); + EF = Eigen::MatrixXi::Constant(0,2,-1); + return; + } + assert(igl::is_edge_manifold(F)); + std::vector > ETT; + for(int f=0;f v2) std::swap(v1,v2); + std::vector r(4); + r[0] = v1; r[1] = v2; + r[2] = f; r[3] = i; + ETT.push_back(r); + } + std::sort(ETT.begin(),ETT.end()); + + // count the number of edges (assume manifoldness) + int En = 1; // the last is always counted + for(int i=0;i& r1 = ETT[i]; + EV(En,0) = r1[0]; + EV(En,1) = r1[1]; + EF(En,0) = r1[2]; + FE(r1[2],r1[3]) = En; + } + else + { + std::vector& r1 = ETT[i]; + std::vector& r2 = ETT[i+1]; + EV(En,0) = r1[0]; + EV(En,1) = r1[1]; + EF(En,0) = r1[2]; + EF(En,1) = r2[2]; + FE(r1[2],r1[3]) = En; + FE(r2[2],r2[3]) = En; + ++i; // skip the next one + } + ++En; + } + + // Sort the relation EF, accordingly to EV + // the first one is the face on the left of the edge + for(unsigned i=0; i, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +template void igl::edge_topology, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +#endif diff --git a/src/igl/edge_topology.h b/src/igl/edge_topology.h new file mode 100644 index 000000000..b7a41c597 --- /dev/null +++ b/src/igl/edge_topology.h @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EDGE_TOPOLOGY_H +#define IGL_EDGE_TOPOLOGY_H + +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Initialize Edges and their topological relations (assumes an edge-manifold + // mesh) + // + // Inputs: + // V #V by dim list of mesh vertex positions (unused) + // F #F by 3 list of triangle indices into V + // Outputs: + // EV #Ex2 matrix storing the edge description as pair of indices to + // vertices + // FE #Fx3 matrix storing the Triangle-Edge relation + // EF #Ex2 matrix storing the Edge-Triangle relation + // + // TODO: This seems to be a inferior duplicate of edge_flaps.h: + // - unused input parameter V + // - roughly 2x slower than edge_flaps + // - outputs less information: edge_flaps reveals corner opposite edge + // - FE uses non-standard and ambiguous order: FE(f,c) is merely an edge + // incident on corner c of face f. In contrast, edge_flaps's EMAP(f,c) + // reveals the edge _opposite_ corner c of face f +template + IGL_INLINE void edge_topology( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::MatrixXi& EV, + Eigen::MatrixXi& FE, + Eigen::MatrixXi& EF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "edge_topology.cpp" +#endif + +#endif diff --git a/src/igl/edges.cpp b/src/igl/edges.cpp new file mode 100644 index 000000000..3d762d410 --- /dev/null +++ b/src/igl/edges.cpp @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "edges.h" +#include "adjacency_matrix.h" +#include + +template +IGL_INLINE void igl::edges( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E) +{ + // build adjacency matrix + typedef typename DerivedF::Scalar Index; + Eigen::SparseMatrix A; + igl::adjacency_matrix(F,A); + // Number of non zeros should be twice number of edges + assert(A.nonZeros()%2 == 0); + // Resize to fit edges + E.resize(A.nonZeros()/2,2); + int i = 0; + // Iterate over outside + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + // only add edge in one direction + if(it.row(), Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::edges, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/edges.h b/src/igl/edges.h new file mode 100644 index 000000000..fa3405263 --- /dev/null +++ b/src/igl/edges.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EDGES_H +#define IGL_EDGES_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Constructs a list of unique edges represented in a given mesh (V,F) + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // F #F by 3 list of mesh faces (must be triangles) + // or + // T #T x 4 matrix of indices of tet corners + // Outputs: + // E #E by 2 list of edges in no particular order + // + // See also: adjacency_matrix + template + IGL_INLINE void edges( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E); +} + +#ifndef IGL_STATIC_LIBRARY +# include "edges.cpp" +#endif + +#endif diff --git a/src/igl/edges_to_path.cpp b/src/igl/edges_to_path.cpp new file mode 100644 index 000000000..14cd895bc --- /dev/null +++ b/src/igl/edges_to_path.cpp @@ -0,0 +1,103 @@ +#include "edges_to_path.h" +#include "dfs.h" +#include "sort.h" +#include "slice.h" +#include "ismember.h" +#include "unique.h" +#include "adjacency_list.h" + +template < + typename DerivedE, + typename DerivedI, + typename DerivedJ, + typename DerivedK> +IGL_INLINE void igl::edges_to_path( + const Eigen::MatrixBase & OE, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & K) +{ + assert(OE.rows()>=1); + if(OE.rows() == 1) + { + I.resize(2); + I(0) = OE(0); + I(1) = OE(1); + J.resize(1); + J(0) = 0; + K.resize(1); + K(0) = 0; + } + + // Compute on reduced graph + DerivedI U; + Eigen::VectorXi vE; + { + Eigen::VectorXi IA; + unique(OE,U,IA,vE); + } + + Eigen::VectorXi V = Eigen::VectorXi::Zero(vE.maxCoeff()+1); + for(int e = 0;e(vE.data(),OE.rows(),OE.cols()).eval(); + { + std::vector > A; + igl::adjacency_list(E,A); + Eigen::VectorXi P,C; + dfs(A,s,I,P,C); + } + if(c == 2) + { + I.conservativeResize(I.size()+1); + I(I.size()-1) = I(0); + } + + DerivedE sE; + Eigen::Matrix sEI; + { + Eigen::MatrixXi _; + sort(E,2,true,sE,_); + Eigen::Matrix EI(I.size()-1,2); + EI.col(0) = I.head(I.size()-1); + EI.col(1) = I.tail(I.size()-1); + sort(EI,2,true,sEI,_); + } + { + Eigen::Array F; + ismember_rows(sEI,sE,F,J); + } + K.resize(I.size()-1); + for(int k = 0;k, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/edges_to_path.h b/src/igl/edges_to_path.h new file mode 100644 index 000000000..403e7029a --- /dev/null +++ b/src/igl/edges_to_path.h @@ -0,0 +1,36 @@ +#ifndef IGL_EDGES_TO_PATH_H +#define IGL_EDGES_TO_PATH_H +#include "igl_inline.h" +#include +namespace igl +{ + // EDGES_TO_PATH Given a set of undirected, unique edges such that all form a + // single connected compoent with exactly 0 or 2 nodes with valence =1, + // determine the/a path visiting all nodes. + // + // Inputs: + // E #E by 2 list of undirected edges + // Outputs: + // I #E+1 list of nodes in order tracing the chain (loop), if the output + // is a loop then I(1) == I(end) + // J #I-1 list of indices into E of edges tracing I + // K #I-1 list of indices into columns of E {1,2} so that K(i) means that + // E(i,K(i)) comes before the other (i.e., E(i,3-K(i)) ). This means that + // I(i) == E(J(i),K(i)) for i<#I, or + // I == E(sub2ind(size(E),J([1:end end]),[K;3-K(end)])))) + // + template < + typename DerivedE, + typename DerivedI, + typename DerivedJ, + typename DerivedK> + IGL_INLINE void edges_to_path( + const Eigen::MatrixBase & E, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & K); +} +#ifndef IGL_STATIC_LIBRARY +# include "edges_to_path.cpp" +#endif +#endif diff --git a/src/igl/eigs.cpp b/src/igl/eigs.cpp new file mode 100755 index 000000000..1fd440ddd --- /dev/null +++ b/src/igl/eigs.cpp @@ -0,0 +1,176 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "eigs.h" + +#include "cotmatrix.h" +#include "sort.h" +#include "slice.h" +#include "massmatrix.h" +#include + +template < + typename Atype, + typename Btype, + typename DerivedU, + typename DerivedS> +IGL_INLINE bool igl::eigs( + const Eigen::SparseMatrix & A, + const Eigen::SparseMatrix & iB, + const size_t k, + const EigsType type, + Eigen::PlainObjectBase & sU, + Eigen::PlainObjectBase & sS) +{ + using namespace Eigen; + using namespace std; + const size_t n = A.rows(); + assert(A.cols() == n && "A should be square."); + assert(iB.rows() == n && "B should be match A's dims."); + assert(iB.cols() == n && "B should be square."); + assert(type == EIGS_TYPE_SM && "Only low frequencies are supported"); + DerivedU U(n,k); + DerivedS S(k,1); + typedef Atype Scalar; + typedef Eigen::Matrix VectorXS; + // Rescale B for better numerics + const Scalar rescale = std::abs(iB.diagonal().maxCoeff()); + const Eigen::SparseMatrix B = iB/rescale; + + Scalar tol = 1e-4; + Scalar conv = 1e-14; + int max_iter = 100; + int i = 0; + //std::cout<<"start"<0) + { + eff_sigma = 1e-8+std::abs(S(i-1)); + } + // whether to use rayleigh quotient method + bool ray = false; + Scalar err = std::numeric_limits::infinity(); + int iter; + Scalar sigma = std::numeric_limits::infinity(); + VectorXS x; + for(iter = 0;iter0 && !ray) + { + // project-out existing modes + for(int j = 0;j0?1.:-1.; + + Scalar err_prev = err; + err = (A*x-sigma*B*x).array().abs().maxCoeff(); + if(err > solver; + const SparseMatrix C = A-eff_sigma*B+tikhonov*B; + //mw.save(C,"C"); + //mw.save(eff_sigma,"eff_sigma"); + //mw.save(tikhonov,"tikhonov"); + solver.compute(C); + switch(solver.info()) + { + case Eigen::Success: + break; + case Eigen::NumericalIssue: + cerr<<"Error: Numerical issue."<1e-14 || + ((U.leftCols(i).transpose()*B*x).array().abs()<=1e-7).all() + ) + { + //cout<<"Found "<, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::SparseMatrix const&, const size_t, igl::EigsType, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template bool igl::eigs, Eigen::Matrix >(Eigen::SparseMatrix const &,Eigen::SparseMatrix const &, const size_t, igl::EigsType, Eigen::PlainObjectBase< Eigen::Matrix > &, Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/eigs.h b/src/igl/eigs.h new file mode 100644 index 000000000..3b376379c --- /dev/null +++ b/src/igl/eigs.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EIGS_H +#define IGL_EIGS_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Act like MATLAB's eigs function. Compute the first/last k eigen pairs of + // the generalized eigen value problem: + // + // A u = s B u + // + // Solutions are approximate and sorted. + // + // Ideally one should use ARPACK and the Eigen unsupported ARPACK module. + // This implementation does simple, naive power iterations. + // + // Inputs: + // A #A by #A symmetric matrix + // B #A by #A symmetric positive-definite matrix + // k number of eigen pairs to compute + // type whether to extract from the high or low end + // Outputs: + // sU #A by k list of sorted eigen vectors (descending) + // sS k list of sorted eigen values (descending) + // + // Known issues: + // - only the 'sm' small magnitude eigen values are well supported + // + enum EigsType + { + EIGS_TYPE_SM = 0, + EIGS_TYPE_LM = 1, + NUM_EIGS_TYPES = 2 + }; + template < + typename Atype, + typename Btype, + typename DerivedU, + typename DerivedS> + IGL_INLINE bool eigs( + const Eigen::SparseMatrix & A, + const Eigen::SparseMatrix & B, + const size_t k, + const EigsType type, + Eigen::PlainObjectBase & sU, + Eigen::PlainObjectBase & sS); +} + +#ifndef IGL_STATIC_LIBRARY +#include "eigs.cpp" +#endif +#endif diff --git a/src/igl/embree/EmbreeIntersector.h b/src/igl/embree/EmbreeIntersector.h new file mode 100644 index 000000000..ec59c0a1a --- /dev/null +++ b/src/igl/embree/EmbreeIntersector.h @@ -0,0 +1,584 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// igl function interface for Embree2.2 +// +// Necessary changes to switch from previous Embree versions: +// * Use igl:Hit instead of embree:Hit (where id0 -> id) +// * For Embree2.2 +// * Uncomment #define __USE_RAY_MASK__ in platform.h to enable masking + +#ifndef IGL_EMBREE_EMBREE_INTERSECTOR_H +#define IGL_EMBREE_EMBREE_INTERSECTOR_H + +#include "../Hit.h" +#include +#include +#include + +#include "embree2/rtcore.h" +#include "embree2/rtcore_ray.h" +#include +#include + +namespace igl +{ + namespace embree + { + class EmbreeIntersector + { + public: + // Initialize embree engine. This will be called on instance `init()` + // calls. If already inited then this function does nothing: it is harmless + // to call more than once. + static inline void global_init(); + private: + // Deinitialize the embree engine. + static inline void global_deinit(); + public: + typedef Eigen::Matrix PointMatrixType; + typedef Eigen::Matrix FaceMatrixType; + public: + inline EmbreeIntersector(); + private: + // Copying and assignment are not allowed. + inline EmbreeIntersector(const EmbreeIntersector & that); + inline EmbreeIntersector & operator=(const EmbreeIntersector &); + public: + virtual inline ~EmbreeIntersector(); + + // Initialize with a given mesh. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of Oriented triangles + // isStatic scene is optimized for static geometry + // Side effects: + // The first time this is ever called the embree engine is initialized. + inline void init( + const PointMatrixType& V, + const FaceMatrixType& F, + bool isStatic = false); + + // Initialize with a given mesh. + // + // Inputs: + // V vector of #V by 3 list of vertex positions for each geometry + // F vector of #F by 3 list of Oriented triangles for each geometry + // masks a 32 bit mask to identify active geometries. + // isStatic scene is optimized for static geometry + // Side effects: + // The first time this is ever called the embree engine is initialized. + inline void init( + const std::vector& V, + const std::vector& F, + const std::vector& masks, + bool isStatic = false); + + // Deinitialize embree datasctructures for current mesh. Also called on + // destruction: no need to call if you just want to init() once and + // destroy. + inline void deinit(); + + // Given a ray find the first hit + // + // Inputs: + // origin 3d origin point of ray + // direction 3d (not necessarily normalized) direction vector of ray + // tnear start of ray segment + // tfar end of ray segment + // masks a 32 bit mask to identify active geometries. + // Output: + // hit information about hit + // Returns true if and only if there was a hit + inline bool intersectRay( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + Hit& hit, + float tnear = 0, + float tfar = std::numeric_limits::infinity(), + int mask = 0xFFFFFFFF) const; + + // Given a ray find the first hit + // This is a conservative hit test where multiple rays within a small radius + // will be tested and only the closesest hit is returned. + // + // Inputs: + // origin 3d origin point of ray + // direction 3d (not necessarily normalized) direction vector of ray + // tnear start of ray segment + // tfar end of ray segment + // masks a 32 bit mask to identify active geometries. + // geoId id of geometry mask (default std::numeric_limits::infinity() if no: no masking) + // closestHit true for gets closest hit, false for furthest hit + // Output: + // hit information about hit + // Returns true if and only if there was a hit + inline bool intersectBeam( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + Hit& hit, + float tnear = 0, + float tfar = std::numeric_limits::infinity(), + int mask = 0xFFFFFFFF, + int geoId = -1, + bool closestHit = true, + unsigned int samples = 4) const; + + // Given a ray find all hits in order + // + // Inputs: + // origin 3d origin point of ray + // direction 3d (not necessarily normalized) direction vector of ray + // tnear start of ray segment + // tfar end of ray segment + // masks a 32 bit mask to identify active geometries. + // Output: + // hit information about hit + // num_rays number of rays shot (at least one) + // Returns true if and only if there was a hit + inline bool intersectRay( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + std::vector &hits, + int& num_rays, + float tnear = 0, + float tfar = std::numeric_limits::infinity(), + int mask = 0xFFFFFFFF) const; + + // Given a ray find the first hit + // + // Inputs: + // a 3d first end point of segment + // ab 3d vector from a to other endpoint b + // Output: + // hit information about hit + // Returns true if and only if there was a hit + inline bool intersectSegment( + const Eigen::RowVector3f& a, + const Eigen::RowVector3f& ab, + Hit &hit, + int mask = 0xFFFFFFFF) const; + + private: + + struct Vertex {float x,y,z,a;}; + struct Triangle {int v0, v1, v2;}; + + RTCScene scene; + unsigned geomID; + Vertex* vertices; + Triangle* triangles; + bool initialized; + + inline void createRay( + RTCRay& ray, + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + float tnear, + float tfar, + int mask) const; + }; + } +} + +// Implementation +#include +// This unfortunately cannot be a static field of EmbreeIntersector because it +// would depend on the template and then we might end up with initializing +// embree twice. If only there was a way to ask embree if it's already +// initialized... +namespace igl +{ + namespace embree + { + // Keeps track of whether the **Global** Embree intersector has been + // initialized. This should never been done at the global scope. + static bool EmbreeIntersector_inited = false; + } +} + +inline void igl::embree::EmbreeIntersector::global_init() +{ + if(!EmbreeIntersector_inited) + { + rtcInit(); + if(rtcGetError() != RTC_NO_ERROR) + std::cerr << "Embree: An error occurred while initializing embree core!" << std::endl; +#ifdef IGL_VERBOSE + else + std::cerr << "Embree: core initialized." << std::endl; +#endif + EmbreeIntersector_inited = true; + } +} + +inline void igl::embree::EmbreeIntersector::global_deinit() +{ + EmbreeIntersector_inited = false; + rtcExit(); +} + +inline igl::embree::EmbreeIntersector::EmbreeIntersector() + : + //scene(NULL), + geomID(0), + vertices(NULL), + triangles(NULL), + initialized(false) +{ +} + +inline igl::embree::EmbreeIntersector::EmbreeIntersector( + const EmbreeIntersector &) + :// To make -Weffc++ happy + //scene(NULL), + geomID(0), + vertices(NULL), + triangles(NULL), + initialized(false) +{ + assert(false && "Embree: Copying EmbreeIntersector is not allowed"); +} + +inline igl::embree::EmbreeIntersector & igl::embree::EmbreeIntersector::operator=( + const EmbreeIntersector &) +{ + assert(false && "Embree: Assigning an EmbreeIntersector is not allowed"); + return *this; +} + + +inline void igl::embree::EmbreeIntersector::init( + const PointMatrixType& V, + const FaceMatrixType& F, + bool isStatic) +{ + std::vector Vtemp; + std::vector Ftemp; + std::vector masks; + Vtemp.push_back(&V); + Ftemp.push_back(&F); + masks.push_back(0xFFFFFFFF); + init(Vtemp,Ftemp,masks,isStatic); +} + +inline void igl::embree::EmbreeIntersector::init( + const std::vector& V, + const std::vector& F, + const std::vector& masks, + bool isStatic) +{ + + if(initialized) + deinit(); + + using namespace std; + global_init(); + + if(V.size() == 0 || F.size() == 0) + { + std::cerr << "Embree: No geometry specified!"; + return; + } + + // create a scene + RTCSceneFlags flags = RTC_SCENE_ROBUST | RTC_SCENE_HIGH_QUALITY; + if(isStatic) + flags = flags | RTC_SCENE_STATIC; + scene = rtcNewScene(flags,RTC_INTERSECT1); + + for(int g=0;g<(int)V.size();g++) + { + // create triangle mesh geometry in that scene + geomID = rtcNewTriangleMesh(scene,RTC_GEOMETRY_STATIC,F[g]->rows(),V[g]->rows(),1); + + // fill vertex buffer + vertices = (Vertex*)rtcMapBuffer(scene,geomID,RTC_VERTEX_BUFFER); + for(int i=0;i<(int)V[g]->rows();i++) + { + vertices[i].x = (float)V[g]->coeff(i,0); + vertices[i].y = (float)V[g]->coeff(i,1); + vertices[i].z = (float)V[g]->coeff(i,2); + } + rtcUnmapBuffer(scene,geomID,RTC_VERTEX_BUFFER); + + // fill triangle buffer + triangles = (Triangle*) rtcMapBuffer(scene,geomID,RTC_INDEX_BUFFER); + for(int i=0;i<(int)F[g]->rows();i++) + { + triangles[i].v0 = (int)F[g]->coeff(i,0); + triangles[i].v1 = (int)F[g]->coeff(i,1); + triangles[i].v2 = (int)F[g]->coeff(i,2); + } + rtcUnmapBuffer(scene,geomID,RTC_INDEX_BUFFER); + + rtcSetMask(scene,geomID,masks[g]); + } + + rtcCommit(scene); + + if(rtcGetError() != RTC_NO_ERROR) + std::cerr << "Embree: An error occurred while initializing the provided geometry!" << endl; +#ifdef IGL_VERBOSE + else + std::cerr << "Embree: geometry added." << endl; +#endif + + initialized = true; +} + +igl::embree::EmbreeIntersector +::~EmbreeIntersector() +{ + if(initialized) + deinit(); +} + +void igl::embree::EmbreeIntersector::deinit() +{ + if(EmbreeIntersector_inited && scene) + { + rtcDeleteScene(scene); + + if(rtcGetError() != RTC_NO_ERROR) + { + std::cerr << "Embree: An error occurred while resetting!" << std::endl; + } +#ifdef IGL_VERBOSE + else + { + std::cerr << "Embree: geometry removed." << std::endl; + } +#endif + } +} + +inline bool igl::embree::EmbreeIntersector::intersectRay( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + Hit& hit, + float tnear, + float tfar, + int mask) const +{ + RTCRay ray; + createRay(ray, origin,direction,tnear,tfar,mask); + + // shot ray + rtcIntersect(scene,ray); +#ifdef IGL_VERBOSE + if(rtcGetError() != RTC_NO_ERROR) + std::cerr << "Embree: An error occurred while resetting!" << std::endl; +#endif + + if((unsigned)ray.geomID != RTC_INVALID_GEOMETRY_ID) + { + hit.id = ray.primID; + hit.gid = ray.geomID; + hit.u = ray.u; + hit.v = ray.v; + hit.t = ray.tfar; + return true; + } + + return false; +} + +inline bool igl::embree::EmbreeIntersector::intersectBeam( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + Hit& hit, + float tnear, + float tfar, + int mask, + int geoId, + bool closestHit, + unsigned int samples) const +{ + bool hasHit = false; + Hit bestHit; + + if(closestHit) + bestHit.t = std::numeric_limits::max(); + else + bestHit.t = 0; + + if((intersectRay(origin,direction,hit,tnear,tfar,mask) && (hit.gid == geoId || geoId == -1))) + { + bestHit = hit; + hasHit = true; + } + + // sample points around actual ray (conservative hitcheck) + const float eps= 1e-5; + + Eigen::RowVector3f up(0,1,0); + Eigen::RowVector3f offset = direction.cross(up).normalized(); + + Eigen::Matrix3f rot = Eigen::AngleAxis(2*3.14159265358979/samples,direction).toRotationMatrix(); + + for(int r=0;r<(int)samples;r++) + { + if(intersectRay(origin+offset*eps,direction,hit,tnear,tfar,mask) && + ((closestHit && (hit.t < bestHit.t)) || + (!closestHit && (hit.t > bestHit.t))) && + (hit.gid == geoId || geoId == -1)) + { + bestHit = hit; + hasHit = true; + } + offset = rot*offset.transpose(); + } + + hit = bestHit; + return hasHit; +} + +inline bool +igl::embree::EmbreeIntersector +::intersectRay( + const Eigen::RowVector3f& origin, + const Eigen::RowVector3f& direction, + std::vector &hits, + int& num_rays, + float tnear, + float tfar, + int mask) const +{ + using namespace std; + num_rays = 0; + hits.clear(); + int last_id0 = -1; + double self_hits = 0; + // This epsilon is directly correleated to the number of missed hits, smaller + // means more accurate and slower + //const double eps = DOUBLE_EPS; + const double eps = FLOAT_EPS; + double min_t = tnear; + bool large_hits_warned = false; + RTCRay ray; + createRay(ray,origin,direction,tnear,tfar,mask); + + while(true) + { + ray.tnear = min_t; + ray.tfar = tfar; + ray.geomID = RTC_INVALID_GEOMETRY_ID; + ray.primID = RTC_INVALID_GEOMETRY_ID; + ray.instID = RTC_INVALID_GEOMETRY_ID; + num_rays++; + rtcIntersect(scene,ray); + if((unsigned)ray.geomID != RTC_INVALID_GEOMETRY_ID) + { + // Hit self again, progressively advance + if(ray.primID == last_id0 || ray.tfar <= min_t) + { + // push min_t a bit more + //double t_push = pow(2.0,self_hits-4)*(hit.t1000 && !large_hits_warned) + { + std::cout<<"Warning: Large number of hits..."<::iterator hit = hits.begin(); hit != hits.end();hit++) + { + std::cout<<(hit->id+1)<<" "; + } + + std::cout.precision(std::numeric_limits< double >::digits10); + std::cout<<"[ "; + + for(vector::iterator hit = hits.begin(); hit != hits.end(); hit++) + { + std::cout<<(hit->t)< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_EMBREE_CONVENIENCE_H +#define IGL_EMBREE_EMBREE_CONVENIENCE_H + +#undef interface +#undef near +#undef far +// Why are these in quotes? isn't that a bad idea? +#ifdef __GNUC__ +// This is how it should be done +# if __GNUC__ >= 4 +# if __GNUC_MINOR__ >= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Weffc++" +# endif +# endif +// This is a hack +# pragma GCC system_header +#endif +#include +#include +#include +#ifdef __GNUC__ +# if __GNUC__ >= 4 +# if __GNUC_MINOR__ >= 6 +# pragma GCC diagnostic pop +# endif +# endif +#endif + +#endif diff --git a/src/igl/embree/ambient_occlusion.cpp b/src/igl/embree/ambient_occlusion.cpp new file mode 100644 index 000000000..d19aa9c46 --- /dev/null +++ b/src/igl/embree/ambient_occlusion.cpp @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ambient_occlusion.h" +#include "../ambient_occlusion.h" +#include "EmbreeIntersector.h" +#include "../Hit.h" + +template < + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::embree::ambient_occlusion( + const igl::embree::EmbreeIntersector & ei, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + const auto & shoot_ray = [&ei]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir)->bool + { + igl::Hit hit; + const float tnear = 1e-4f; + return ei.intersectRay(s,dir,hit,tnear); + }; + return igl::ambient_occlusion(shoot_ray,P,N,num_samples,S); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::embree::ambient_occlusion( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + using namespace Eigen; + EmbreeIntersector ei; + ei.init(V.template cast(),F.template cast()); + ambient_occlusion(ei,P,N,num_samples,S); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::embree::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::ambient_occlusion, Eigen::Matrix, Eigen::Matrix >(igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::ambient_occlusion, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::ambient_occlusion, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::ambient_occlusion, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/embree/ambient_occlusion.h b/src/igl/embree/ambient_occlusion.h new file mode 100644 index 000000000..6df3a53ee --- /dev/null +++ b/src/igl/embree/ambient_occlusion.h @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_AMBIENT_OCCLUSION_H +#define IGL_EMBREE_AMBIENT_OCCLUSION_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace embree + { + // Forward define + class EmbreeIntersector; + // Compute ambient occlusion per given point + // + // Inputs: + // ei EmbreeIntersector containing (V,F) + // P #P by 3 list of origin points + // N #P by 3 list of origin normals + // Outputs: + // S #P list of ambient occlusion values between 1 (fully occluded) and + // 0 (not occluded) + // + template < + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void ambient_occlusion( + const EmbreeIntersector & ei, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Wrapper which builds new EmbreeIntersector for (V,F). That's expensive so + // avoid this if repeatedly calling. + template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void ambient_occlusion( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + } +}; +#ifndef IGL_STATIC_LIBRARY +# include "ambient_occlusion.cpp" +#endif + +#endif diff --git a/src/igl/embree/bone_heat.cpp b/src/igl/embree/bone_heat.cpp new file mode 100644 index 000000000..8a85af98c --- /dev/null +++ b/src/igl/embree/bone_heat.cpp @@ -0,0 +1,116 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bone_heat.h" +#include "EmbreeIntersector.h" +#include "bone_visible.h" +#include "../project_to_line_segment.h" +#include "../cotmatrix.h" +#include "../massmatrix.h" +#include "../mat_min.h" +#include + +bool igl::embree::bone_heat( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & P, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + Eigen::MatrixXd & W) +{ + using namespace std; + using namespace Eigen; + assert(CE.rows() == 0 && "Cage edges not supported."); + assert(C.cols() == V.cols() && "V and C should have same #cols"); + assert(BE.cols() == 2 && "BE should have #cols=2"); + assert(F.cols() == 3 && "F should contain triangles."); + assert(V.cols() == 3 && "V should contain 3D positions."); + + const int n = V.rows(); + const int np = P.rows(); + const int nb = BE.rows(); + const int m = np + nb; + + // "double sided lighting" + MatrixXi FF; + FF.resize(F.rows()*2,F.cols()); + FF << F, F.rowwise().reverse(); + // Initialize intersector + EmbreeIntersector ei; + ei.init(V.cast(),F.cast()); + + typedef Matrix VectorXb; + typedef Matrix MatrixXb; + MatrixXb vis_mask(n,m); + // Distances + MatrixXd D(n,m); + // loop over points + for(int j = 0;j 0) + { + cerr<<"Error: Cage edges are not supported. Ignored."<1e10?1e10:hii); + } + } + SparseMatrix Q,L,M; + cotmatrix(V,F,L); + massmatrix(V,F,MASSMATRIX_TYPE_DEFAULT,M); + const auto & H = Hdiag.asDiagonal(); + Q = (-L+M*H); + SimplicialLLT > llt; + llt.compute(Q); + switch(llt.info()) + { + case Eigen::Success: + break; + case Eigen::NumericalIssue: + cerr<<"Error: Numerical issue."< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_BONE_HEAT_H +#define IGL_EMBREE_BONE_HEAT_H +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace embree + { + // BONE_HEAT Compute skinning weights W given a surface mesh (V,F) and an + // internal skeleton (C,BE) according to "Automatic Rigging" [Baran and + // Popovic 2007]. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh corner indices into V + // C #C by 3 list of joint locations + // P #P list of point handle indices into C + // BE #BE by 2 list of bone edge indices into C + // CE #CE by 2 list of cage edge indices into **P** + // Outputs: + // W #V by #P+#BE matrix of weights. + // Returns true only on success. + // + IGL_INLINE bool bone_heat( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & C, + const Eigen::VectorXi & P, + const Eigen::MatrixXi & BE, + const Eigen::MatrixXi & CE, + Eigen::MatrixXd & W); + } +}; + +#ifndef IGL_STATIC_LIBRARY +# include "bone_heat.cpp" +#endif + +#endif diff --git a/src/igl/embree/bone_visible.cpp b/src/igl/embree/bone_visible.cpp new file mode 100644 index 000000000..4f2fe9126 --- /dev/null +++ b/src/igl/embree/bone_visible.cpp @@ -0,0 +1,145 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bone_visible.h" +#include "../project_to_line.h" +#include "../EPS.h" +#include "../Hit.h" +#include "../Timer.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedSD, + typename Derivedflag> +IGL_INLINE void igl::embree::bone_visible( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & s, + const Eigen::PlainObjectBase & d, + Eigen::PlainObjectBase & flag) +{ + // "double sided lighting" + Eigen::Matrix FF; + FF.resize(F.rows()*2,F.cols()); + FF << F, F.rowwise().reverse(); + // Initialize intersector + EmbreeIntersector ei; + ei.init(V.template cast(),FF.template cast()); + return bone_visible(V,F,ei,s,d,flag); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedSD, + typename Derivedflag> +IGL_INLINE void igl::embree::bone_visible( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const EmbreeIntersector & ei, + const Eigen::PlainObjectBase & s, + const Eigen::PlainObjectBase & d, + Eigen::PlainObjectBase & flag) +{ + using namespace std; + using namespace Eigen; + flag.resize(V.rows()); + const double sd_norm = (s-d).norm(); + // Embree seems to be parallel when constructing but not when tracing rays +#pragma omp parallel for + // loop over mesh vertices + for(int v = 0;v1) + { + t = 1; + sqrd = (Vv-d).array().pow(2).sum(); + projv = d; + } + } + igl::Hit hit; + // perhaps 1.0 should be 1.0-epsilon, or actually since we checking the + // incident face, perhaps 1.0 should be 1.0+eps + const Vector3d dir = (Vv-projv)*1.0; + if(ei.intersectSegment( + projv.template cast(), + dir.template cast(), + hit)) + { + // mod for double sided lighting + const int fi = hit.id % F.rows(); + + //if(v == 1228-1) + //{ + // Vector3d bc,P; + // bc << 1 - hit.u - hit.v, hit.u, hit.v; // barycentric + // P = V.row(F(fi,0))*bc(0) + + // V.row(F(fi,1))*bc(1) + + // V.row(F(fi,2))*bc(2); + // cout<<(fi+1)<sqrd) + { + flag(v) = true; + } + }else + { + // no hit so vectex v is visible + flag(v) = true; + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::embree::bone_visible, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::embree::bone_visible, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/embree/bone_visible.h b/src/igl/embree/bone_visible.h new file mode 100644 index 000000000..1e490b51d --- /dev/null +++ b/src/igl/embree/bone_visible.h @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_BONE_VISIBLE_H +#define IGL_EMBREE_BONE_VISIBLE_H +#include +#include +#include "EmbreeIntersector.h" +namespace igl +{ + namespace embree + { + // + // BONE_VISIBLE test whether vertices of mesh are "visible" to a given bone, + // where "visible" is defined as in [Baran & Popovic 07]. Instead of checking + // whether each point can see *any* of the bone, we just check if each point + // can see its own projection onto the bone segment. In other words, we project + // each vertex v onto the bone, projv. Then we check if there are any + // intersections between the line segment (projv-->v) and the mesh. + // + // [flag] = bone_visible(V,F,s,d); + // + // Input: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // s row vector of position of start end point of bone + // d row vector of position of dest end point of bone + // Output: + // flag #V by 1 list of bools (true) visible, (false) obstructed + // + // Note: This checks for hits along the segment which are facing in *any* + // direction from the ray. + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedSD, + typename Derivedflag> + IGL_INLINE void bone_visible( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & s, + const Eigen::PlainObjectBase & d, + Eigen::PlainObjectBase & flag); + // Inputs: + // ei EmbreeIntersector for mesh (V,F) should be double sided + template < + typename DerivedV, + typename DerivedF, + typename DerivedSD, + typename Derivedflag> + IGL_INLINE void bone_visible( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const EmbreeIntersector & ei, + const Eigen::PlainObjectBase & s, + const Eigen::PlainObjectBase & d, + Eigen::PlainObjectBase & flag); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "bone_visible.cpp" +#endif +#endif diff --git a/src/igl/embree/embree2/rtcore.h b/src/igl/embree/embree2/rtcore.h new file mode 100644 index 000000000..fb24757a9 --- /dev/null +++ b/src/igl/embree/embree2/rtcore.h @@ -0,0 +1,257 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_H__ +#define __RTCORE_H__ + +#include +#include + +#if defined(_WIN32) +#if defined(_M_X64) +typedef long long ssize_t; +#else +typedef int ssize_t; +#endif +#endif + +#ifndef RTCORE_API +#if defined(_WIN32) && !defined(ENABLE_STATIC_LIB) +# define RTCORE_API extern "C" __declspec(dllimport) +#else +# define RTCORE_API extern "C" +#endif +#endif + +#ifdef _WIN32 +# define RTCORE_ALIGN(...) __declspec(align(__VA_ARGS__)) +#else +# define RTCORE_ALIGN(...) __attribute__((aligned(__VA_ARGS__))) +#endif + +#ifdef __GNUC__ + #define RTCORE_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define RTCORE_DEPRECATED __declspec(deprecated) +#else + #define RTCORE_DEPRECATED +#endif + +/*! Embree API version */ +#define RTCORE_VERSION_MAJOR 2 +#define RTCORE_VERSION_MINOR 9 +#define RTCORE_VERSION_PATCH 0 +#define RTCORE_VERSION 20900 + +/*! \file rtcore.h Defines the Embree Ray Tracing Kernel API for C and C++ + + This file defines the Embree ray tracing kernel API for C and + C++. The user is supposed to include this file, and alternatively + the rtcore_ray.h file, but none of the other .h files in this + folder. */ + +/*! \{ */ + +/*! Axis aligned bounding box representation */ +struct RTCORE_ALIGN(16) RTCBounds +{ + float lower_x, lower_y, lower_z, align0; + float upper_x, upper_y, upper_z, align1; +}; + +/*! \brief Defines an opaque device type */ +typedef struct __RTCDevice {}* RTCDevice; + +/*! \brief Creates a new Embree device. + + Creates a new Embree device to be used by the application. An + application typically creates only a single Embree device, but it is + valid to use multiple devices inside an application. A configuration + string can be passed at construction time, that allows to configure + implementation specific parameters. If this string is NULL, a + default configuration is used. The following configuration flags are + supported by the Embree implementation of the API: + + verbose = num, // sets verbosity level (default is 0) + + If Embree is started on an unsupported CPU, rtcNewDevice will fail and + set the RTC_UNSUPPORTED_CPU error code. + +*/ +RTCORE_API RTCDevice rtcNewDevice(const char* cfg = NULL); + +/*! \brief Deletes an Embree device. + + Deletes the Embree device again. After deletion, all scene handles + are invalid. The application should invoke this call before + terminating. */ +RTCORE_API void rtcDeleteDevice(RTCDevice device); + +/*! \brief Initializes the Embree ray tracing core + + WARNING: This function is deprecated, use rtcNewDevice instead. + + Initializes the ray tracing core and passed some configuration + string. The configuration string allows to configure implementation + specific parameters. If this string is NULL, a default configuration + is used. The following configuration flags are supported by the + Embree implementation of the API: + + verbose = num, // sets verbosity level (default is 0) + + If Embree is started on an unsupported CPU, rtcInit will fail and + set the RTC_UNSUPPORTED_CPU error code. + +*/ +RTCORE_API RTCORE_DEPRECATED void rtcInit(const char* cfg = NULL); + +/*! \brief Shuts down Embree + + WARNING: This function is deprecated, use rtcDeleteDevice instead. + + Shuts down the ray tracing core. After shutdown, all scene handles + are invalid, and invoking any API call except rtcInit is not + allowed. The application should invoke this call before + terminating. It is safe to call rtcInit again after an rtcExit + call. */ +RTCORE_API RTCORE_DEPRECATED void rtcExit(); + +/*! \brief Parameters that can get configured using the rtcSetParameter functions. */ +enum RTCParameter { + RTC_SOFTWARE_CACHE_SIZE = 0, /*! Configures the software cache size (used + to cache subdivision surfaces for + instance). The size is specified as an + integer number of bytes. The software + cache cannot be configured during + rendering. (write only) */ + + RTC_CONFIG_INTERSECT1 = 1, //!< checks if rtcIntersect1 is supported (read only) + RTC_CONFIG_INTERSECT4 = 2, //!< checks if rtcIntersect4 is supported (read only) + RTC_CONFIG_INTERSECT8 = 3, //!< checks if rtcIntersect8 is supported (read only) + RTC_CONFIG_INTERSECT16 = 4, //!< checks if rtcIntersect16 is supported (read only) + RTC_CONFIG_INTERSECTN = 5, //!< checks if rtcIntersectN is supported (read only) + + RTC_CONFIG_RAY_MASK = 6, //!< checks if ray masks are supported (read only) + RTC_CONFIG_BACKFACE_CULLING = 7, //!< checks if backface culling is supported (read only) + RTC_CONFIG_INTERSECTION_FILTER = 8, //!< checks if intersection filters are enabled (read only) + RTC_CONFIG_INTERSECTION_FILTER_RESTORE = 9, //!< checks if intersection filters restores previous hit (read only) + RTC_CONFIG_BUFFER_STRIDE = 10, //!< checks if buffer strides are supported (read only) + RTC_CONFIG_IGNORE_INVALID_RAYS = 11, //!< checks if invalid rays are ignored (read only) + RTC_CONFIG_TASKING_SYSTEM = 12, //!< return used tasking system (0 = INTERNAL, 1 = TBB) (read only) + + RTC_CONFIG_VERSION_MAJOR = 13, //!< returns Embree major version (read only) + RTC_CONFIG_VERSION_MINOR = 14, //!< returns Embree minor version (read only) + RTC_CONFIG_VERSION_PATCH = 15, //!< returns Embree patch version (read only) + RTC_CONFIG_VERSION = 16, //!< returns Embree version as integer (e.g. Embree v2.8.2 -> 20802) (read only) +}; + +/*! \brief Configures some parameters. + WARNING: This function is deprecated, use rtcDeviceSetParameter1i instead. +*/ +RTCORE_API RTCORE_DEPRECATED void rtcSetParameter1i(const RTCParameter parm, ssize_t val); + +/*! \brief Reads some device parameter. + WARNING: This function is deprecated, use rtcDeviceGetParameter1i instead. +*/ +RTCORE_API RTCORE_DEPRECATED ssize_t rtcGetParameter1i(const RTCParameter parm); + +/*! \brief Configures some device parameters. */ +RTCORE_API void rtcDeviceSetParameter1i(RTCDevice device, const RTCParameter parm, ssize_t val); + +/*! \brief Reads some device parameter. */ +RTCORE_API ssize_t rtcDeviceGetParameter1i(RTCDevice device, const RTCParameter parm); + +/*! \brief Error codes returned by the rtcGetError function. */ +enum RTCError { + RTC_NO_ERROR = 0, //!< No error has been recorded. + RTC_UNKNOWN_ERROR = 1, //!< An unknown error has occured. + RTC_INVALID_ARGUMENT = 2, //!< An invalid argument is specified + RTC_INVALID_OPERATION = 3, //!< The operation is not allowed for the specified object. + RTC_OUT_OF_MEMORY = 4, //!< There is not enough memory left to execute the command. + RTC_UNSUPPORTED_CPU = 5, //!< The CPU is not supported as it does not support SSE2. + RTC_CANCELLED = 6, //!< The user has cancelled the operation through the RTC_PROGRESS_MONITOR_FUNCTION callback +}; + +/*! \brief Returns the value of the per-thread error flag. + + WARNING: This function is deprecated, use rtcDeviceGetError instead. + + If an error occurs this flag is set to an error code if it stores no + previous error. The rtcGetError function reads and returns the + currently stored error and clears the error flag again. */ +RTCORE_API RTCORE_DEPRECATED RTCError rtcGetError(); + +/*! \brief Returns the value of the per-thread error flag. + + If an error occurs this flag is set to an error code if it stores no + previous error. The rtcGetError function reads and returns the + currently stored error and clears the error flag again. */ +RTCORE_API RTCError rtcDeviceGetError(RTCDevice device); + +/*! \brief Type of error callback function. */ +typedef void (*RTCErrorFunc)(const RTCError code, const char* str); +RTCORE_DEPRECATED typedef RTCErrorFunc RTC_ERROR_FUNCTION; + +/*! \brief Sets a callback function that is called whenever an error occurs. + WARNING: This function is deprecated, use rtcDeviceSetErrorFunction instead. + */ +RTCORE_API RTCORE_DEPRECATED void rtcSetErrorFunction(RTCErrorFunc func); + +/*! \brief Sets a callback function that is called whenever an error occurs. */ +RTCORE_API void rtcDeviceSetErrorFunction(RTCDevice device, RTCErrorFunc func); + +/*! \brief Type of memory consumption callback function. */ +typedef bool (*RTCMemoryMonitorFunc)(const ssize_t bytes, const bool post); +RTCORE_DEPRECATED typedef RTCMemoryMonitorFunc RTC_MEMORY_MONITOR_FUNCTION; + +/*! \brief Sets the memory consumption callback function which is + * called before or after the library allocates or frees memory. + WARNING: This function is deprecated, use rtcDeviceSetMemoryMonitorFunction instead. +*/ +RTCORE_API RTCORE_DEPRECATED void rtcSetMemoryMonitorFunction(RTCMemoryMonitorFunc func); + +/*! \brief Sets the memory consumption callback function which is + * called before or after the library allocates or frees memory. */ +RTCORE_API void rtcDeviceSetMemoryMonitorFunction(RTCDevice device, RTCMemoryMonitorFunc func); + +/*! \brief Implementation specific (do not call). + + This function is implementation specific and only for debugging + purposes. Do not call it. */ +RTCORE_API RTCORE_DEPRECATED void rtcDebug(); // FIXME: remove + +#include "rtcore_scene.h" +#include "rtcore_geometry.h" +#include "rtcore_geometry_user.h" + +/*! \brief Helper to easily combing scene flags */ +inline RTCSceneFlags operator|(const RTCSceneFlags a, const RTCSceneFlags b) { + return (RTCSceneFlags)((size_t)a | (size_t)b); +} + +/*! \brief Helper to easily combing algorithm flags */ +inline RTCAlgorithmFlags operator|(const RTCAlgorithmFlags a, const RTCAlgorithmFlags b) { + return (RTCAlgorithmFlags)((size_t)a | (size_t)b); +} + +/*! \brief Helper to easily combing geometry flags */ +inline RTCGeometryFlags operator|(const RTCGeometryFlags a, const RTCGeometryFlags b) { + return (RTCGeometryFlags)((size_t)a | (size_t)b); +} + +/*! \} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore.isph b/src/igl/embree/embree2/rtcore.isph new file mode 100644 index 000000000..f46f05252 --- /dev/null +++ b/src/igl/embree/embree2/rtcore.isph @@ -0,0 +1,220 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_ISPH__ +#define __RTCORE_ISPH__ + +#ifdef _WIN32 +# define RTCORE_ALIGN(...) // FIXME: need to specify alignment +#else +# define RTCORE_ALIGN(...) // FIXME: need to specify alignment +#endif + +#define RTCORE_DEPRECATED // FIXME: deprecation not supported by ISPC + +/*! Embree API version */ +#define RTCORE_VERSION_MAJOR 2 +#define RTCORE_VERSION_MINOR 9 +#define RTCORE_VERSION_PATCH 0 +#define RTCORE_VERSION 20900 + +/*! \file rtcore.isph Defines the Embree Ray Tracing Kernel API for ISPC. + + This file defines the Embree ray tracing kernel API for C and + C++. The user is supposed to include this file, and alternatively + the rtcore_ray.isph file, but none of the other .isph files in this + folder. */ + +/*! \{ */ + +/*! Axis aligned bounding box representation */ +RTCORE_ALIGN(16) struct RTCBounds +{ + float lower_x, lower_y, lower_z, align0; + float upper_x, upper_y, upper_z, align1; +}; + +/*! \brief Defines an opaque device type */ +typedef uniform struct __RTCDevice {}* uniform RTCDevice; + +/*! \brief Creates a new Embree device. + + Creates a new Embree device to be used by the application. An + application typically creates only a single Embree device, but it is + valid to use multiple devices inside an application. A configuration + string can be passed at construction time, that allows to configure + implementation specific parameters. If this string is NULL, a + default configuration is used. The following configuration flags are + supported by the Embree implementation of the API: + + verbose = num, // sets verbosity level (default is 0) + + If Embree is started on an unsupported CPU, rtcNewDevice will fail and + set the RTC_UNSUPPORTED_CPU error code. + +*/ +RTCDevice rtcNewDevice(const uniform int8* uniform cfg = NULL); + +/*! \brief Deletes an Embree device. + + Deletes the Embree device again. After deletion, all scene handles + are invalid. The application should invoke this call before + terminating. */ +void rtcDeleteDevice(RTCDevice device); + +/*! \brief Initializes the Embree ray tracing core + + WARNING: This function is deprecated, use rtcNewDevice instead. + + Initializes the ray tracing core and passed some configuration + string. The configuration string allows to configure implementation + specific parameters. If this string is NULL, a default configuration + is used. The following configuration flags are supported by the + Embree implementation of the API: + + verbose = num, // sets verbosity level (default is 0) + + If Embree is started on an unsupported CPU, rtcInit will fail and + set the RTC_UNSUPPORTED_CPU error code. + +*/ +RTCORE_DEPRECATED void rtcInit(const uniform int8* uniform cfg = NULL); + +/*! \brief Shuts down Embree. + + WARNING: This function is deprecated, use rtcDeleteDevice instead. + + Shuts down the ray tracing core. After shutdown, all scene handles + are invalid, and invoking any API call except rtcInit is not + allowed. The application should invoke this call before + terminating. It is safe to call rtcInit again after an rtcExit + call. */ +RTCORE_DEPRECATED void rtcExit(); + +/*! \brief Parameters that can get configured using the rtcSetParameter functions. */ +enum RTCParameter { + RTC_SOFTWARE_CACHE_SIZE = 0, /*! Configures the software cache size (used + to cache subdivision surfaces for + instance). The size is specified as an + integer number of bytes. The software + cache cannot be configured during + rendering. (write only) */ + + RTC_CONFIG_INTERSECT1 = 1, //!< checks if rtcIntersect1 is supported (read only) + RTC_CONFIG_INTERSECT4 = 2, //!< checks if rtcIntersect4 is supported (read only) + RTC_CONFIG_INTERSECT8 = 3, //!< checks if rtcIntersect8 is supported (read only) + RTC_CONFIG_INTERSECT16 = 4, //!< checks if rtcIntersect16 is supported (read only) + RTC_CONFIG_INTERSECTN = 5, //!< checks if rtcIntersectN is supported (read only) + + RTC_CONFIG_RAY_MASK = 6, //!< checks if ray masks are supported (read only) + RTC_CONFIG_BACKFACE_CULLING = 7, //!< checks if backface culling is supported (read only) + RTC_CONFIG_INTERSECTION_FILTER = 8, //!< checks if intersection filters are enabled (read only) + RTC_CONFIG_INTERSECTION_FILTER_RESTORE = 9, //!< checks if intersection filters restores previous hit (read only) + RTC_CONFIG_BUFFER_STRIDE = 10, //!< checks if buffer strides are supported (read only) + RTC_CONFIG_IGNORE_INVALID_RAYS = 11, //!< checks if invalid rays are ignored (read only) + RTC_CONFIG_TASKING_SYSTEM = 12, //!< return used tasking system (0 = INTERNAL, 1 = TBB) (read only) + + + RTC_CONFIG_VERSION_MAJOR = 13, //!< returns Embree major version (read only) + RTC_CONFIG_VERSION_MINOR = 14, //!< returns Embree minor version (read only) + RTC_CONFIG_VERSION_PATCH = 15, //!< returns Embree patch version (read only) + RTC_CONFIG_VERSION = 16, //!< returns Embree version as integer (e.g. Embree v2.8.2 -> 20802) (read only) +}; + +/*! \brief Configures some parameters. + WARNING: This function is deprecated, use rtcDeviceSetParameter1i instead. +*/ +RTCORE_DEPRECATED void rtcSetParameter1i(const uniform RTCParameter parm, uniform size_t val); // FIXME: should be ssize_t + +/*! \brief Reads some parameters. + WARNING: This function is deprecated, use rtcDeviceGetParameter1i instead. +*/ +uniform size_t rtcGetParameter1i(const uniform RTCParameter parm); // FIXME: should return ssize_t + +/*! \brief Configures some device parameters.*/ +void rtcDeviceSetParameter1i(RTCDevice device, const uniform RTCParameter parm, uniform size_t val); // FIXME: should be ssize_t + +/*! \brief Reads some device parameters. */ +uniform size_t rtcDeviceGetParameter1i(RTCDevice device, const uniform RTCParameter parm); // FIXME: should return ssize_t + +/*! \brief Error codes returned by the rtcGetError function. */ +enum RTCError { + RTC_NO_ERROR = 0, //!< No error has been recorded. + RTC_UNKNOWN_ERROR = 1, //!< An unknown error has occured. + RTC_INVALID_ARGUMENT = 2, //!< An invalid argument is specified + RTC_INVALID_OPERATION = 3, //!< The operation is not allowed for the specified object. + RTC_OUT_OF_MEMORY = 4, //!< There is not enough memory left to execute the command. + RTC_UNSUPPORTED_CPU = 5, //!< The CPU is not supported as it does not support SSE2. + RTC_CANCELLED = 6 //!< The user has cancelled the operation through the RTCProgressMonitorFunc callback +}; + +/*! \brief Returns the value of the per-thread error flag. + + WARNING: This function is deprecated, use rtcDeviceGetError instead. + + If an error occurs this flag is set to an error code if it stores no + previous error. The rtcGetError function reads and returns the + currently stored error and clears the error flag again. */ +RTCORE_DEPRECATED uniform RTCError rtcGetError(); + +/*! \brief Returns the value of the per-thread error flag. + + If an error occurs this flag is set to an error code if it stores no + previous error. The rtcGetError function reads and returns the + currently stored error and clears the error flag again. */ +uniform RTCError rtcDeviceGetError(RTCDevice device); + +/*! \brief Type of error callback function. */ +typedef void (*uniform RTCErrorFunc)(const uniform RTCError code, const uniform int8* uniform str); +RTCORE_DEPRECATED typedef RTCErrorFunc RTC_ERROR_FUNCTION; + +/*! \brief Sets a callback function that is called whenever an error occurs. + WARNING: This function is deprecated, use rtcDeviceSetErrorFunction instead. +*/ +RTCORE_DEPRECATED void rtcSetErrorFunction(uniform RTCErrorFunc func); + +/*! \brief Sets a callback function that is called whenever an error occurs. */ +void rtcDeviceSetErrorFunction(RTCDevice device, uniform RTCErrorFunc func); + +/*! \brief Type of memory consumption callback function. */ +typedef uniform bool (*uniform RTCMemoryMonitorFunc)(const uniform size_t bytes, const uniform bool post); // FIXME: should be ssize_t +RTCORE_DEPRECATED typedef RTCMemoryMonitorFunc RTC_MEMORY_MONITOR_FUNCTION; + +/*! \brief Sets the memory consumption callback function which is + * called before the library allocates or after the library frees + * memory. + * WARNING: This function is deprecated, use rtcDeviceSetMemoryMonitorFunction instead. +*/ +RTCORE_DEPRECATED void rtcSetMemoryMonitorFunction(RTCMemoryMonitorFunc func); + +/*! \brief Sets the memory consumption callback function which is + * called before the library allocates or after the library frees + * memory. */ +void rtcDeviceSetMemoryMonitorFunction(RTCDevice device, RTCMemoryMonitorFunc func); + +/*! \brief Implementation specific (do not call). + + This function is implementation specific and only for debugging + purposes. Do not call it. */ +RTCORE_DEPRECATED void rtcDebug(); // FIXME: remove + +#include "rtcore_scene.isph" +#include "rtcore_geometry.isph" +#include "rtcore_geometry_user.isph" + +/*! \} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_geometry.h b/src/igl/embree/embree2/rtcore_geometry.h new file mode 100644 index 000000000..5b80130fc --- /dev/null +++ b/src/igl/embree/embree2/rtcore_geometry.h @@ -0,0 +1,483 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_GEOMETRY_H__ +#define __RTCORE_GEOMETRY_H__ + +/*! \ingroup embree_kernel_api */ +/*! \{ */ + +/*! invalid geometry ID */ +#define RTC_INVALID_GEOMETRY_ID ((unsigned)-1) + +/*! \brief Specifies the type of buffers when mapping buffers */ +enum RTCBufferType { + RTC_INDEX_BUFFER = 0x01000000, + + RTC_VERTEX_BUFFER = 0x02000000, + RTC_VERTEX_BUFFER0 = 0x02000000, + RTC_VERTEX_BUFFER1 = 0x02000001, + + RTC_USER_VERTEX_BUFFER = 0x02100000, + RTC_USER_VERTEX_BUFFER0 = 0x02100000, + RTC_USER_VERTEX_BUFFER1 = 0x02100001, + + RTC_FACE_BUFFER = 0x03000000, + RTC_LEVEL_BUFFER = 0x04000001, + + RTC_EDGE_CREASE_INDEX_BUFFER = 0x05000000, + RTC_EDGE_CREASE_WEIGHT_BUFFER = 0x06000000, + + RTC_VERTEX_CREASE_INDEX_BUFFER = 0x07000000, + RTC_VERTEX_CREASE_WEIGHT_BUFFER = 0x08000000, + + RTC_HOLE_BUFFER = 0x09000001, +}; + +/*! \brief Supported types of matrix layout for functions involving matrices */ +enum RTCMatrixType { + RTC_MATRIX_ROW_MAJOR = 0, + RTC_MATRIX_COLUMN_MAJOR = 1, + RTC_MATRIX_COLUMN_MAJOR_ALIGNED16 = 2, +}; + +/*! \brief Supported geometry flags to specify handling in dynamic scenes. */ +enum RTCGeometryFlags +{ + RTC_GEOMETRY_STATIC = 0, //!< specifies static geometry that will change rarely + RTC_GEOMETRY_DEFORMABLE = 1, //!< specifies dynamic geometry with deformable motion (BVH refit possible) + RTC_GEOMETRY_DYNAMIC = 2, //!< specifies dynamic geometry with arbitrary motion (BVH refit not possible) +}; + +/*! \brief Boundary interpolation mode for subdivision surfaces */ +enum RTCBoundaryMode +{ + RTC_BOUNDARY_NONE = 0, //!< ignores border patches + RTC_BOUNDARY_EDGE_ONLY = 1, //!< soft boundary (default) + RTC_BOUNDARY_EDGE_AND_CORNER = 2 //!< boundary corner vertices are sharp vertices +}; + +/*! Intersection filter function for single rays. */ +typedef void (*RTCFilterFunc)(void* ptr, /*!< pointer to user data */ + RTCRay& ray /*!< intersection to filter */); + +/*! Intersection filter function for ray packets of size 4. */ +typedef void (*RTCFilterFunc4)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay4& ray /*!< intersection to filter */); + +/*! Intersection filter function for ray packets of size 8. */ +typedef void (*RTCFilterFunc8)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay8& ray /*!< intersection to filter */); + +/*! Intersection filter function for ray packets of size 16. */ +typedef void (*RTCFilterFunc16)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay16& ray /*!< intersection to filter */); + +/*! Displacement mapping function. */ +typedef void (*RTCDisplacementFunc)(void* ptr, /*!< pointer to user data of geometry */ + unsigned geomID, /*!< ID of geometry to displace */ + unsigned primID, /*!< ID of primitive of geometry to displace */ + const float* u, /*!< u coordinates (source) */ + const float* v, /*!< v coordinates (source) */ + const float* nx, /*!< x coordinates of normalized normal at point to displace (source) */ + const float* ny, /*!< y coordinates of normalized normal at point to displace (source) */ + const float* nz, /*!< z coordinates of normalized normal at point to displace (source) */ + float* px, /*!< x coordinates of points to displace (source and target) */ + float* py, /*!< y coordinates of points to displace (source and target) */ + float* pz, /*!< z coordinates of points to displace (source and target) */ + size_t N /*!< number of points to displace */ ); + +/*! \brief Creates a new scene instance. + + A scene instance contains a reference to a scene to instantiate and + the transformation to instantiate the scene with. An implementation + will typically transform the ray with the inverse of the provided + transformation and continue traversing the ray through the provided + scene. If any geometry is hit, the instance ID (instID) member of + the ray will get set to the geometry ID of the instance. */ +RTCORE_API unsigned rtcNewInstance (RTCScene target, //!< the scene the instance belongs to + RTCScene source //!< the scene to instantiate + ); + +/*! \brief Creates a new scene instance. + + A scene instance contains a reference to a scene to instantiate and + the transformation to instantiate the scene with. For motion blurred + instances, a number of timesteps can get specified (currently only 1 + or 2 timesteps are supported). An implementation will typically + transform the ray with the inverse of the provided transformation + and continue traversing the ray through the provided scene. If any + geometry is hit, the instance ID (instID) member of the ray will get + set to the geometry ID of the instance. */ +RTCORE_API unsigned rtcNewInstance2 (RTCScene target, //!< the scene the instance belongs to + RTCScene source, //!< the scene to instantiate + size_t numTimeSteps = 1); //!< number of timesteps, one matrix per timestep + +/*! \brief Sets transformation of the instance */ +RTCORE_API void rtcSetTransform (RTCScene scene, //!< scene handle + unsigned geomID, //!< ID of geometry + RTCMatrixType layout, //!< layout of transformation matrix + const float* xfm //!< pointer to transformation matrix + ); + + +/*! \brief Sets transformation of the instance for specified timestep */ +RTCORE_API void rtcSetTransform2 (RTCScene scene, //!< scene handle + unsigned int geomID, //!< ID of geometry + RTCMatrixType layout, //!< layout of transformation matrix + const float* xfm, //!< pointer to transformation matrix + size_t timeStep = 0 //!< timestep to set the matrix for + ); + +/*! \brief Creates a new triangle mesh. The number of triangles + (numTriangles), number of vertices (numVertices), and number of time + steps (1 for normal meshes, and 2 for linear motion blur), have to + get specified. The triangle indices can be set be mapping and + writing to the index buffer (RTC_INDEX_BUFFER) and the triangle + vertices can be set by mapping and writing into the vertex buffer + (RTC_VERTEX_BUFFER). In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of + three 32 bit integer indices for each triangle. An index points to + the ith vertex. The vertex buffer stores single precision x,y,z + floating point coordinates aligned to 16 bytes. The value of the 4th + float used for alignment can be arbitrary. */ +RTCORE_API unsigned rtcNewTriangleMesh (RTCScene scene, //!< the scene the mesh belongs to + RTCGeometryFlags flags, //!< geometry flags + size_t numTriangles, //!< number of triangles + size_t numVertices, //!< number of vertices + size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + + +/*! \brief Creates a new quad mesh. The number of quads + (numQuads), number of vertices (numVertices), and number of time + steps (1 for normal meshes, and 2 for linear motion blur), have to + get specified. The quad indices can be set be mapping and + writing to the index buffer (RTC_INDEX_BUFFER) and the quad + vertices can be set by mapping and writing into the vertex buffer + (RTC_VERTEX_BUFFER). In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of + three 32 bit integer indices for each quad. An index points to + the ith vertex. The vertex buffer stores single precision x,y,z + floating point coordinates aligned to 16 bytes. The value of the 4th + float used for alignment can be arbitrary. */ +RTCORE_API unsigned rtcNewQuadMesh (RTCScene scene, //!< the scene the mesh belongs to + RTCGeometryFlags flags, //!< geometry flags + size_t numQuads, //!< number of quads + size_t numVertices, //!< number of vertices + size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Creates a new subdivision mesh. The number of faces + (numFaces), edges/indices (numEdges), vertices (numVertices), edge + creases (numEdgeCreases), vertex creases (numVertexCreases), holes + (numHoles), and time steps (numTimeSteps) have to get speficied at + construction time. + + The following buffers have to get filled by the application: the face + buffer (RTC_FACE_BUFFER) contains the number edges/indices (3 or 4) + of each of the numFaces faces, the index buffer (RTC_INDEX_BUFFER) + contains multiple (3 or 4) 32bit vertex indices for each face and + numEdges indices in total, the vertex buffer (RTC_VERTEX_BUFFER) + stores numVertices vertices as single precision x,y,z floating point + coordinates aligned to 16 bytes. The value of the 4th float used for + alignment can be arbitrary. + + Optionally, the application can fill the hole buffer + (RTC_HOLE_BUFFER) with numHoles many 32 bit indices of faces that + should be considered non-existing. + + Optionally, the application can fill the level buffer + (RTC_LEVEL_BUFFER) with a tessellation level for each of the numEdges + edges. The subdivision level is a positive floating point value, that + specifies how many quads along the edge should get generated during + tessellation. The tessellation level is a lower bound, thus the + implementation is free to choose a larger level. If no level buffer + is specified a level of 1 is used. + + Optionally, the application can fill the sparse edge crease buffers + to make some edges appear sharper. The edge crease index buffer + (RTC_EDGE_CREASE_INDEX_BUFFER) contains numEdgeCreases many pairs of + 32 bit vertex indices that specify unoriented edges. The edge crease + weight buffer (RTC_EDGE_CREASE_WEIGHT_BUFFER) stores for each of + theses crease edges a positive floating point weight. The larger this + weight, the sharper the edge. Specifying a weight of infinify is + supported and marks an edge as infinitely sharp. Storing an edge + multiple times with the same crease weight is allowed, but has lower + performance. Storing the an edge multiple times with different + crease weights results in undefined behaviour. For a stored edge + (i,j), the reverse direction edges (j,i) does not have to get stored, + as both are considered the same edge. + + Optionally, the application can fill the sparse vertex crease buffers + to make some vertices appear sharper. The vertex crease index buffer + (RTC_VERTEX_CREASE_INDEX_BUFFER), contains numVertexCreases many 32 + bit vertex indices to speficy a set of vertices. The vertex crease + weight buffer (RTC_VERTEX_CREASE_WEIGHT_BUFFER) specifies for each of + these vertices a positive floating point weight. The larger this + weight, the sharper the vertex. Specifying a weight of infinity is + supported and makes the vertex infinitely sharp. Storing a vertex + multiple times with the same crease weight is allowed, but has lower + performance. Storing a vertex multiple times with different crease + weights results in undefined behaviour. + +*/ +RTCORE_API unsigned rtcNewSubdivisionMesh (RTCScene scene, //!< the scene the mesh belongs to + RTCGeometryFlags flags, //!< geometry flags + size_t numFaces, //!< number of faces + size_t numEdges, //!< number of edges + size_t numVertices, //!< number of vertices + size_t numEdgeCreases, //!< number of edge creases + size_t numVertexCreases, //!< number of vertex creases + size_t numHoles, //!< number of holes + size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Creates a new hair geometry, consisting of multiple hairs + represented as cubic bezier curves with varying radii. The number of + curves (numCurves), number of vertices (numVertices), and number of + time steps (1 for normal curves, and 2 for linear motion blur), have + to get specified at construction time. Further, the curve index + buffer (RTC_INDEX_BUFFER) and the curve vertex buffer + (RTC_VERTEX_BUFFER) have to get set by mapping and writing to the + appropiate buffers. In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of a + single 32 bit integer index for each curve, that references the + start vertex of the curve. The vertex buffer stores 4 control points + per curve, each such control point consists of a single precision + (x,y,z) position and radius, stored in that order in + memory. Individual hairs are considered to be subpixel sized which + allows the implementation to approximate the intersection + calculation. This in particular means that zooming onto one hair + might show geometric artefacts. */ +RTCORE_API unsigned rtcNewHairGeometry (RTCScene scene, //!< the scene the curves belong to + RTCGeometryFlags flags, //!< geometry flags + size_t numCurves, //!< number of curves + size_t numVertices, //!< number of vertices + size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! Sets a uniform tessellation rate for subdiv meshes and hair + * geometry. For subdivision meshes the RTC_LEVEL_BUFFER can also be used + * optionally to set a different tessellation rate per edge.*/ +RTCORE_API void rtcSetTessellationRate (RTCScene scene, unsigned geomID, float tessellationRate); + +/*! \brief Creates a new line segment geometry, consisting of multiple + segments with varying radii. The number of line segments (numSegments), + number of vertices (numVertices), and number of time steps (1 for + normal line segments, and 2 for linear motion blur), have to get + specified at construction time. Further, the segment index buffer + (RTC_INDEX_BUFFER) and the segment vertex buffer (RTC_VERTEX_BUFFER) + have to get set by mapping and writing to the appropiate buffers. In + case of linear motion blur, two vertex buffers have to get filled + (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), one for each time step. The + index buffer has the default layout of a single 32 bit integer index + for each line segment, that references the start vertex of the segment. + The vertex buffer stores 2 end points per line segment, each such point + consists of a single precision (x,y,z) position and radius, stored in + that order in memory. Individual segments are considered to be subpixel + sized which allows the implementation to approximate the intersection + calculation. This in particular means that zooming onto one line segment + might show geometric artefacts. */ +RTCORE_API unsigned rtcNewLineSegments (RTCScene scene, //!< the scene the line segments belong to + RTCGeometryFlags flags, //!< geometry flags + size_t numSegments, //!< number of line segments + size_t numVertices, //!< number of vertices + size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Sets 32 bit ray mask. */ +RTCORE_API void rtcSetMask (RTCScene scene, unsigned geomID, int mask); + +/*! \brief Sets boundary interpolation mode for subdivision surfaces */ +RTCORE_API void rtcSetBoundaryMode(RTCScene scene, unsigned geomID, RTCBoundaryMode mode); + +/*! \brief Maps specified buffer. This function can be used to set index and + * vertex buffers of geometries. */ +RTCORE_API void* rtcMapBuffer(RTCScene scene, unsigned geomID, RTCBufferType type); + +/*! \brief Unmaps specified buffer. + + A buffer has to be unmapped before the rtcEnable, rtcDisable, + rtcUpdate, or rtcDeleteGeometry calls are executed. */ +RTCORE_API void rtcUnmapBuffer(RTCScene scene, unsigned geomID, RTCBufferType type); + +/*! \brief Shares a data buffer between the application and + * Embree. The passed buffer is used by Embree to store index and + * vertex data. It has to remain valid as long as the mesh exists, + * and the user is responsible to free the data when the mesh gets + * deleted. One can optionally speficy a byte offset and byte stride + * of the elements stored inside the buffer. The addresses + * ptr+offset+i*stride have to be aligned to 4 bytes on Xeon CPUs and + * 16 bytes on Xeon Phi accelerators. For vertex buffers, the 4 bytes + * after the z-coordinate of the last vertex have to be readable memory, + * thus padding is required for some layouts. If this function is not + * called, Embree will allocate and manage buffers of the default + * layout. */ +RTCORE_API void rtcSetBuffer(RTCScene scene, unsigned geomID, RTCBufferType type, + const void* ptr, size_t byteOffset, size_t byteStride); + +/*! \brief Enable geometry. Enabled geometry can be hit by a ray. */ +RTCORE_API void rtcEnable (RTCScene scene, unsigned geomID); + +/*! \brief Update all geometry buffers. + + Each time geometry buffers got modified, the user has to call some + update function to tell the ray tracing engine which buffers got + modified. The rtcUpdate function taggs each geometry buffer of the + specified geometry as modified. */ +RTCORE_API void rtcUpdate (RTCScene scene, unsigned geomID); + +/*! \brief Update spefific geometry buffer. + + Each time geometry buffers got modified, the user has to call some + update function to tell the ray tracing engine which buffers got + modified. The rtcUpdateBuffer function taggs a specific buffer of + some geometry as modified. */ +RTCORE_API void rtcUpdateBuffer (RTCScene scene, unsigned geomID, RTCBufferType type); + +/*! \brief Disable geometry. + + Disabled geometry is not hit by any ray. Disabling and enabling + geometry gives higher performance than deleting and recreating + geometry. */ +RTCORE_API void rtcDisable (RTCScene scene, unsigned geomID); + +/*! \brief Sets the displacement function. */ +RTCORE_API void rtcSetDisplacementFunction (RTCScene scene, unsigned geomID, RTCDisplacementFunc func, RTCBounds* bounds); + +/*! \brief Sets the intersection filter function for single rays. */ +RTCORE_API void rtcSetIntersectionFilterFunction (RTCScene scene, unsigned geomID, RTCFilterFunc func); + +/*! \brief Sets the intersection filter function for ray packets of size 4. */ +RTCORE_API void rtcSetIntersectionFilterFunction4 (RTCScene scene, unsigned geomID, RTCFilterFunc4 func); + +/*! \brief Sets the intersection filter function for ray packets of size 8. */ +RTCORE_API void rtcSetIntersectionFilterFunction8 (RTCScene scene, unsigned geomID, RTCFilterFunc8 func); + +/*! \brief Sets the intersection filter function for ray packets of size 16. */ +RTCORE_API void rtcSetIntersectionFilterFunction16 (RTCScene scene, unsigned geomID, RTCFilterFunc16 func); + +/*! \brief Sets the occlusion filter function for single rays. */ +RTCORE_API void rtcSetOcclusionFilterFunction (RTCScene scene, unsigned geomID, RTCFilterFunc func); + +/*! \brief Sets the occlusion filter function for ray packets of size 4. */ +RTCORE_API void rtcSetOcclusionFilterFunction4 (RTCScene scene, unsigned geomID, RTCFilterFunc4 func); + +/*! \brief Sets the occlusion filter function for ray packets of size 8. */ +RTCORE_API void rtcSetOcclusionFilterFunction8 (RTCScene scene, unsigned geomID, RTCFilterFunc8 func); + +/*! \brief Sets the occlusion filter function for ray packets of size 16. */ +RTCORE_API void rtcSetOcclusionFilterFunction16 (RTCScene scene, unsigned geomID, RTCFilterFunc16 func); + +/*! Set pointer for user defined data per geometry. Invokations + * of the various user intersect and occluded functions get passed + * this data pointer when called. */ +RTCORE_API void rtcSetUserData (RTCScene scene, unsigned geomID, void* ptr); + +/*! Get pointer for user defined data per geometry based on geomID. */ +RTCORE_API void* rtcGetUserData (RTCScene scene, unsigned geomID); + +/*! Interpolates user data to some u/v location. The data buffer + * specifies per vertex data to interpolate and can be one of the + * RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to + * contain numFloats floating point values to interpolate for each + * vertex of the geometry. The dP array will get filled with the + * interpolated data and the dPdu and dPdv arrays with the u and v + * derivative of the interpolation. If the pointers dP is NULL, the + * value will not get calculated. If dPdu and dPdv are NULL the + * derivatives will not get calculated. Both dPdu and dPdv have to be + * either valid or NULL. The buffer has to be padded at the end such + * that the last element can be read safely using SSE + * instructions. */ +RTCORE_API void rtcInterpolate(RTCScene scene, unsigned geomID, unsigned primID, float u, float v, RTCBufferType buffer, + float* P, float* dPdu, float* dPdv, size_t numFloats); + +/*! Interpolates user data to some u/v location. The data buffer + * specifies per vertex data to interpolate and can be one of the + * RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to + * contain numFloats floating point values to interpolate for each + * vertex of the geometry. The P array will get filled with the + * interpolated datam the dPdu and dPdv arrays with the u and v + * derivative of the interpolation, and the ddPdudu, ddPdvdv, and + * ddPdudv arrays with the respective second derivatives. One can + * disable 1) the calculation of the interpolated value by setting P + * to NULL, 2) the calculation of the 1st order derivatives by + * setting dPdu and dPdv to NULL, 3) the calculation of the second + * order derivatives by setting ddPdudu, ddPdvdv, and ddPdudv to + * NULL. The buffers have to be padded at the end such that the last + * element can be read or written safely using SSE instructions. */ +RTCORE_API void rtcInterpolate2(RTCScene scene, unsigned geomID, unsigned primID, float u, float v, RTCBufferType buffer, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, size_t numFloats); + +/*! Interpolates user data to an array of u/v locations. The valid + * pointer points to an integer array that specified which entries in + * the u/v arrays are valid (-1 denotes valid, and 0 invalid). If the + * valid pointer is NULL all elements are considers valid. The data + * buffer specifies per vertex data to interpolate and can be one of + * the RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to + * contain numFloats floating point values to interpolate for each + * vertex of the geometry. The P array will get filled with the + * interpolated data, and the dPdu and dPdv arrays with the u and v + * derivative of the interpolation. If the pointers P is NULL, the + * value will not get calculated. If dPdu and dPdv are NULL the + * derivatives will not get calculated. Both dPdu and dPdv have to be + * either valid or NULL. These destination arrays are filled in + * structure of array (SoA) layout. The buffer has to be padded at + * the end such that the last element can be read safely using SSE + * instructions.*/ +RTCORE_API void rtcInterpolateN(RTCScene scene, unsigned geomID, + const void* valid, const unsigned* primIDs, const float* u, const float* v, size_t numUVs, + RTCBufferType buffer, + float* P, float* dPdu, float* dPdv, size_t numFloats); + +/*! Interpolates user data to an array of u/v locations. The valid + * pointer points to an integer array that specified which entries in + * the u/v arrays are valid (-1 denotes valid, and 0 invalid). If the + * valid pointer is NULL all elements are considers valid. The data + * buffer specifies per vertex data to interpolate and can be one of + * the RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to + * contain numFloats floating point values to interpolate for each + * vertex of the geometry. The P array will get filled with the + * interpolated datam the dPdu and dPdv arrays with the u and v + * derivative of the interpolation, and the ddPdudu, ddPdvdv, and + * ddPdudv arrays with the respective second derivatives. One can + * disable 1) the calculation of the interpolated value by setting P + * to NULL, 2) the calculation of the 1st order derivatives by + * setting dPdu and dPdv to NULL, 3) the calculation of the second + * order derivatives by setting ddPdudu, ddPdvdv, and ddPdudv to + * NULL. These destination arrays are filled in structure of array + * (SoA) layout. The buffer has to be padded at the end such that + * the last element can be read safely using SSE + * instructions. */ +RTCORE_API void rtcInterpolateN2(RTCScene scene, unsigned geomID, + const void* valid, const unsigned* primIDs, const float* u, const float* v, size_t numUVs, + RTCBufferType buffer, + float* P, float* dPdu, float* dPdv, float* ddPdudu, float* ddPdvdv, float* ddPdudv, size_t numFloats); + +/*! \brief Deletes the geometry. */ +RTCORE_API void rtcDeleteGeometry (RTCScene scene, unsigned geomID); + + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_geometry.isph b/src/igl/embree/embree2/rtcore_geometry.isph new file mode 100644 index 000000000..b2fa68cfc --- /dev/null +++ b/src/igl/embree/embree2/rtcore_geometry.isph @@ -0,0 +1,405 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_GEOMETRY_ISPH__ +#define __RTCORE_GEOMETRY_ISPH__ + +/*! \ingroup embree_kernel_api_ispc */ +/*! \{ */ + +/*! invalid geometry ID */ +#define RTC_INVALID_GEOMETRY_ID ((uniform unsigned int)-1) + +/*! \brief Specifies the type of buffers when mapping buffers */ +enum RTCBufferType { + RTC_INDEX_BUFFER = 0x01000000, + + RTC_VERTEX_BUFFER = 0x02000000, + RTC_VERTEX_BUFFER0 = 0x02000000, + RTC_VERTEX_BUFFER1 = 0x02000001, + + RTC_USER_VERTEX_BUFFER = 0x02100000, + RTC_USER_VERTEX_BUFFER0 = 0x02100000, + RTC_USER_VERTEX_BUFFER1 = 0x02100001, + + RTC_FACE_BUFFER = 0x03000000, + RTC_LEVEL_BUFFER = 0x04000001, + + RTC_EDGE_CREASE_INDEX_BUFFER = 0x05000000, + RTC_EDGE_CREASE_WEIGHT_BUFFER = 0x06000000, + + RTC_VERTEX_CREASE_INDEX_BUFFER = 0x07000000, + RTC_VERTEX_CREASE_WEIGHT_BUFFER = 0x08000000, + + RTC_HOLE_BUFFER = 0x09000001, +}; + +/*! \brief Supported types of matrix layout for functions involving matrices */ +enum RTCMatrixType { + RTC_MATRIX_ROW_MAJOR = 0, + RTC_MATRIX_COLUMN_MAJOR = 1, + RTC_MATRIX_COLUMN_MAJOR_ALIGNED16 = 2, +}; + +/*! \brief Supported geometry flags to specify handling in dynamic scenes. */ +enum RTCGeometryFlags +{ + RTC_GEOMETRY_STATIC = 0, //!< specifies static geometry that will change rarely + RTC_GEOMETRY_DEFORMABLE = 1, //!< specifies dynamic geometry with deformable motion (BVH refit possible) + RTC_GEOMETRY_DYNAMIC = 2, //!< specifies dynamic geometry with arbitrary motion (BVH refit not possible) +}; + +/*! \brief Boundary interpolation mode for subdivision surfaces */ +enum RTCBoundaryMode +{ + RTC_BOUNDARY_NONE = 0, //!< ignores border patches + RTC_BOUNDARY_EDGE_ONLY = 1, //!< soft boundary (default) + RTC_BOUNDARY_EDGE_AND_CORNER = 2 //!< boundary corner vertices are sharp vertices +}; + +/*! Intersection filter function for uniform rays. */ +typedef void (*uniform RTCFilterFuncUniform)(void* uniform ptr, /*!< pointer to user data */ + uniform RTCRay1& ray /*!< intersection to filter */); + +/*! Intersection filter function for varying rays. */ +typedef void (*uniform RTCFilterFuncVarying)(void* uniform ptr, /*!< pointer to user data */ + varying RTCRay& ray /*!< intersection to filter */); + + +/*! \brief Creates a new scene instance. + + A scene instance contains a reference to a scene to instantiate and + the transformation to instantiate the scene with. An implementation + will typically transform the ray with the inverse of the provided + transformation and continue traversing the ray through the provided + scene. If any geometry is hit, the instance ID (instID) member of + the ray will get set to the geometry ID of the instance. */ +uniform unsigned int rtcNewInstance (RTCScene target, //!< the scene the instance belongs to + RTCScene source //!< the geometry to instantiate + ); + +/*! \brief Creates a new scene instance. + + A scene instance contains a reference to a scene to instantiate and + the transformation to instantiate the scene with. For motion blurred + instances, a number of timesteps can get specified (currently only 1 + or 2 timesteps are supported). An implementation will typically + transform the ray with the inverse of the provided transformation + and continue traversing the ray through the provided scene. If any + geometry is hit, the instance ID (instID) member of the ray will get + set to the geometry ID of the instance. */ +uniform unsigned rtcNewInstance2 (RTCScene target, //!< the scene the instance belongs to + RTCScene source, //!< the scene to instantiate + uniform size_t numTimeSteps = 1); //!< number of timesteps, one matrix per timestep + + +/*! \brief Sets transformation of the instance */ +void rtcSetTransform (RTCScene scene, //!< scene handle + uniform unsigned int geomID, //!< ID of geometry + uniform RTCMatrixType layout, //!< layout of transformation matrix + const uniform float* uniform xfm //!< pointer to transformation matrix + ); + +/*! \brief Sets transformation of the instance for specified timestep */ +void rtcSetTransform2 (RTCScene scene, //!< scene handle + uniform unsigned int geomID, //!< ID of geometry + uniform RTCMatrixType layout, //!< layout of transformation matrix + const uniform float* uniform xfm, //!< pointer to transformation matrix + uniform size_t timeStep = 0 //!< timestep to set the matrix for + ); + +/*! \brief Creates a new triangle mesh. The number of triangles + (numTriangles), number of vertices (numVertices), and number of time + steps (1 for normal meshes, and 2 for linear motion blur), have to + get specified. The triangle indices can be set be mapping and + writing to the index buffer (RTC_INDEX_BUFFER) and the triangle + vertices can be set by mapping and writing into the vertex buffer + (RTC_VERTEX_BUFFER). In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of + three 32 bit integer indices for each triangle. An index points to + the ith vertex. The vertex buffer stores single precision x,y,z + floating point coordinates aligned to 16 bytes. The value of the 4th + float used for alignment can be arbitrary. */ +uniform unsigned int rtcNewTriangleMesh (RTCScene scene, //!< the scene the mesh belongs to + uniform RTCGeometryFlags flags, //!< geometry flags + uniform size_t numTriangles, //!< number of triangles + uniform size_t numVertices, //!< number of vertices + uniform size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Creates a new quad mesh. The number of quads + (numQuads), number of vertices (numVertices), and number of time + steps (1 for normal meshes, and 2 for linear motion blur), have to + get specified. The quad indices can be set be mapping and + writing to the index buffer (RTC_INDEX_BUFFER) and the quad + vertices can be set by mapping and writing into the vertex buffer + (RTC_VERTEX_BUFFER). In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of + three 32 bit integer indices for each quad. An index points to + the ith vertex. The vertex buffer stores single precision x,y,z + floating point coordinates aligned to 16 bytes. The value of the 4th + float used for alignment can be arbitrary. */ +uniform unsigned int rtcNewQuadMesh (RTCScene scene, //!< the scene the mesh belongs to + uniform RTCGeometryFlags flags, //!< geometry flags + uniform size_t numQuads, //!< number of quads + uniform size_t numVertices, //!< number of vertices + uniform size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Creates a new subdivision mesh. The number of faces + (numFaces), edges/indices (numEdges), vertices (numVertices), edge + creases (numEdgeCreases), vertex creases (numVertexCreases), holes + (numHoles), and time steps (numTimeSteps) have to get speficied at + construction time. + + The following buffers have to get filled by the application: the face + buffer (RTC_FACE_BUFFER) contains the number edges/indices (3 or 4) + of each of the numFaces faces, the index buffer (RTC_INDEX_BUFFER) + contains multiple (3 or 4) 32bit vertex indices for each face and + numEdges indices in total, the vertex buffer (RTC_VERTEX_BUFFER) + stores numVertices vertices as single precision x,y,z floating point + coordinates aligned to 16 bytes. The value of the 4th float used for + alignment can be arbitrary. + + Optionally, the application can fill the hole buffer + (RTC_HOLE_BUFFER) with numHoles many 32 bit indices of faces that + should be considered non-existing. + + Optionally, the application can fill the level buffer + (RTC_LEVEL_BUFFER) with a tessellation level for each of the numEdges + edges. The subdivision level is a positive floating point value, that + specifies how many quads along the edge should get generated during + tessellation. The tessellation level is a lower bound, thus the + implementation is free to choose a larger level. If no level buffer + is specified a level of 1 is used. + + Optionally, the application can fill the sparse edge crease buffers + to make some edges appear sharper. The edge crease index buffer + (RTC_EDGE_CREASE_INDEX_BUFFER) contains numEdgeCreases many pairs of + 32 bit vertex indices that specify unoriented edges. The edge crease + weight buffer (RTC_EDGE_CREASE_WEIGHT_BUFFER) stores for each of + theses crease edges a positive floating point weight. The larger this + weight, the sharper the edge. Specifying a weight of infinify is + supported and marks an edge as infinitely sharp. Storing an edge + multiple times with the same crease weight is allowed, but has lower + performance. Storing the an edge multiple times with different + crease weights results in undefined behaviour. For a stored edge + (i,j), the reverse direction edges (j,i) does not have to get stored, + as both are considered the same edge. + + Optionally, the application can fill the sparse vertex crease buffers + to make some vertices appear sharper. The vertex crease index buffer + (RTC_VERTEX_CREASE_INDEX_BUFFER), contains numVertexCreases many 32 + bit vertex indices to speficy a set of vertices. The vertex crease + weight buffer (RTC_VERTEX_CREASE_WEIGHT_BUFFER) specifies for each of + these vertices a positive floating point weight. The larger this + weight, the sharper the vertex. Specifying a weight of infinity is + supported and makes the vertex infinitely sharp. Storing a vertex + multiple times with the same crease weight is allowed, but has lower + performance. Storing a vertex multiple times with different crease + weights results in undefined behaviour. + +*/ + +uniform unsigned int rtcNewSubdivisionMesh (RTCScene scene, //!< the scene the mesh belongs to + uniform RTCGeometryFlags flags, //!< geometry flags + uniform size_t numFaces, //!< number of faces + uniform size_t numEdges, //!< number of edges + uniform size_t numVertices, //!< number of vertices + uniform size_t numEdgeCreases, //!< number of edge creases + uniform size_t numVertexCreases, //!< number of vertex creases + uniform size_t numHoles, //!< number of holes + uniform size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Creates a new hair geometry, consisting of multiple hairs + represented as cubic bezier curves with varying radii. The number of + curves (numCurves), number of vertices (numVertices), and number of + time steps (1 for normal curves, and 2 for linear motion blur), have + to get specified at construction time. Further, the curve index + buffer (RTC_INDEX_BUFFER) and the curve vertex buffer + (RTC_VERTEX_BUFFER) have to get set by mapping and writing to the + appropiate buffers. In case of linear motion blur, two vertex + buffers have to get filled (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), + one for each time step. The index buffer has the default layout of a + single 32 bit integer index for each curve, that references the + start vertex of the curve. The vertex buffer stores 4 control points + per curve, each such control point consists of a single precision + (x,y,z) position and radius, stored in that order in + memory. Individual hairs are considered to be subpixel sized which + allows the implementation to approximate the intersection + calculation. This in particular means that zooming onto one hair + might show geometric artefacts. */ +uniform unsigned int rtcNewHairGeometry (RTCScene scene, //!< the scene the curves belong to + uniform RTCGeometryFlags flags, //!< geometry flags + uniform size_t numCurves, //!< number of curves + uniform size_t numVertices, //!< number of vertices + uniform size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! Sets a uniform tessellation rate for subdiv meshes and hair + * geometry. For subdivision meshes the RTC_LEVEL_BUFFER can also be used + * optionally to set a different tessellation rate per edge.*/ +void rtcSetTessellationRate (RTCScene scene, uniform unsigned geomID, uniform float tessellationRate); + +/*! \brief Creates a new line segment geometry, consisting of multiple + segments with varying radii. The number of line segments (numSegments), + number of vertices (numVertices), and number of time steps (1 for + normal line segments, and 2 for linear motion blur), have to get + specified at construction time. Further, the segment index buffer + (RTC_INDEX_BUFFER) and the segment vertex buffer (RTC_VERTEX_BUFFER) + have to get set by mapping and writing to the appropiate buffers. In + case of linear motion blur, two vertex buffers have to get filled + (RTC_VERTEX_BUFFER0, RTC_VERTEX_BUFFER1), one for each time step. The + index buffer has the default layout of a single 32 bit integer index + for each line segment, that references the start vertex of the segment. + The vertex buffer stores 2 end points per line segment, each such point + consists of a single precision (x,y,z) position and radius, stored in + that order in memory. Individual segments are considered to be subpixel + sized which allows the implementation to approximate the intersection + calculation. This in particular means that zooming onto one line segment + might show geometric artefacts. */ +uniform unsigned int rtcNewLineSegments (RTCScene scene, //!< the scene the line segments belong to + uniform RTCGeometryFlags flags, //!< geometry flags + uniform size_t numSegments, //!< number of line segments + uniform size_t numVertices, //!< number of vertices + uniform size_t numTimeSteps = 1 //!< number of motion blur time steps + ); + +/*! \brief Sets 32 bit ray mask. */ +void rtcSetMask (RTCScene scene, uniform unsigned int geomID, uniform int mask); + +/*! \brief Sets boundary interpolation mode for subdivision surfaces */ +void rtcSetBoundaryMode(RTCScene scene, uniform unsigned int geomID, uniform RTCBoundaryMode mode); + +/*! \brief Maps specified buffer. This function can be used to set index and + * vertex buffers of geometries. */ +void* uniform rtcMapBuffer(RTCScene scene, uniform unsigned int geomID, uniform RTCBufferType type); + +/*! \brief Unmaps specified buffer. + + A buffer has to be unmapped before the rtcEnable, rtcDisable, + rtcUpdate, or rtcDeleteGeometry calls are executed. */ +void rtcUnmapBuffer(RTCScene scene, uniform unsigned int geomID, uniform RTCBufferType type); + +/*! \brief Shares a data buffer between the application and + * Embree. The passed buffer is used by Embree to store index and + * vertex data. It has to remain valid as long as the mesh exists, + * and the user is responsible to free the data when the mesh gets + * deleted. One can optionally speficy a byte offset and byte stride + * of the elements stored inside the buffer. The addresses + * ptr+offset+i*stride have to be aligned to 4 bytes on Xeon CPUs and + * 16 bytes on Xeon Phi accelerators. For vertex buffers, the 4 bytes + * after the z-coordinate of the last vertex have to be readable memory, + * thus padding is required for some layouts. If this function is not + * called, Embree will allocate and manage buffers of the default + * layout. */ +void rtcSetBuffer(RTCScene scene, uniform unsigned int geomID, uniform RTCBufferType type, + const void* uniform ptr, uniform size_t byteOffset, uniform size_t byteStride); + +/*! \brief Enable geometry. Enabled geometry can be hit by a ray. */ +void rtcEnable (RTCScene scene, uniform unsigned int geomID); + +/*! \brief Update spefific geometry buffer. + + Each time geometry buffers got modified, the user has to call some + update function to tell the ray tracing engine which buffers got + modified. The rtcUpdateBuffer function taggs a specific buffer of + some geometry as modified. */ +void rtcUpdate (RTCScene scene, uniform unsigned int geomID); + +/*! \brief Update spefific geometry buffer. + + Each time geometry buffers got modified, the user has to call some + update function to tell the ray tracing engine which buffers got + modified. The rtcUpdateBuffer function taggs a specific buffer of + some geometry as modified. */ +void rtcUpdateBuffer (RTCScene scene, uniform unsigned int geomID, uniform RTCBufferType type); + +/*! \brief Disable geometry. + + Disabled geometry is not hit by any ray. Disabling and enabling + geometry gives higher performance than deleting and recreating + geometry. */ +void rtcDisable (RTCScene scene, uniform unsigned int geomID); + +/*! \brief Sets the intersection filter function for uniform rays. */ +void rtcSetIntersectionFilterFunction1 (RTCScene scene, uniform unsigned int geomID, uniform RTCFilterFuncUniform func); + +/*! \brief Sets the intersection filter function for varying rays. */ +void rtcSetIntersectionFilterFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCFilterFuncVarying func); + +/*! \brief Sets the occlusion filter function for uniform rays. */ +void rtcSetOcclusionFilterFunction1 (RTCScene scene, uniform unsigned int geomID, uniform RTCFilterFuncUniform func); + +/*! \brief Sets the occlusion filter function for varying rays. */ +void rtcSetOcclusionFilterFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCFilterFuncVarying func); + +/*! Set pointer for user defined data per geometry. Invokations + * of the various user intersect and occluded functions get passed + * this data pointer when called. */ +void rtcSetUserData (RTCScene scene, uniform unsigned int geomID, void* uniform ptr); + +/*! Get pointer for user defined data per geometry based on geomID. */ +void* uniform rtcGetUserData (RTCScene scene, uniform unsigned int geomID); + +/*! Interpolates user data to some varying u/v location. The data + * buffer specifies per vertex data to interpolate and can be one of + * the RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to contain + * numFloats floating point values to interpolate for each vertex of + * the geometry. The P array will get filled with the interpolated + * data, and the dPdu and dPdv arrays with the u and v derivative of + * the interpolation. If the pointers P is NULL, the value will not + * get calculated. If dPdu and dPdv are NULL the derivatives will not + * get calculated. Both dPdu and dPdv have to be either valid or + * NULL. These destination arrays are filled in structure of array + * (SoA) layout. The buffer has to be padded at the end such + * that the last element can be read safely using SSE + * instructions. */ +void rtcInterpolate(RTCScene scene, uniform unsigned int geomID, varying unsigned int primIDs, varying float u, varying float v, + uniform RTCBufferType buffer, + varying float* uniform P, varying float* uniform dPdu, varying float* uniform dPdv, uniform size_t numFloats); + +/*! Interpolates user data to some varying u/v location. The data + * buffer specifies per vertex data to interpolate and can be one of + * the RTC_VERTEX_BUFFER0/1 or RTC_USER_VERTEX_BUFFER0/1 and has to contain + * numFloats floating point values to interpolate for each vertex of + * the geometry. The P array will get filled with the + * interpolated datam the dPdu and dPdv arrays with the u and v + * derivative of the interpolation, and the ddPdudu, ddPdvdv, and + * ddPdudv arrays with the respective second derivatives. One can + * disable 1) the calculation of the interpolated value by setting P + * to NULL, 2) the calculation of the 1st order derivatives by + * setting dPdu and dPdv to NULL, 3) the calculation of the second + * order derivatives by setting ddPdudu, ddPdvdv, and ddPdudv to + * NULL. These destination arrays are filled in structure of array + * (SoA) layout. The buffer has to be padded at the end such that + * the last element can be read safely using SSE + * instructions. */ +void rtcInterpolate2(RTCScene scene, uniform unsigned int geomID, varying unsigned int primIDs, varying float u, varying float v, + uniform RTCBufferType buffer, + varying float* uniform P, varying float* uniform dPdu, varying float* uniform dPdv, + varying float* uniform ddPdudu, varying float* uniform ddPdvdv, varying float* uniform ddPdudv, + uniform size_t numFloats); + +/*! \brief Deletes the geometry. */ +void rtcDeleteGeometry (RTCScene scene, uniform unsigned int geomID); + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_geometry_user.h b/src/igl/embree/embree2/rtcore_geometry_user.h new file mode 100644 index 000000000..e4b4d4b8a --- /dev/null +++ b/src/igl/embree/embree2/rtcore_geometry_user.h @@ -0,0 +1,154 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_USER_GEOMETRY_H__ +#define __RTCORE_USER_GEOMETRY_H__ + +/*! \ingroup embree_kernel_api */ +/*! \{ */ + +/*! Type of bounding function. */ +typedef void (*RTCBoundsFunc)(void* ptr, /*!< pointer to user data */ + size_t item, /*!< item to calculate bounds for */ + RTCBounds& bounds_o /*!< returns calculated bounds */); + +/*! Type of bounding function. */ +typedef void (*RTCBoundsFunc2)(void* userPtr, /*!< pointer to user data */ + void* geomUserPtr, /*!< pointer to geometry user data */ + size_t item, /*!< item to calculate bounds for */ + RTCBounds* bounds_o /*!< returns calculated bounds */); + +/*! Type of intersect function pointer for single rays. */ +typedef void (*RTCIntersectFunc)(void* ptr, /*!< pointer to user data */ + RTCRay& ray, /*!< ray to intersect */ + size_t item /*!< item to intersect */); + +/*! Type of intersect function pointer for ray packets of size 4. */ +typedef void (*RTCIntersectFunc4)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay4& ray, /*!< ray packet to intersect */ + size_t item /*!< item to intersect */); + +/*! Type of intersect function pointer for ray packets of size 8. */ +typedef void (*RTCIntersectFunc8)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay8& ray, /*!< ray packet to intersect */ + size_t item /*!< item to intersect */); + +/*! Type of intersect function pointer for ray packets of size 16. */ +typedef void (*RTCIntersectFunc16)(const void* valid, /*!< pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay16& ray, /*!< ray packet to intersect */ + size_t item /*!< item to intersect */); + +/*! Type of occlusion function pointer for single rays. */ +typedef void (*RTCOccludedFunc) (void* ptr, /*!< pointer to user data */ + RTCRay& ray, /*!< ray to test occlusion */ + size_t item /*!< item to test for occlusion */); + +/*! Type of occlusion function pointer for ray packets of size 4. */ +typedef void (*RTCOccludedFunc4) (const void* valid, /*! pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay4& ray, /*!< Ray packet to test occlusion. */ + size_t item /*!< item to test for occlusion */); + +/*! Type of occlusion function pointer for ray packets of size 8. */ +typedef void (*RTCOccludedFunc8) (const void* valid, /*! pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay8& ray, /*!< Ray packet to test occlusion. */ + size_t item /*!< item to test for occlusion */); + +/*! Type of occlusion function pointer for ray packets of size 16. */ +typedef void (*RTCOccludedFunc16) (const void* valid, /*! pointer to valid mask */ + void* ptr, /*!< pointer to user data */ + RTCRay16& ray, /*!< Ray packet to test occlusion. */ + size_t item /*!< item to test for occlusion */); + +/*! Creates a new user geometry object. This feature makes it possible + * to add arbitrary types of geometry to the scene by providing + * appropiate bounding, intersect and occluded functions. A user + * geometry object is a set of user geometries. As the rtcIntersect + * and rtcOccluded functions support different ray packet sizes, the + * user also has to provide different versions of intersect and + * occluded function pointers for these packet sizes. However, the + * ray packet size of the called function pointer always matches the + * packet size of the originally invoked rtcIntersect and rtcOccluded + * functions. A user data pointer, that points to a user specified + * representation of the geometry, is passed to each intersect and + * occluded function invokation, as well as the index of the geometry + * of the set to intersect. */ +RTCORE_API unsigned rtcNewUserGeometry (RTCScene scene, /*!< the scene the user geometry set is created in */ + size_t numGeometries /*!< the number of geometries contained in the set */); + +RTCORE_API unsigned rtcNewUserGeometry2 (RTCScene scene, /*!< the scene the user geometry set is created in */ + size_t numGeometries, /*!< the number of geometries contained in the set */ + size_t numTimeSteps = 1 /*!< number of motion blur time steps */); + +/*! Sets the bounding function to calculate bounding boxes of the user + * geometry items when building spatial index structures. The + * calculated bounding box have to be conservative and should be + * tight. */ +RTCORE_API void rtcSetBoundsFunction (RTCScene scene, unsigned geomID, RTCBoundsFunc bounds); + +/*! Sets the bounding function to calculate bounding boxes of the user + * geometry items when building spatial index structures. The + * calculated bounding box have to be conservative and should be + * tight. */ +RTCORE_API void rtcSetBoundsFunction2 (RTCScene scene, unsigned geomID, RTCBoundsFunc2 bounds, void* userPtr); + +/*! Set intersect function for single rays. The rtcIntersect function + * will call the passed function for intersecting the user + * geometry. */ +RTCORE_API void rtcSetIntersectFunction (RTCScene scene, unsigned geomID, RTCIntersectFunc intersect); + +/*! Set intersect function for ray packets of size 4. The + * rtcIntersect4 function will call the passed function for + * intersecting the user geometry. */ +RTCORE_API void rtcSetIntersectFunction4 (RTCScene scene, unsigned geomID, RTCIntersectFunc4 intersect4); + +/*! Set intersect function for ray packets of size 8. The + * rtcIntersect8 function will call the passed function for + * intersecting the user geometry.*/ +RTCORE_API void rtcSetIntersectFunction8 (RTCScene scene, unsigned geomID, RTCIntersectFunc8 intersect8); + +/*! Set intersect function for ray packets of size 16. The + * rtcIntersect16 function will call the passed function for + * intersecting the user geometry. */ +RTCORE_API void rtcSetIntersectFunction16 (RTCScene scene, unsigned geomID, RTCIntersectFunc16 intersect16); + +/*! Set occlusion function for single rays. The rtcOccluded function + * will call the passed function for intersecting the user + * geometry. */ +RTCORE_API void rtcSetOccludedFunction (RTCScene scene, unsigned geomID, RTCOccludedFunc occluded); + +/*! Set occlusion function for ray packets of size 4. The rtcOccluded4 + * function will call the passed function for intersecting the user + * geometry. */ +RTCORE_API void rtcSetOccludedFunction4 (RTCScene scene, unsigned geomID, RTCOccludedFunc4 occluded4); + +/*! Set occlusion function for ray packets of size 8. The rtcOccluded8 + * function will call the passed function for intersecting the user + * geometry. */ +RTCORE_API void rtcSetOccludedFunction8 (RTCScene scene, unsigned geomID, RTCOccludedFunc8 occluded8); + +/*! Set occlusion function for ray packets of size 16. The + * rtcOccluded16 function will call the passed function for + * intersecting the user geometry. */ +RTCORE_API void rtcSetOccludedFunction16 (RTCScene scene, unsigned geomID, RTCOccludedFunc16 occluded16); + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_geometry_user.isph b/src/igl/embree/embree2/rtcore_geometry_user.isph new file mode 100644 index 000000000..d89ec1b91 --- /dev/null +++ b/src/igl/embree/embree2/rtcore_geometry_user.isph @@ -0,0 +1,128 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_USER_GEOMETRY_ISPH__ +#define __RTCORE_USER_GEOMETRY_ISPH__ + +/*! \ingroup embree_kernel_api_ispc */ +/*! \{ */ + +/*! Type of bounding function. */ +typedef void (*RTCBoundsFunc)(void* uniform ptr, /*!< pointer to user data */ + uniform size_t item, /*!< item to calculate bounds for */ + uniform RTCBounds& bounds_o /*!< returns calculated bounds */); + +/*! Type of bounding function. */ +typedef void (*RTCBoundsFunc2)(void* uniform userPtr, /*!< pointer to user data */ + void* uniform geomUserPtr, /*!< pointer to geometry user data */ + uniform size_t item, /*!< item to calculate bounds for */ + RTCBounds* uniform bounds_o /*!< returns calculated bounds */); + +/*! Type of intersect function pointer for uniform rays. */ +typedef void (*RTCIntersectFuncUniform)(void* uniform ptr, /*!< pointer to user data */ + uniform RTCRay1& ray, /*!< ray to intersect */ + uniform size_t item /*< item to intersect */); + +/*! Type of intersect function pointer for varying rays. */ +typedef void (*RTCIntersectFuncVarying)(void* uniform ptr, /*!< pointer to user data */ + varying RTCRay& ray, /*!< ray to intersect */ + uniform size_t item /*< item to intersect */); + +/*! Type of occlusion function pointer for uniform rays. */ +typedef void (*RTCOccludedFuncUniform) (void* uniform ptr, /*!< pointer to user data */ + uniform RTCRay1& ray, /*!< ray to test occlusion */ + uniform size_t item /*< item to test for occlusion */); + + +/*! Type of occlusion function pointer for varying rays. */ +typedef void (*RTCOccludedFuncVarying) (void* uniform ptr, /*!< pointer to user data */ + varying RTCRay& ray, /*!< ray to test occlusion */ + uniform size_t item /*< item to test for occlusion */); + + +typedef void (*RTCDisplacementFunc)(void* uniform ptr, /*!< pointer to user data of geometry */ + uniform unsigned int geomID, /*!< ID of geometry to displace */ + uniform unsigned int primID, /*!< ID of primitive of geometry to displace */ + uniform const float* uniform u, /*!< u coordinates (source) */ + uniform const float* uniform v, /*!< v coordinates (source) */ + uniform const float* uniform nx, /*!< x coordinates of normal at point to displace (source) */ + uniform const float* uniform ny, /*!< y coordinates of normal at point to displace (source) */ + uniform const float* uniform nz, /*!< z coordinates of normal at point to displace (source) */ + uniform float* uniform px, /*!< x coordinates of points to displace (source and target) */ + uniform float* uniform py, /*!< y coordinates of points to displace (source and target) */ + uniform float* uniform pz, /*!< z coordinates of points to displace (source and target) */ + uniform size_t N /*!< number of points to displace */ ); + + +/*! Creates a new user geometry object. This feature makes it possible + * to add arbitrary types of geometry to the scene by providing + * appropiate intersect and occluded functions, as well as a bounding + * box of the implemented geometry. As the rtcIntersect and + * rtcOccluded functions support different ray packet sizes, the user + * also has to provide different versions of intersect and occluded + * function pointers for the different packet sized. However, only + * rtcIntersect and rtcOccluded functions of specific packet sizes + * are called, it is sufficient to provide only the corresponding + * function pointer for the user geometry. However, the functions + * provided have to intersect the same geometry. A user data pointer, + * that points to a user specified representation of the geometry, is + * passed to each intersect and occluded function invokation. */ +uniform unsigned int rtcNewUserGeometry (RTCScene scene, /*!< the scene the user geometry set is created in */ + uniform size_t numGeometries /*!< the number of geometries contained in the set */); + +uniform unsigned int rtcNewUserGeometry2 (RTCScene scene, /*!< the scene the user geometry set is created in */ + uniform size_t numGeometries, /*!< the number of geometries contained in the set */ + uniform size_t numTimeSteps = 1 /*!< number of motion blur time steps */); + +/*! Sets the bounding function to calculate bounding boxes of the user + * geometry items when building spatial index structures. The + * calculated bounding box have to be conservative and should be + * tight.*/ +void rtcSetBoundsFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCBoundsFunc bounds); + +/*! Sets the bounding function to calculate bounding boxes of the user + * geometry items when building spatial index structures. The + * calculated bounding box have to be conservative and should be + * tight.*/ +void rtcSetBoundsFunction2 (RTCScene scene, uniform unsigned int geomID, uniform RTCBoundsFunc2 bounds, void* uniform userPtr); + +/*! Set intersect function for uniform rays. The rtcIntersect1 + * function will call the passed function for intersecting the user + * geometry. */ +void rtcSetIntersectFunction1 (RTCScene scene, uniform unsigned int geomID, uniform RTCIntersectFuncUniform intersect); + +/*! Set intersect function for varying rays. The rtcIntersect function + * will call the passed function for intersecting the user + * geometry. */ +void rtcSetIntersectFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCIntersectFuncVarying intersect); + +/*! Set occlusion function for uniform rays. The rtcOccluded1 function + * will call the passed function for intersecting the user + * geometry. */ +void rtcSetOccludedFunction1 (RTCScene scene, uniform unsigned int geomID, uniform RTCOccludedFuncUniform occluded); + +/*! Set occlusion function for varying rays. The rtcOccluded function + * will call the passed function for intersecting the user + * geometry. */ +void rtcSetOccludedFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCOccludedFuncVarying occluded); + + +/*! \brief Sets the displacement function. */ +void rtcSetDisplacementFunction (RTCScene scene, uniform unsigned int geomID, uniform RTCDisplacementFunc func, uniform RTCBounds *uniform bounds); + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_ray.h b/src/igl/embree/embree2/rtcore_ray.h new file mode 100644 index 000000000..f20b11b5f --- /dev/null +++ b/src/igl/embree/embree2/rtcore_ray.h @@ -0,0 +1,195 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_RAY_H__ +#define __RTCORE_RAY_H__ + +/*! \ingroup embree_kernel_api */ +/*! \{ */ + +/*! \brief Ray structure for an individual ray */ +struct RTCORE_ALIGN(16) RTCRay +{ + /* ray data */ +public: + float org[3]; //!< Ray origin + float align0; + + float dir[3]; //!< Ray direction + float align1; + + float tnear; //!< Start of ray segment + float tfar; //!< End of ray segment (set to hit distance) + + float time; //!< Time of this ray for motion blur + unsigned mask; //!< Used to mask out objects during traversal + + /* hit data */ +public: + float Ng[3]; //!< Unnormalized geometry normal + float align2; + + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + + unsigned geomID; //!< geometry ID + unsigned primID; //!< primitive ID + unsigned instID; //!< instance ID +}; + +/*! Ray structure for packets of 4 rays. */ +struct RTCORE_ALIGN(16) RTCRay4 +{ + /* ray data */ +public: + float orgx[4]; //!< x coordinate of ray origin + float orgy[4]; //!< y coordinate of ray origin + float orgz[4]; //!< z coordinate of ray origin + + float dirx[4]; //!< x coordinate of ray direction + float diry[4]; //!< y coordinate of ray direction + float dirz[4]; //!< z coordinate of ray direction + + float tnear[4]; //!< Start of ray segment + float tfar[4]; //!< End of ray segment (set to hit distance) + + float time[4]; //!< Time of this ray for motion blur + unsigned mask[4]; //!< Used to mask out objects during traversal + + /* hit data */ +public: + float Ngx[4]; //!< x coordinate of geometry normal + float Ngy[4]; //!< y coordinate of geometry normal + float Ngz[4]; //!< z coordinate of geometry normal + + float u[4]; //!< Barycentric u coordinate of hit + float v[4]; //!< Barycentric v coordinate of hit + + unsigned geomID[4]; //!< geometry ID + unsigned primID[4]; //!< primitive ID + unsigned instID[4]; //!< instance ID +}; + +/*! Ray structure for packets of 8 rays. */ +struct RTCORE_ALIGN(32) RTCRay8 +{ + /* ray data */ +public: + float orgx[8]; //!< x coordinate of ray origin + float orgy[8]; //!< y coordinate of ray origin + float orgz[8]; //!< z coordinate of ray origin + + float dirx[8]; //!< x coordinate of ray direction + float diry[8]; //!< y coordinate of ray direction + float dirz[8]; //!< z coordinate of ray direction + + float tnear[8]; //!< Start of ray segment + float tfar[8]; //!< End of ray segment (set to hit distance) + + float time[8]; //!< Time of this ray for motion blur + unsigned mask[8]; //!< Used to mask out objects during traversal + + /* hit data */ +public: + float Ngx[8]; //!< x coordinate of geometry normal + float Ngy[8]; //!< y coordinate of geometry normal + float Ngz[8]; //!< z coordinate of geometry normal + + float u[8]; //!< Barycentric u coordinate of hit + float v[8]; //!< Barycentric v coordinate of hit + + unsigned geomID[8]; //!< geometry ID + unsigned primID[8]; //!< primitive ID + unsigned instID[8]; //!< instance ID +}; + +/*! \brief Ray structure for packets of 16 rays. */ +struct RTCORE_ALIGN(64) RTCRay16 +{ + /* ray data */ +public: + float orgx[16]; //!< x coordinate of ray origin + float orgy[16]; //!< y coordinate of ray origin + float orgz[16]; //!< z coordinate of ray origin + + float dirx[16]; //!< x coordinate of ray direction + float diry[16]; //!< y coordinate of ray direction + float dirz[16]; //!< z coordinate of ray direction + + float tnear[16]; //!< Start of ray segment + float tfar[16]; //!< End of ray segment (set to hit distance) + + float time[16]; //!< Time of this ray for motion blur + unsigned mask[16]; //!< Used to mask out objects during traversal + + /* hit data */ +public: + float Ngx[16]; //!< x coordinate of geometry normal + float Ngy[16]; //!< y coordinate of geometry normal + float Ngz[16]; //!< z coordinate of geometry normal + + float u[16]; //!< Barycentric u coordinate of hit + float v[16]; //!< Barycentric v coordinate of hit + + unsigned geomID[16]; //!< geometry ID + unsigned primID[16]; //!< primitive ID + unsigned instID[16]; //!< instance ID +}; + + +/*! \brief Ray structure template for packets of N rays in SOA layout. */ +struct RTCRaySOA +{ + /* ray data */ +public: + + float* orgx; //!< x coordinate of ray origin + float* orgy; //!< y coordinate of ray origin + float* orgz; //!< z coordinate of ray origin + + float* dirx; //!< x coordinate of ray direction + float* diry; //!< y coordinate of ray direction + float* dirz; //!< z coordinate of ray direction + + float* tnear; //!< Start of ray segment (optional) + float* tfar; //!< End of ray segment (set to hit distance) + + + float* time; //!< Time of this ray for motion blur (optional) + unsigned* mask; //!< Used to mask out objects during traversal (optional) + + /* hit data */ + +public: + + float* Ngx; //!< x coordinate of geometry normal (optional) + float* Ngy; //!< y coordinate of geometry normal (optional) + float* Ngz; //!< z coordinate of geometry normal (optional) + + + + float* u; //!< Barycentric u coordinate of hit + float* v; //!< Barycentric v coordinate of hit + + + unsigned* geomID; //!< geometry ID + unsigned* primID; //!< primitive ID + unsigned* instID; //!< instance ID (optional) +}; + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_ray.isph b/src/igl/embree/embree2/rtcore_ray.isph new file mode 100644 index 000000000..f1bf4d268 --- /dev/null +++ b/src/igl/embree/embree2/rtcore_ray.isph @@ -0,0 +1,117 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_RAY_ISPH__ +#define __RTCORE_RAY_ISPH__ + +/*! \ingroup embree_kernel_api_ispc */ +/*! \{ */ + +/*! Ray structure for uniform (single) rays. */ +struct RTCRay1 +{ + /* ray data */ + float org[3]; //!< Ray origin + float align0; //!< unused member to force alignment of following members + + float dir[3]; //!< Ray direction + float align1; //!< unused member to force alignment of following members + + float tnear; //!< Start of ray segment + float tfar; //!< End of ray segment (set to hit distance) + float time; //!< Time of this ray for motion blur + unsigned mask; //!< Used to mask out objects during traversal + + /* hit data */ + float Ng[3]; //!< Unnormalized geometry normal + float align2; + + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + + unsigned geomID; //!< geometry ID + unsigned primID; //!< primitive ID + unsigned instID; //!< instance ID + varying unsigned align[0]; //!< aligns ray on stack to at least 16 bytes +}; + +/*! Ray structure for packets of 4 rays. */ +struct RTCRay +{ + /* ray data */ + float orgx; //!< x coordinate of ray origin + float orgy; //!< y coordinate of ray origin + float orgz; //!< z coordinate of ray origin + + float dirx; //!< x coordinate of ray direction + float diry; //!< y coordinate of ray direction + float dirz; //!< z coordinate of ray direction + + float tnear; //!< Start of ray segment + float tfar; //!< End of ray segment + float time; //!< Time of this ray for motion blur + unsigned mask; //!< Used to mask out objects during traversal + + /* hit data */ + float Ngx; //!< x coordinate of geometry normal + float Ngy; //!< y coordinate of geometry normal + float Ngz; //!< z coordinate of geometry normal + + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + + unsigned geomID; //!< geometry ID + unsigned primID; //!< primitive ID + unsigned instID; //!< instance ID +}; + + +struct RTCRaySOA +{ + /* ray data */ + + uniform float* uniform orgx; //!< x coordinate of ray origin + uniform float* uniform orgy; //!< y coordinate of ray origin + uniform float* uniform orgz; //!< z coordinate of ray origin + + uniform float* uniform dirx; //!< x coordinate of ray direction + uniform float* uniform diry; //!< y coordinate of ray direction + uniform float* uniform dirz; //!< z coordinate of ray direction + + uniform float* uniform tnear; //!< Start of ray segment (optional) + uniform float* uniform tfar; //!< End of ray segment (set to hit distance) + + uniform float* uniform time; //!< Time of this ray for motion blur (optional) + uniform unsigned* uniform mask; //!< Used to mask out objects during traversal (optional) + + /* hit data */ + + uniform float* uniform Ngx; //!< x coordinate of geometry normal (optional) + uniform float* uniform Ngy; //!< y coordinate of geometry normal (optional) + uniform float* uniform Ngz; //!< z coordinate of geometry normal (optional) + + uniform float* uniform u; //!< Barycentric u coordinate of hit + uniform float* uniform v; //!< Barycentric v coordinate of hit + + uniform unsigned* uniform geomID; //!< geometry ID + uniform unsigned* uniform primID; //!< primitive ID + uniform unsigned* uniform instID; //!< instance ID (optional) +}; + + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_scene.h b/src/igl/embree/embree2/rtcore_scene.h new file mode 100644 index 000000000..df04e0a2b --- /dev/null +++ b/src/igl/embree/embree2/rtcore_scene.h @@ -0,0 +1,187 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_SCENE_H__ +#define __RTCORE_SCENE_H__ + +/*! \ingroup embree_kernel_api */ +/*! \{ */ + +/*! forward declarations for ray structures */ +struct RTCRay; +struct RTCRay4; +struct RTCRay8; +struct RTCRay16; +struct RTCRaySOA; + +/*! scene flags */ +enum RTCSceneFlags +{ + /* dynamic type flags */ + RTC_SCENE_STATIC = (0 << 0), //!< specifies static scene + RTC_SCENE_DYNAMIC = (1 << 0), //!< specifies dynamic scene + + /* acceleration structure flags */ + RTC_SCENE_COMPACT = (1 << 8), //!< use memory conservative data structures + RTC_SCENE_COHERENT = (1 << 9), //!< optimize data structures for coherent rays + RTC_SCENE_INCOHERENT = (1 << 10), //!< optimize data structures for in-coherent rays (enabled by default) + RTC_SCENE_HIGH_QUALITY = (1 << 11), //!< create higher quality data structures + + /* traversal algorithm flags */ + RTC_SCENE_ROBUST = (1 << 16) //!< use more robust traversal algorithms +}; + +/*! enabled algorithm flags */ +enum RTCAlgorithmFlags +{ + RTC_INTERSECT1 = (1 << 0), //!< enables the rtcIntersect1 and rtcOccluded1 functions for this scene + RTC_INTERSECT4 = (1 << 1), //!< enables the rtcIntersect4 and rtcOccluded4 functions for this scene + RTC_INTERSECT8 = (1 << 2), //!< enables the rtcIntersect8 and rtcOccluded8 functions for this scene + RTC_INTERSECT16 = (1 << 3), //!< enables the rtcIntersect16 and rtcOccluded16 functions for this scene + RTC_INTERPOLATE = (1 << 4), //!< enables the rtcInterpolate function for this scene + + RTC_INTERSECTN = (1 << 5), //!< enables the rtcIntersectN and rtcOccludedN functions for this scene +}; + +/*! layout flags for ray streams */ +enum RTCRayNFlags +{ + RTC_RAYN_DEFAULT = (1 << 0) +}; + + +/*! \brief Defines an opaque scene type */ +typedef struct __RTCScene {}* RTCScene; + +/*! Creates a new scene. + WARNING: This function is deprecated, use rtcDeviceNewScene instead. +*/ +RTCORE_API RTCORE_DEPRECATED RTCScene rtcNewScene (RTCSceneFlags flags, RTCAlgorithmFlags aflags); + +/*! Creates a new scene. */ +RTCORE_API RTCScene rtcDeviceNewScene (RTCDevice device, RTCSceneFlags flags, RTCAlgorithmFlags aflags); + +/*! \brief Type of progress callback function. */ +typedef bool (*RTCProgressMonitorFunc)(void* ptr, const double n); +RTCORE_DEPRECATED typedef RTCProgressMonitorFunc RTC_PROGRESS_MONITOR_FUNCTION; + +/*! \brief Sets the progress callback function which is called during hierarchy build of this scene. */ +RTCORE_API void rtcSetProgressMonitorFunction(RTCScene scene, RTCProgressMonitorFunc func, void* ptr); + +/*! Commits the geometry of the scene. After initializing or modifying + * geometries, commit has to get called before tracing + * rays. */ +RTCORE_API void rtcCommit (RTCScene scene); + +/*! Commits the geometry of the scene. The calling threads will be + * used internally as a worker threads on some implementations. The + * function will wait until 'numThreads' threads have called this + * function and all threads return from the function after the scene + * commit is finished. The application threads will not be used as + * worker threads when the TBB tasking system is enabled (which is + * the default). On CPUs, we recommend also using TBB inside your + * application to share threads. We recommend using the + * rtcCommitThread feature to share threads on the Xeon Phi + * coprocessor. */ +RTCORE_API void rtcCommitThread(RTCScene scene, unsigned int threadID, unsigned int numThreads); + +/*! Returns to AABB of the scene. rtcCommit has to get called + * previously to this function. */ +RTCORE_API void rtcGetBounds(RTCScene scene, RTCBounds& bounds_o); + +/*! Intersects a single ray with the scene. The ray has to be aligned + * to 16 bytes. This function can only be called for scenes with the + * RTC_INTERSECT1 flag set. */ +RTCORE_API void rtcIntersect (RTCScene scene, RTCRay& ray); + +/*! Intersects a packet of 4 rays with the scene. The valid mask and + * ray have both to be aligned to 16 bytes. This function can only be + * called for scenes with the RTC_INTERSECT4 flag set. */ +RTCORE_API void rtcIntersect4 (const void* valid, RTCScene scene, RTCRay4& ray); + +/*! Intersects a packet of 8 rays with the scene. The valid mask and + * ray have both to be aligned to 32 bytes. This function can only be + * called for scenes with the RTC_INTERSECT8 flag set. For performance + * reasons, the rtcIntersect8 function should only get called if the + * CPU supports AVX. */ +RTCORE_API void rtcIntersect8 (const void* valid, RTCScene scene, RTCRay8& ray); + +/*! Intersects a packet of 16 rays with the scene. The valid mask and + * ray have both to be aligned to 64 bytes. This function can only be + * called for scenes with the RTC_INTERSECT16 flag set. For + * performance reasons, the rtcIntersect16 function should only get + * called if the CPU supports the 16-wide SIMD instructions. */ +RTCORE_API void rtcIntersect16 (const void* valid, RTCScene scene, RTCRay16& ray); + +/*! Intersects a stream of N rays in AOS layout with the scene. This + * function can only be called for scenes with the RTC_INTERSECTN + * flag set. The stride specifies the offset between rays in + * bytes. */ +RTCORE_API void rtcIntersectN (RTCScene scene, RTCRay* rayN, const size_t N, const size_t stride, const size_t flags = RTC_RAYN_DEFAULT); + +/*! Intersects one or multiple streams of N rays in compact SOA layout + * with the scene. This function can only be called for scenes with + * the RTC_INTERSECTN flag set. 'streams' specifies the number of + * dense SOA ray streams, and 'stride' the offset in bytes between + * those. */ +RTCORE_API void rtcIntersectN_SOA (RTCScene scene, RTCRaySOA& rayN, const size_t N, const size_t streams, const size_t stride, const size_t flags = RTC_RAYN_DEFAULT); + + +/*! Tests if a single ray is occluded by the scene. The ray has to be + * aligned to 16 bytes. This function can only be called for scenes + * with the RTC_INTERSECT1 flag set. */ +RTCORE_API void rtcOccluded (RTCScene scene, RTCRay& ray); + +/*! Tests if a packet of 4 rays is occluded by the scene. This + * function can only be called for scenes with the RTC_INTERSECT4 + * flag set. The valid mask and ray have both to be aligned to 16 + * bytes. */ +RTCORE_API void rtcOccluded4 (const void* valid, RTCScene scene, RTCRay4& ray); + +/*! Tests if a packet of 8 rays is occluded by the scene. The valid + * mask and ray have both to be aligned to 32 bytes. This function + * can only be called for scenes with the RTC_INTERSECT8 flag + * set. For performance reasons, the rtcOccluded8 function should + * only get called if the CPU supports AVX. */ +RTCORE_API void rtcOccluded8 (const void* valid, RTCScene scene, RTCRay8& ray); + +/*! Tests if a packet of 16 rays is occluded by the scene. The valid + * mask and ray have both to be aligned to 64 bytes. This function + * can only be called for scenes with the RTC_INTERSECT16 flag + * set. For performance reasons, the rtcOccluded16 function should + * only get called if the CPU supports the 16-wide SIMD + * instructions. */ +RTCORE_API void rtcOccluded16 (const void* valid, RTCScene scene, RTCRay16& ray); + +/*! Tests if a stream of N rays on AOS layout is occluded by the + * scene. This function can only be called for scenes with the + * RTC_INTERSECTN flag set. The stride specifies the offset between + * rays in bytes.*/ +RTCORE_API void rtcOccludedN (RTCScene scene, RTCRay* rayN, const size_t N, const size_t stride, const size_t flags = RTC_RAYN_DEFAULT); + +/*! Intersects one or multiple streams of N rays in compact SOA layout + * with the scene. This function can only be called for scenes with + * the RTC_INTERSECTN flag set. 'streams' specifies the number of + * dense SOA ray streams, and 'stride' the offset in bytes between + * those. */ +RTCORE_API void rtcOccludedN_SOA (RTCScene scene, RTCRaySOA& rayN, const size_t N, const size_t streams, const size_t stride, const size_t flags = RTC_RAYN_DEFAULT); + +/*! Deletes the scene. All contained geometry get also destroyed. */ +RTCORE_API void rtcDeleteScene (RTCScene scene); + +/*! @} */ + +#endif diff --git a/src/igl/embree/embree2/rtcore_scene.isph b/src/igl/embree/embree2/rtcore_scene.isph new file mode 100644 index 000000000..a85eee219 --- /dev/null +++ b/src/igl/embree/embree2/rtcore_scene.isph @@ -0,0 +1,152 @@ +// ======================================================================== // +// Copyright 2009-2015 Intel Corporation // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ======================================================================== // + +#ifndef __RTCORE_SCENE_ISPH__ +#define __RTCORE_SCENE_ISPH__ + +/*! \ingroup embree_kernel_api */ +/*! \{ */ + +/*! forward declarations for ray structures */ +struct RTCRay1; +struct RTCRay; +struct RTCRaySOA; + +/*! scene flags */ +enum RTCSceneFlags +{ + /* dynamic type flags */ + RTC_SCENE_STATIC = (0 << 0), //!< specifies static scene + RTC_SCENE_DYNAMIC = (1 << 0), //!< specifies dynamic scene + + /* acceleration structure flags */ + RTC_SCENE_COMPACT = (1 << 8), //!< use memory conservative data structures + RTC_SCENE_COHERENT = (1 << 9), //!< optimize data structures for coherent rays (enabled by default) + RTC_SCENE_INCOHERENT = (1 << 10), //!< optimize data structures for in-coherent rays + RTC_SCENE_HIGH_QUALITY = (1 << 11), //!< create higher quality data structures + + /* traversal algorithm flags */ + RTC_SCENE_ROBUST = (1 << 16) //!< use more robust traversal algorithms +}; + +/*! enabled algorithm flags */ +enum RTCAlgorithmFlags +{ + RTC_INTERSECT_UNIFORM = (1 << 0), //!< enables the uniform rtcIntersect1 and uniform rtcOccluded1 functions for this scene + RTC_INTERSECT_VARYING = (1 << 1), //!< enables the varying rtcIntersect and varying rtcOccluded functions for this scene + RTC_INTERPOLATE = (1 << 4) //!< enables the rtcInterpolate function for this scene +}; + +/*! layout flags for ray streams */ +enum RTCRayNFlags +{ + RTC_RAYN_DEFAULT = (1 << 0) +}; + + +/*! \brief Defines an opaque scene type */ +typedef uniform struct __RTCScene {}* uniform RTCScene; + +/*! Creates a new scene. + WARNING: This function is deprecated, use rtcDeviceNewScene instead. +*/ +RTCORE_DEPRECATED RTCScene rtcNewScene (uniform RTCSceneFlags flags, uniform RTCAlgorithmFlags aflags); + +/*! Creates a new scene. */ +RTCScene rtcDeviceNewScene (RTCDevice device, uniform RTCSceneFlags flags, uniform RTCAlgorithmFlags aflags); + +/*! \brief Type of progress callback function. */ +typedef uniform bool (*uniform RTC_PROGRESS_MONITOR_FUNCTION)(void* uniform ptr, const uniform double n); + +/*! \brief Sets the progress callback function which is called during hierarchy build. */ +void rtcSetProgressMonitorFunction(RTCScene scene, RTC_PROGRESS_MONITOR_FUNCTION func, void* uniform ptr); + +/*! Commits the geometry of the scene. After initializing or modifying + * geometries, commit has to get called before tracing + * rays. */ +void rtcCommit (RTCScene scene); + +/*! Commits the geometry of the scene. The calling threads will be + * used internally as a worker threads on some implementations. The + * function will wait until 'numThreads' threads have called this + * function and all threads return from the function after the scene + * commit is finished. The application threads will not be used as + * worker threads when the TBB tasking system is enabled (which is + * the default). On CPUs, we recommend also using TBB inside your + * application to share threads. We recommend using the + * rtcCommitThread feature to share threads on the Xeon Phi + * coprocessor. */ +void rtcCommitThread(RTCScene scene, uniform unsigned int threadID, uniform unsigned int numThreads); + +/*! Returns to AABB of the scene. rtcCommit has to get called + * previously to this function. */ +void rtcGetBounds(RTCScene scene, uniform RTCBounds& bounds_o); + +/*! Intersects a uniform ray with the scene. This function can only be + * called for scenes with the RTC_INTERSECT_UNIFORM flag set. The ray + * has to be aligned to 16 bytes. */ +void rtcIntersect1 (RTCScene scene, uniform RTCRay1& ray); + +/*! Intersects a varying ray with the scene. This function can only be + * called for scenes with the RTC_INTERSECT_VARYING flag set. The + * valid mask and ray have both to be aligned to sizeof(varing float) + * bytes. */ +void rtcIntersect (RTCScene scene, varying RTCRay& ray); + + +/*! Intersects a stream of N rays in AOS layout with the scene. This + * function can only be called for scenes with the RTC_INTERSECTN + * flag set. The stride specifies the offset between rays in + * bytes. */ +void rtcIntersectN (RTCScene scene, uniform RTCRay* uniform rayN, const uniform size_t N, const uniform size_t stride, const uniform size_t flags); + +/*! Intersects one or multiple streams of N rays in compact SOA layout with the scene. This + * function can only be called for scenes with the RTC_INTERSECTN + * flag set. 'streams' specifies the number of dense SOA ray + * streams, and 'stride' the offset in bytes between those. */ +void rtcIntersectN_SOA (RTCScene scene, uniform RTCRaySOA& rayN, const uniform size_t N, const uniform size_t streams, const uniform size_t offset, const uniform size_t flags); + + +/*! Tests if a uniform ray is occluded by the scene. This function can + * only be called for scenes with the RTC_INTERSECT_UNIFORM flag + * set. The ray has to be aligned to 16 bytes. */ +void rtcOccluded1 (RTCScene scene, uniform RTCRay1& ray); + +/*! Tests if a varying ray is occluded by the scene. This function can + * only be called for scenes with the RTC_INTERSECT_VARYING flag + * set. The valid mask and ray have both to be aligned to + * sizeof(varing float) bytes. */ +void rtcOccluded (RTCScene scene, varying RTCRay& ray); + + +/*! Tests if a stream of N rays on AOS layout is occluded by the + * scene. This function can only be called for scenes with the + * RTC_INTERSECTN flag set. The stride specifies the offset between + * rays in bytes.*/ +void rtcOccludedN (RTCScene scene, uniform RTCRay* uniform rayN, const uniform size_t N, const uniform size_t stride, const uniform size_t flags); + +/*! Intersects one or multiple streams of N rays in compact SOA layout with the scene. This + * function can only be called for scenes with the RTC_INTERSECTN + * flag set. 'streams' specifies the number of dense SOA ray + * streams, and 'stride' the offset in bytes between those. */ +void rtcOccludedN_SOA (RTCScene scene, uniform RTCRaySOA& rayN, const uniform size_t N, const uniform size_t streams, const uniform size_t offset, const uniform size_t flags); + +/*! Deletes the geometry again. */ +void rtcDeleteScene (RTCScene scene); + +/*! @} */ + +#endif diff --git a/src/igl/embree/line_mesh_intersection.cpp b/src/igl/embree/line_mesh_intersection.cpp new file mode 100644 index 000000000..0f6e02b8c --- /dev/null +++ b/src/igl/embree/line_mesh_intersection.cpp @@ -0,0 +1,85 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "line_mesh_intersection.h" +#include "../Hit.h" + +// For error printing +#include +#include + +#include +#include + +template +IGL_INLINE ScalarMatrix igl::embree::line_mesh_intersection +( + const ScalarMatrix & V_source, + const ScalarMatrix & N_source, + const ScalarMatrix & V_target, + const IndexMatrix & F_target +) +{ + + double tol = 0.00001; + + Eigen::MatrixXd ray_pos = V_source; + Eigen::MatrixXd ray_dir = N_source; + + // Allocate matrix for the result + ScalarMatrix R; + R.resize(V_source.rows(), 3); + + // Initialize embree + EmbreeIntersector embree; + embree.init(V_target.template cast(),F_target.template cast()); + + // Shoot rays from the source to the target + for (unsigned i=0; i(), A_dir.cast(),A); + + Eigen::RowVector3d B_pos = ray_pos.row(i) - tol * ray_dir.row(i); + Eigen::RowVector3d B_dir = ray_dir.row(i); + + bool B_hit = embree.intersectBeam(B_pos.cast(), B_dir.cast(),B); + + + int choice = -1; + + if (A_hit && ! B_hit) + choice = 0; + else if (!A_hit && B_hit) + choice = 1; + else if (A_hit && B_hit) + choice = A.t > B.t; + + Eigen::RowVector3d temp; + + if (choice == -1) + temp << -1, 0, 0; + else if (choice == 0) + temp << A.id, A.u, A.v; + else if (choice == 1) + temp << B.id, B.u, B.v; + + R.row(i) = temp; + + } + + return R; + +} + +#ifdef IGL_STATIC_LIBRARY +template Eigen::Matrix igl::embree::line_mesh_intersection, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +#endif diff --git a/src/igl/embree/line_mesh_intersection.h b/src/igl/embree/line_mesh_intersection.h new file mode 100644 index 000000000..b7b849c8a --- /dev/null +++ b/src/igl/embree/line_mesh_intersection.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_LINE_MESH_INTERSECTION_H +#define IGL_EMBREE_LINE_MESH_INTERSECTION_H +#include + +#include +#include +#include + +namespace igl +{ + namespace embree + { + // Project the point cloud V_source onto the triangle mesh + // V_target,F_target. + // A ray is casted for every vertex in the direction specified by + // N_source and its opposite. + // + // Input: + // V_source: #Vx3 Vertices of the source mesh + // N_source: #Vx3 Normals of the point cloud + // V_target: #V2x3 Vertices of the target mesh + // F_target: #F2x3 Faces of the target mesh + // + // Output: + // #Vx3 matrix of baricentric coordinate. Each row corresponds to + // a vertex of the projected mesh and it has the following format: + // id b1 b2. id is the id of a face of the source mesh. b1 and b2 are + // the barycentric coordinates wrt the first two edges of the triangle + // To convert to standard global coordinates, see barycentric_to_global.h + template + IGL_INLINE ScalarMatrix line_mesh_intersection + ( + const ScalarMatrix & V_source, + const ScalarMatrix & N_source, + const ScalarMatrix & V_target, + const IndexMatrix & F_target + ); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "line_mesh_intersection.cpp" +#endif + +#endif diff --git a/src/igl/embree/reorient_facets_raycast.cpp b/src/igl/embree/reorient_facets_raycast.cpp new file mode 100644 index 000000000..fabc021d9 --- /dev/null +++ b/src/igl/embree/reorient_facets_raycast.cpp @@ -0,0 +1,259 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "reorient_facets_raycast.h" +#include "../per_face_normals.h" +#include "../doublearea.h" +#include "../random_dir.h" +#include "../bfs_orient.h" +#include "EmbreeIntersector.h" +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::embree::reorient_facets_raycast( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + int rays_total, + int rays_minimum, + bool facet_wise, + bool use_parity, + bool is_verbose, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) +{ + using namespace Eigen; + using namespace std; + assert(F.cols() == 3); + assert(V.cols() == 3); + + // number of faces + const int m = F.rows(); + + MatrixXi FF = F; + if (facet_wise) { + C.resize(m); + for (int i = 0; i < m; ++i) C(i) = i; + + } else { + if (is_verbose) cout << "extracting patches... "; + bfs_orient(F,FF,C); + } + if (is_verbose) cout << (C.maxCoeff() + 1) << " components. "; + + // number of patches + const int num_cc = C.maxCoeff()+1; + + // Init Embree + EmbreeIntersector ei; + ei.init(V.template cast(),FF); + + // face normal + MatrixXd N; + per_face_normals(V,FF,N); + + // face area + Matrix A; + doublearea(V,FF,A); + double area_total = A.sum(); + + // determine number of rays per component according to its area + VectorXd area_per_component; + area_per_component.setZero(num_cc); + for (int f = 0; f < m; ++f) + { + area_per_component(C(f)) += A(f); + } + VectorXi num_rays_per_component(num_cc); + for (int c = 0; c < num_cc; ++c) + { + num_rays_per_component(c) = max(static_cast(rays_total * area_per_component(c) / area_total), rays_minimum); + } + rays_total = num_rays_per_component.sum(); + + // generate all the rays + if (is_verbose) cout << "generating rays... "; + uniform_real_distribution rdist; + mt19937 prng; + prng.seed(time(nullptr)); + vector ray_face; + vector ray_ori; + vector ray_dir; + ray_face.reserve(rays_total); + ray_ori .reserve(rays_total); + ray_dir .reserve(rays_total); + for (int c = 0; c < num_cc; ++c) + { + if (area_per_component[c] == 0) + { + continue; + } + vector CF; // set of faces per component + vector CF_area; + for (int f = 0; f < m; ++f) + { + if (C(f)==c) + { + CF.push_back(f); + CF_area.push_back(A(f)); + } + } + // discrete distribution for random selection of faces with probability proportional to their areas + discrete_distribution ddist(CF.size(), 0, CF.size(), [&](double i){ return CF_area[static_cast(i)]; }); // simple ctor of (Iter, Iter) not provided by the stupid VC11/12 + for (int i = 0; i < num_rays_per_component[c]; ++i) + { + int f = CF[ddist(prng)]; // select face with probability proportional to face area + float s = rdist(prng); // random barycentric coordinate (reference: Generating Random Points in Triangles [Turk, Graphics Gems I 1990]) + float t = rdist(prng); + float sqrt_t = sqrtf(t); + float a = 1 - sqrt_t; + float b = (1 - s) * sqrt_t; + float c = s * sqrt_t; + Vector3f p = a * V.row(FF(f,0)).template cast().eval() // be careful with the index!!! + + b * V.row(FF(f,1)).template cast().eval() + + c * V.row(FF(f,2)).template cast().eval(); + Vector3f n = N.row(f).cast(); + if (n.isZero()) continue; + // random direction in hemisphere around n (avoid too grazing angle) + Vector3f d; + while (true) { + d = random_dir().cast(); + float ndotd = n.dot(d); + if (fabsf(ndotd) < 0.1f) + { + continue; + } + if (ndotd < 0) + { + d *= -1.0f; + } + break; + } + ray_face.push_back(f); + ray_ori .push_back(p); + ray_dir .push_back(d); + + if (is_verbose && ray_face.size() % (rays_total / 10) == 0) cout << "."; + } + } + if (is_verbose) cout << ray_face.size() << " rays. "; + + // per component voting: first=front, second=back + vector> C_vote_distance(num_cc, make_pair(0, 0)); // sum of distance between ray origin and intersection + vector> C_vote_infinity(num_cc, make_pair(0, 0)); // number of rays reaching infinity + vector> C_vote_parity(num_cc, make_pair(0, 0)); // sum of parity count for each ray + + if (is_verbose) cout << "shooting rays... "; +#pragma omp parallel for + for (int i = 0; i < (int)ray_face.size(); ++i) + { + int f = ray_face[i]; + Vector3f o = ray_ori [i]; + Vector3f d = ray_dir [i]; + int c = C(f); + + // shoot ray toward front & back + vector hits_front; + vector hits_back; + int num_rays_front; + int num_rays_back; + ei.intersectRay(o, d, hits_front, num_rays_front); + ei.intersectRay(o, -d, hits_back , num_rays_back ); + if (!hits_front.empty() && hits_front[0].id == f) hits_front.erase(hits_front.begin()); + if (!hits_back .empty() && hits_back [0].id == f) hits_back .erase(hits_back .begin()); + + if (use_parity) { +#pragma omp atomic + C_vote_parity[c].first += hits_front.size() % 2; +#pragma omp atomic + C_vote_parity[c].second += hits_back .size() % 2; + + } else { + if (hits_front.empty()) + { +#pragma omp atomic + C_vote_infinity[c].first++; + } else { +#pragma omp atomic + C_vote_distance[c].first += hits_front[0].t; + } + + if (hits_back.empty()) + { +#pragma omp atomic + C_vote_infinity[c].second++; + } else { +#pragma omp atomic + C_vote_distance[c].second += hits_back[0].t; + } + } + } + + I.resize(m); + for(int f = 0; f < m; ++f) + { + int c = C(f); + if (use_parity) { + I(f) = C_vote_parity[c].first > C_vote_parity[c].second ? 1 : 0; // Ideally, parity for the front/back side should be 1/0 (i.e., parity sum for all rays should be smaller on the front side) + + } else { + I(f) = (C_vote_infinity[c].first == C_vote_infinity[c].second && C_vote_distance[c].first < C_vote_distance[c].second) || + C_vote_infinity[c].first < C_vote_infinity[c].second + ? 1 : 0; + } + // To account for the effect of bfs_orient + if (F.row(f) != FF.row(f)) + I(f) = 1 - I(f); + } + if (is_verbose) cout << "done!" << endl; +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> +IGL_INLINE void igl::embree::reorient_facets_raycast( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I) +{ + const int rays_total = F.rows()*100; + const int rays_minimum = 10; + const bool facet_wise = false; + const bool use_parity = false; + const bool is_verbose = false; + Eigen::VectorXi C; + reorient_facets_raycast( + V,F,rays_total,rays_minimum,facet_wise,use_parity,is_verbose,I,C); + // Conservative in case FF = F + FF.conservativeResize(F.rows(),F.cols()); + for(int i = 0;i, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::embree::reorient_facets_raycast, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, int, bool, bool, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::embree::reorient_facets_raycast, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, int, bool, bool, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/embree/reorient_facets_raycast.h b/src/igl/embree/reorient_facets_raycast.h new file mode 100644 index 000000000..a43efac94 --- /dev/null +++ b/src/igl/embree/reorient_facets_raycast.h @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_REORIENT_FACETS_RAYCAST_H +#define IGL_EMBREE_REORIENT_FACETS_RAYCAST_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace embree + { + // Orient each component (identified by C) of a mesh (V,F) using ambient + // occlusion such that the front side is less occluded than back side, as + // described in "A Simple Method for Correcting Facet Orientations in + // Polygon Meshes Based on Ray Casting" [Takayama et al. 2014]. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // rays_total Total number of rays that will be shot + // rays_minimum Minimum number of rays that each patch should receive + // facet_wise Decision made for each face independently, no use of patches + // (i.e., each face is treated as a patch) + // use_parity Use parity mode + // is_verbose Verbose output to cout + // Outputs: + // I #F list of whether face has been flipped + // C #F list of patch ID (output of bfs_orient > manifold patches) + template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename DerivedC> + IGL_INLINE void reorient_facets_raycast( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + int rays_total, + int rays_minimum, + bool facet_wise, + bool use_parity, + bool is_verbose, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C); + // Outputs: + // FF #F by 3 list of reoriented faces + // Defaults: + // rays_total = F.rows()*100; + // rays_minimum = 10; + // facet_wise = false; + // use_parity = false; + // is_verbose = false; + template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> + IGL_INLINE void reorient_facets_raycast( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I); + } +}; + +#ifndef IGL_STATIC_LIBRARY +# include "reorient_facets_raycast.cpp" +#endif + +#endif diff --git a/src/igl/embree/shape_diameter_function.cpp b/src/igl/embree/shape_diameter_function.cpp new file mode 100644 index 000000000..3b0be489e --- /dev/null +++ b/src/igl/embree/shape_diameter_function.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "shape_diameter_function.h" +#include "../shape_diameter_function.h" +#include "EmbreeIntersector.h" +#include "../Hit.h" + +template < + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::embree::shape_diameter_function( + const igl::embree::EmbreeIntersector & ei, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + const auto & shoot_ray = [&ei]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir)->double + { + igl::Hit hit; + const float tnear = 1e-4f; + if(ei.intersectRay(s,dir,hit,tnear)) + { + return hit.t; + }else + { + return std::numeric_limits::infinity(); + } + }; + return igl::shape_diameter_function(shoot_ray,P,N,num_samples,S); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::embree::shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + using namespace Eigen; + EmbreeIntersector ei; + ei.init(V.template cast(),F.template cast()); + shape_diameter_function(ei,P,N,num_samples,S); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::embree::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::shape_diameter_function, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::shape_diameter_function, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::embree::shape_diameter_function, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/embree/shape_diameter_function.h b/src/igl/embree/shape_diameter_function.h new file mode 100644 index 000000000..c18922d28 --- /dev/null +++ b/src/igl/embree/shape_diameter_function.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_SHAPE_DIAMETER_FUNCTION_H +#define IGL_EMBREE_SHAPE_DIAMETER_FUNCTION_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace embree + { + // Forward define + class EmbreeIntersector; + // Compute shape diamter function per given point + // + // Inputs: + // ei EmbreeIntersector containing (V,F) + // P #P by 3 list of origin points + // N #P by 3 list of origin normals + // Outputs: + // S #P list of shape diamater function values between bounding box + // diagonal (perfect sphere) and 0 (perfect needle hook) + // + template < + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void shape_diameter_function( + const EmbreeIntersector & ei, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Wrapper which builds new EmbreeIntersector for (V,F). That's expensive so + // avoid this if repeatedly calling. + template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + } +}; +#ifndef IGL_STATIC_LIBRARY +# include "shape_diameter_function.cpp" +#endif + +#endif + diff --git a/src/igl/embree/unproject_in_mesh.cpp b/src/igl/embree/unproject_in_mesh.cpp new file mode 100644 index 000000000..79a24a0ab --- /dev/null +++ b/src/igl/embree/unproject_in_mesh.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_in_mesh.h" +#include "EmbreeIntersector.h" +#include "../unproject_ray.h" +#include "../unproject_in_mesh.h" +#include + +template +IGL_INLINE int igl::embree::unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + Eigen::PlainObjectBase & obj, + std::vector & hits) +{ + using namespace std; + using namespace Eigen; + const auto & shoot_ray = [&ei]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir, + std::vector & hits) + { + int num_rays_shot; + ei.intersectRay(s,dir,hits,num_rays_shot); + }; + return igl::unproject_in_mesh(pos,model,proj,viewport,shoot_ray,obj,hits); +} + +template +IGL_INLINE int igl::embree::unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + Eigen::PlainObjectBase & obj) +{ + std::vector hits; + return unproject_in_mesh(pos,model,proj,viewport,ei,obj,hits); +} + + +#ifdef IGL_STATIC_LIBRARY +template int igl::embree::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase >&, std::vector >&); +template int igl::embree::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase >&, std::vector >&); +template int igl::embree::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::embree::EmbreeIntersector const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/embree/unproject_in_mesh.h b/src/igl/embree/unproject_in_mesh.h new file mode 100644 index 000000000..707c1e625 --- /dev/null +++ b/src/igl/embree/unproject_in_mesh.h @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_UNPROJECT_IN_MESH +#define IGL_EMBREE_UNPROJECT_IN_MESH +#include +#include + +#include +#include "../Hit.h" + +namespace igl +{ + namespace embree + { + // Forward define + class EmbreeIntersector; + + // Unproject a screen location (using current opengl viewport, projection, and + // model view) to a 3D position _inside_ a given mesh. If the ray through the + // given screen location (x,y) _hits_ the mesh more than twice then the 3D + // midpoint between the first two hits is return. If it hits once, then that + // point is return. If it does not hit the mesh then obj is not set. + // + // + // Inputs: + // pos screen space coordinates + // model model matrix + // proj projection matrix + // viewport vieweport vector + // ei EmbreeIntersector containing (V,F) + // Outputs: + // obj 3d unprojected mouse point in mesh + // hits vector of embree hits + // Returns number of hits + // + // Note: Previous prototype did not require model, proj, and viewport. This + // has been removed. Instead replace with: + // + // Eigen::Matrix4f model,proj; + // Eigen::Vector4f viewport; + // igl::opengl2::model_proj_viewport(model,proj,viewport); + // igl::embree::unproject_in_mesh(Vector2f(x,y),model,proj,viewport,ei,obj,hits); + // + template < typename Derivedobj> + IGL_INLINE int unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + Eigen::PlainObjectBase & obj, + std::vector & hits); + template < typename Derivedobj> + IGL_INLINE int unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + Eigen::PlainObjectBase & obj); + + } +} +#ifndef IGL_STATIC_LIBRARY +# include "unproject_in_mesh.cpp" +#endif +#endif diff --git a/src/igl/embree/unproject_onto_mesh.cpp b/src/igl/embree/unproject_onto_mesh.cpp new file mode 100644 index 000000000..a9d336690 --- /dev/null +++ b/src/igl/embree/unproject_onto_mesh.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_onto_mesh.h" +#include "EmbreeIntersector.h" +#include "../unproject_onto_mesh.h" +#include + +IGL_INLINE bool igl::embree::unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::MatrixXi& F, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + int& fid, + Eigen::Vector3f& bc) +{ + using namespace std; + using namespace Eigen; + const auto & shoot_ray = [&ei]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir, + igl::Hit & hit)->bool + { + return ei.intersectRay(s,dir,hit); + }; + return igl::unproject_onto_mesh(pos,model,proj,viewport,shoot_ray,fid,bc); +} + +IGL_INLINE bool igl::embree::unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::MatrixXi& F, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + int& fid, + int& vid) +{ + Eigen::Vector3f bc; + if(!igl::embree::unproject_onto_mesh(pos,F,model,proj,viewport,ei,fid,bc)) + { + return false; + } + int i; + bc.maxCoeff(&i); + vid = F(fid,i); + return true; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/embree/unproject_onto_mesh.h b/src/igl/embree/unproject_onto_mesh.h new file mode 100644 index 000000000..db0a2e0cb --- /dev/null +++ b/src/igl/embree/unproject_onto_mesh.h @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EMBREE_UNPROJECT_ONTO_MESH_H +#define IGL_EMBREE_UNPROJECT_ONTO_MESH_H +#include +#include + +#include + +namespace igl +{ + namespace embree + { + // Forward define + class EmbreeIntersector; + // Unproject a screen location (using the given model, proj and viewport) to find + // the first hit on a mesh. + // + // Inputs: + // pos screen space coordinates + // F #F by 3 face matrix + // model model matrix + // proj projection matrix + // viewport vieweport vector + // ei EmbreeIntersector containing (V,F) + // Outputs: + // fid id of the first face hit + // bc barycentric coordinates of hit + // Returns true if there is a hit + IGL_INLINE bool unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::MatrixXi& F, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + int& fid, + Eigen::Vector3f& bc); + + // Unproject a screen location (using the given model, proj and viewport) to find + // the first face on the mesh and the closest vertex + // + // Inputs: + // pos screen space coordinates + // F #F by 3 face matrix + // model model matrix + // proj projection matrix + // viewport vieweport vector + // ei EmbreeIntersector containing (V,F) + // Outputs: + // fid id of the first face hit + // vid vertex id of the closest vertex hit + // Returns true if there is a hit + IGL_INLINE bool unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::MatrixXi& F, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const EmbreeIntersector & ei, + int& fid, + int& vid); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "unproject_onto_mesh.cpp" +#endif +#endif diff --git a/src/igl/euler_characteristic.cpp b/src/igl/euler_characteristic.cpp new file mode 100644 index 000000000..ab7eba22b --- /dev/null +++ b/src/igl/euler_characteristic.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "euler_characteristic.h" + +#include "edge_topology.h" +#include "edges.h" + +template +IGL_INLINE int igl::euler_characteristic( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F) +{ + + int euler_v = V.rows(); + Eigen::MatrixXi EV, FE, EF; + igl::edge_topology(V, F, EV, FE, EF); + int euler_e = EV.rows(); + int euler_f = F.rows(); + + int euler_char = euler_v - euler_e + euler_f; + return euler_char; + +} + +template +IGL_INLINE int igl::euler_characteristic( + const Eigen::MatrixBase & F) +{ + const int nf = F.rows(); + const int nv = F.maxCoeff()+1; + Eigen::Matrix E; + edges(F,E); + const int ne = E.rows(); + return nv - ne + nf; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template int igl::euler_characteristic, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template int igl::euler_characteristic >(Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/euler_characteristic.h b/src/igl/euler_characteristic.h new file mode 100644 index 000000000..f4c0c0a0f --- /dev/null +++ b/src/igl/euler_characteristic.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EULER_CHARACTERISTIC_H +#define IGL_EULER_CHARACTERISTIC_H +#include "igl_inline.h" + +#include +#include +#include +namespace igl +{ + // Computes the Euler characteristic of a given mesh (V,F) + // + // Inputs: + // F #F by dim list of mesh faces (must be triangles) + // Returns An int containing the Euler characteristic + template + IGL_INLINE int euler_characteristic( + const Eigen::MatrixBase & F); + + // Computes the Euler characteristic of a given mesh (V,F) + // Templates: + // Scalar should be a floating point number type + // Index should be an integer type + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by dim list of mesh faces (must be triangles) + // Returns An int containing the Euler characteristic + template + IGL_INLINE int euler_characteristic( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "euler_characteristic.cpp" +#endif + +#endif diff --git a/src/igl/exact_geodesic.cpp b/src/igl/exact_geodesic.cpp new file mode 100644 index 000000000..e20783b88 --- /dev/null +++ b/src/igl/exact_geodesic.cpp @@ -0,0 +1,3225 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Zhongshi Jiang +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "exact_geodesic.h" + +//Copyright (C) 2008 Danil Kirsanov, MIT License +//Code from https://code.google.com/archive/p/geodesic/ +// Compiled into a single file by Zhongshi Jiang + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace igl{ +namespace geodesic{ + +//#include "geodesic_constants_and_simple_functions.h" + +//double const GEODESIC_INF = std::numeric_limits::max(); +double const GEODESIC_INF = 1e100; + +//in order to avoid numerical problems with "infinitely small" intervals, +//we drop all the intervals smaller than SMALLEST_INTERVAL_RATIO*edge_length +double const SMALLEST_INTERVAL_RATIO = 1e-6; +//double const SMALL_EPSILON = 1e-10; + + +inline double cos_from_edges(double const a, //compute the cosine of the angle given the lengths of the edges + double const b, + double const c) +{ + assert(a>1e-50); + assert(b>1e-50); + assert(c>1e-50); + + double result = (b*b + c*c - a*a)/(2.0*b*c); + result = std::max(result, -1.0); + return std::min(result, 1.0); +} + +inline double angle_from_edges(double const a, //compute the cosine of the angle given the lengths of the edges + double const b, + double const c) +{ + return acos(cos_from_edges(a,b,c)); +} + +template +inline bool read_mesh_from_file(char* filename, + Points& points, + Faces& faces) +{ + std::ifstream file(filename); + assert(file.is_open()); + if(!file.is_open()) return false; + + unsigned num_points; + file >> num_points; + assert(num_points>=3); + + unsigned num_faces; + file >> num_faces; + + points.resize(num_points*3); + for(typename Points::iterator i=points.begin(); i!=points.end(); ++i) + { + file >> *i; + } + + faces.resize(num_faces*3); + for(typename Faces::iterator i=faces.begin(); i!=faces.end(); ++i) + { + file >> *i; + } + file.close(); + + return true; +} + +// #include "geodesic_memory" +template //quickly allocates multiple elements of a given type; no deallocation +class SimlpeMemoryAllocator +{ +public: + typedef T* pointer; + + SimlpeMemoryAllocator(unsigned block_size = 0, + unsigned max_number_of_blocks = 0) + { + reset(block_size, + max_number_of_blocks); + }; + + ~SimlpeMemoryAllocator(){}; + + void reset(unsigned block_size, + unsigned max_number_of_blocks) + { + m_block_size = block_size; + m_max_number_of_blocks = max_number_of_blocks; + + + m_current_position = 0; + + m_storage.reserve(max_number_of_blocks); + m_storage.resize(1); + m_storage[0].resize(block_size); + }; + + pointer allocate(unsigned const n) //allocate n units + { + assert(n < m_block_size); + + if(m_current_position + n >= m_block_size) + { + m_storage.push_back( std::vector() ); + m_storage.back().resize(m_block_size); + m_current_position = 0; + } + pointer result = & m_storage.back()[m_current_position]; + m_current_position += n; + + return result; + }; +private: + std::vector > m_storage; + unsigned m_block_size; //size of a single block + unsigned m_max_number_of_blocks; //maximum allowed number of blocks + unsigned m_current_position; //first unused element inside the current block +}; + + +template //quickly allocates and deallocates single elements of a given type +class MemoryAllocator +{ +public: + typedef T* pointer; + + MemoryAllocator(unsigned block_size = 1024, + unsigned max_number_of_blocks = 1024) + { + reset(block_size, + max_number_of_blocks); + }; + + ~MemoryAllocator(){}; + + void clear() + { + reset(m_block_size, + m_max_number_of_blocks); + } + + void reset(unsigned block_size, + unsigned max_number_of_blocks) + { + m_block_size = block_size; + m_max_number_of_blocks = max_number_of_blocks; + + assert(m_block_size > 0); + assert(m_max_number_of_blocks > 0); + + m_current_position = 0; + + m_storage.reserve(max_number_of_blocks); + m_storage.resize(1); + m_storage[0].resize(block_size); + + m_deleted.clear(); + m_deleted.reserve(2*block_size); + }; + + pointer allocate() //allocates single unit of memory + { + pointer result; + if(m_deleted.empty()) + { + if(m_current_position + 1 >= m_block_size) + { + m_storage.push_back( std::vector() ); + m_storage.back().resize(m_block_size); + m_current_position = 0; + } + result = & m_storage.back()[m_current_position]; + ++m_current_position; + } + else + { + result = m_deleted.back(); + m_deleted.pop_back(); + } + + return result; + }; + + void deallocate(pointer p) //allocate n units + { + if(m_deleted.size() < m_deleted.capacity()) + { + m_deleted.push_back(p); + } + }; + +private: + std::vector > m_storage; + unsigned m_block_size; //size of a single block + unsigned m_max_number_of_blocks; //maximum allowed number of blocks + unsigned m_current_position; //first unused element inside the current block + + std::vector m_deleted; //pointers to deleted elemets +}; + + +class OutputBuffer +{ +public: + OutputBuffer(): + m_num_bytes(0) + {} + + void clear() + { + m_num_bytes = 0; + m_buffer = std::shared_ptr(); + } + + template + T* allocate(unsigned n) + { + double wanted = n*sizeof(T); + if(wanted > m_num_bytes) + { + unsigned new_size = (unsigned) ceil(wanted / (double)sizeof(double)); + m_buffer = std::shared_ptr(new double[new_size]); + m_num_bytes = new_size*sizeof(double); + } + + return (T*)m_buffer.get(); + } + + template + T* get() + { + return (T*)m_buffer.get(); + } + + template + unsigned capacity() + { + return (unsigned)floor((double)m_num_bytes/(double)sizeof(T)); + }; + +private: + + std::shared_ptr m_buffer; + unsigned m_num_bytes; +}; + + + + +class Vertex; +class Edge; +class Face; +class Mesh; +class MeshElementBase; + +typedef Vertex* vertex_pointer; +typedef Edge* edge_pointer; +typedef Face* face_pointer; +typedef Mesh* mesh_pointer; +typedef MeshElementBase* base_pointer; + +template //simple vector that stores info about mesh references +class SimpleVector //for efficiency, it uses an outside memory allocator +{ +public: + SimpleVector(): + m_size(0), + m_begin(NULL) + {}; + + typedef Data* iterator; + + unsigned size(){return m_size;}; + iterator begin(){return m_begin;}; + iterator end(){return m_begin + m_size;}; + + template + void set_allocation(DataPointer begin, unsigned size) + { + assert(begin != NULL || size == 0); + m_size = size; + m_begin = (iterator)begin; + } + + Data& operator[](unsigned i) + { + assert(i < m_size); + return *(m_begin + i); + } + + void clear() + { + m_size = 0; + m_begin = NULL; + } + +private: + unsigned m_size; + Data* m_begin; +}; + +enum PointType +{ + VERTEX, + EDGE, + FACE, + UNDEFINED_POINT +}; + +class MeshElementBase //prototype of vertices, edges and faces +{ +public: + typedef SimpleVector vertex_pointer_vector; + typedef SimpleVector edge_pointer_vector; + typedef SimpleVector face_pointer_vector; + + MeshElementBase(): + m_id(0), + m_type(UNDEFINED_POINT) + {}; + + vertex_pointer_vector& adjacent_vertices(){return m_adjacent_vertices;}; + edge_pointer_vector& adjacent_edges(){return m_adjacent_edges;}; + face_pointer_vector& adjacent_faces(){return m_adjacent_faces;}; + + unsigned& id(){return m_id;}; + PointType type(){return m_type;}; + +protected: + vertex_pointer_vector m_adjacent_vertices; //list of the adjacent vertices + edge_pointer_vector m_adjacent_edges; //list of the adjacent edges + face_pointer_vector m_adjacent_faces; //list of the adjacent faces + + unsigned m_id; //unique id + PointType m_type; //vertex, edge or face +}; + +class Point3D //point in 3D and corresponding operations +{ +public: + Point3D(){}; + Point3D(Point3D* p) + { + x() = p->x(); + y() = p->y(); + z() = p->z(); + }; + + double* xyz(){return m_coordinates;}; + double& x(){return *m_coordinates;}; + double& y(){return *(m_coordinates+1);}; + double& z(){return *(m_coordinates+2);}; + + void set(double new_x, double new_y, double new_z) + { + x() = new_x; + y() = new_y; + z() = new_z; + } + + void set(double* data) + { + x() = *data; + y() = *(data+1); + z() = *(data+2); + } + + double distance(double* v) + { + double dx = m_coordinates[0] - v[0]; + double dy = m_coordinates[1] - v[1]; + double dz = m_coordinates[2] - v[2]; + + return sqrt(dx*dx + dy*dy + dz*dz); + }; + + double distance(Point3D* v) + { + return distance(v->xyz()); + }; + + void add(Point3D* v) + { + x() += v->x(); + y() += v->y(); + z() += v->z(); + }; + + void multiply(double v) + { + x() *= v; + y() *= v; + z() *= v; + }; + +private: + double m_coordinates[3]; //xyz +}; + +class Vertex: public MeshElementBase, public Point3D +{ +public: + Vertex() + { + m_type = VERTEX; + }; + + ~Vertex(){}; + + bool& saddle_or_boundary(){return m_saddle_or_boundary;}; +private: + //this flag speeds up exact geodesic algorithm + bool m_saddle_or_boundary; //it is true if total adjacent angle is larger than 2*PI or this vertex belongs to the mesh boundary +}; + + +class Face: public MeshElementBase +{ +public: + Face() + { + m_type = FACE; + }; + + ~Face(){}; + + edge_pointer opposite_edge(vertex_pointer v); + vertex_pointer opposite_vertex(edge_pointer e); + edge_pointer next_edge(edge_pointer e, vertex_pointer v); + + double vertex_angle(vertex_pointer v) + { + for(unsigned i=0; i<3; ++i) + { + if(adjacent_vertices()[i]->id() == v->id()) + { + return m_corner_angles[i]; + } + } + assert(0); + return 0; + } + + double* corner_angles(){return m_corner_angles;}; + +private: + double m_corner_angles[3]; //triangle angles in radians; angles correspond to vertices in m_adjacent_vertices +}; + +class Edge: public MeshElementBase +{ +public: + Edge() + { + m_type = EDGE; + }; + + ~Edge(){}; + + double& length(){return m_length;}; + + face_pointer opposite_face(face_pointer f) + { + if(adjacent_faces().size() == 1) + { + assert(adjacent_faces()[0]->id() == f->id()); + return NULL; + } + + assert(adjacent_faces()[0]->id() == f->id() || + adjacent_faces()[1]->id() == f->id()); + + return adjacent_faces()[0]->id() == f->id() ? + adjacent_faces()[1] : adjacent_faces()[0]; + }; + + vertex_pointer opposite_vertex(vertex_pointer v) + { + assert(belongs(v)); + + return adjacent_vertices()[0]->id() == v->id() ? + adjacent_vertices()[1] : adjacent_vertices()[0]; + }; + + bool belongs(vertex_pointer v) + { + return adjacent_vertices()[0]->id() == v->id() || + adjacent_vertices()[1]->id() == v->id(); + } + + bool is_boundary(){return adjacent_faces().size() == 1;}; + + vertex_pointer v0(){return adjacent_vertices()[0];}; + vertex_pointer v1(){return adjacent_vertices()[1];}; + + void local_coordinates(Point3D* point, + double& x, + double& y) + { + double d0 = point->distance(v0()); + if(d0 < 1e-50) + { + x = 0.0; + y = 0.0; + return; + } + + double d1 = point->distance(v1()); + if(d1 < 1e-50) + { + x = m_length; + y = 0.0; + return; + } + + x = m_length/2.0 + (d0*d0 - d1*d1)/(2.0*m_length); + y = sqrt(std::max(0.0, d0*d0 - x*x)); + return; + } + +private: + double m_length; //length of the edge +}; + +class SurfacePoint:public Point3D //point on the surface of the mesh +{ +public: + SurfacePoint(): + m_p(NULL) + {}; + + SurfacePoint(vertex_pointer v): //set the surface point in the vertex + SurfacePoint::Point3D(v), + m_p(v) + {}; + + SurfacePoint(face_pointer f): //set the surface point in the center of the face + m_p(f) + { + set(0,0,0); + add(f->adjacent_vertices()[0]); + add(f->adjacent_vertices()[1]); + add(f->adjacent_vertices()[2]); + multiply(1./3.); + }; + + SurfacePoint(edge_pointer e, //set the surface point in the middle of the edge + double a = 0.5): + m_p(e) + { + double b = 1 - a; + + vertex_pointer v0 = e->adjacent_vertices()[0]; + vertex_pointer v1 = e->adjacent_vertices()[1]; + + x() = b*v0->x() + a*v1->x(); + y() = b*v0->y() + a*v1->y(); + z() = b*v0->z() + a*v1->z(); + }; + + SurfacePoint(base_pointer g, + double x, + double y, + double z, + PointType t = UNDEFINED_POINT): + m_p(g) + { + set(x,y,z); + }; + + void initialize(SurfacePoint const& p) + { + *this = p; + } + + ~SurfacePoint(){}; + + PointType type(){return m_p ? m_p->type() : UNDEFINED_POINT;}; + base_pointer& base_element(){return m_p;}; +protected: + base_pointer m_p; //could be face, vertex or edge pointer +}; + +inline edge_pointer Face::opposite_edge(vertex_pointer v) +{ + for(unsigned i=0; i<3; ++i) + { + edge_pointer e = adjacent_edges()[i]; + if(!e->belongs(v)) + { + return e; + } + } + assert(0); + return NULL; +} + +inline vertex_pointer Face::opposite_vertex(edge_pointer e) +{ + for(unsigned i=0; i<3; ++i) + { + vertex_pointer v = adjacent_vertices()[i]; + if(!e->belongs(v)) + { + return v; + } + } + assert(0); + return NULL; +} + +inline edge_pointer Face::next_edge(edge_pointer e, vertex_pointer v) +{ + assert(e->belongs(v)); + + for(unsigned i=0; i<3; ++i) + { + edge_pointer next = adjacent_edges()[i]; + if(e->id() != next->id() && next->belongs(v)) + { + return next; + } + } + assert(0); + return NULL; +} + +struct HalfEdge //prototype of the edge; used for mesh construction +{ + unsigned face_id; + unsigned vertex_0; //adjacent vertices sorted by id value + unsigned vertex_1; //they are sorted, vertex_0 < vertex_1 +}; + +inline bool operator < (const HalfEdge &x, const HalfEdge &y) +{ + if(x.vertex_0 == y.vertex_0) + { + return x.vertex_1 < y.vertex_1; + } + else + { + return x.vertex_0 < y.vertex_0; + } +} + +inline bool operator != (const HalfEdge &x, const HalfEdge &y) +{ + return x.vertex_0 != y.vertex_0 || x.vertex_1 != y.vertex_1; +} + +inline bool operator == (const HalfEdge &x, const HalfEdge &y) +{ + return x.vertex_0 == y.vertex_0 && x.vertex_1 == y.vertex_1; +} + +struct edge_visible_from_source +{ + unsigned source; + edge_pointer edge; +}; + +class Mesh +{ +public: + Mesh() + {}; + + ~Mesh(){}; + + template + void initialize_mesh_data(unsigned num_vertices, + Points& p, + unsigned num_faces, + Faces& tri); //build mesh from regular point-triangle representation + + template + void initialize_mesh_data(Points& p, Faces& tri); //build mesh from regular point-triangle representation + + std::vector& vertices(){return m_vertices;}; + std::vector& edges(){return m_edges;}; + std::vector& faces(){return m_faces;}; + + unsigned closest_vertices(SurfacePoint* p, + std::vector* storage = NULL); //list vertices closest to the point + +private: + + void build_adjacencies(); //build internal structure of the mesh + bool verify(); //verifies connectivity of the mesh and prints some debug info + + typedef void* void_pointer; + void_pointer allocate_pointers(unsigned n) + { + return m_pointer_allocator.allocate(n); + } + + std::vector m_vertices; + std::vector m_edges; + std::vector m_faces; + + SimlpeMemoryAllocator m_pointer_allocator; //fast memory allocating for Face/Vertex/Edge cross-references +}; + +inline unsigned Mesh::closest_vertices(SurfacePoint* p, + std::vector* storage) +{ + assert(p->type() != UNDEFINED_POINT); + + if(p->type() == VERTEX) + { + if(storage) + { + storage->push_back(static_cast(p->base_element())); + } + return 1; + } + else if(p->type() == FACE) + { + if(storage) + { + vertex_pointer* vp= p->base_element()->adjacent_vertices().begin(); + storage->push_back(*vp); + storage->push_back(*(vp+1)); + storage->push_back(*(vp+2)); + } + return 2; + } + else if(p->type() == EDGE) //for edge include all 4 adjacent vertices + { + edge_pointer edge = static_cast(p->base_element()); + + if(storage) + { + storage->push_back(edge->adjacent_vertices()[0]); + storage->push_back(edge->adjacent_vertices()[1]); + + for(unsigned i = 0; i < edge->adjacent_faces().size(); ++i) + { + face_pointer face = edge->adjacent_faces()[i]; + storage->push_back(face->opposite_vertex(edge)); + } + } + return 2 + edge->adjacent_faces().size(); + } + + assert(0); + return 0; +} + +template +void Mesh::initialize_mesh_data(Points& p, Faces& tri) //build mesh from regular point-triangle representation +{ + assert(p.size() % 3 == 0); + unsigned const num_vertices = p.size() / 3; + assert(tri.size() % 3 == 0); + unsigned const num_faces = tri.size() / 3; + + initialize_mesh_data(num_vertices, p, num_faces, tri); +} + +template +void Mesh::initialize_mesh_data(unsigned num_vertices, + Points& p, + unsigned num_faces, + Faces& tri) +{ + unsigned const approximate_number_of_internal_pointers = (num_vertices + num_faces)*4; + unsigned const max_number_of_pointer_blocks = 100; + m_pointer_allocator.reset(approximate_number_of_internal_pointers, + max_number_of_pointer_blocks); + + m_vertices.resize(num_vertices); + for(unsigned i=0; iadjacent Faces + std::vector count(m_vertices.size()); //count adjacent vertices + for(unsigned i=0; iid(); + assert(vertex_id < m_vertices.size()); + count[vertex_id]++; + } + } + + for(unsigned i=0; iadjacent_faces()[count[v->id()]++] = &f; + } + } + + //find all edges + //i.e. find all half-edges, sort and combine them into edges + std::vector half_edges(m_faces.size()*3); + unsigned k = 0; + for(unsigned i=0; iid(); + unsigned vertex_id_2 = f.adjacent_vertices()[(j+1) % 3]->id(); + half_edges[k].vertex_0 = std::min(vertex_id_1, vertex_id_2); + half_edges[k].vertex_1 = std::max(vertex_id_1, vertex_id_2); + + k++; + } + } + std::sort(half_edges.begin(), half_edges.end()); + + unsigned number_of_edges = 1; + for(unsigned i=1; iadjacent Vertices and Faces + m_edges.resize(number_of_edges); + unsigned edge_id = 0; + for(unsigned i=0; idistance(e.adjacent_vertices()[1]); + assert(e.length() > 1e-100); //algorithm works well with non-degenerate meshes only + + if(i != half_edges.size()-1 && half_edges[i] == half_edges[i+1]) //double edge + { + e.adjacent_faces().set_allocation(allocate_pointers(2),2); + e.adjacent_faces()[0] = &m_faces[half_edges[i].face_id]; + e.adjacent_faces()[1] = &m_faces[half_edges[i+1].face_id]; + i += 2; + } + else //single edge + { + e.adjacent_faces().set_allocation(allocate_pointers(1),1); //one adjucent faces + e.adjacent_faces()[0] = &m_faces[half_edges[i].face_id]; + i += 1; + } + } + + // Vertices->adjacent Edges + std::fill(count.begin(), count.end(), 0); + for(unsigned i=0; iid()]++; + count[e.adjacent_vertices()[1]->id()]++; + } + for(unsigned i=0; iadjacent_edges()[count[v->id()]++] = &e; + } + } + + // Faces->adjacent Edges + for(unsigned i=0; iid()]<3); + f->adjacent_edges()[count[f->id()]++] = &e; + } + } + + //compute angles for the faces + for(unsigned i=0; ilength(); + } + + double angle = angle_from_edges(abc[0], abc[1], abc[2]); + assert(angle>1e-5); //algorithm works well with non-degenerate meshes only + + f.corner_angles()[j] = angle; + sum += angle; + } + assert(std::abs(sum - igl::PI) < 1e-5); //algorithm works well with non-degenerate meshes only + } + + //define m_turn_around_flag for vertices + std::vector total_vertex_angle(m_vertices.size()); + for(unsigned i=0; iid()] += f.corner_angles()[j]; + } + } + + for(unsigned i=0; i 2.0*igl::PI - 1e-5); + } + + for(unsigned i=0; isaddle_or_boundary() = true; + e.adjacent_vertices()[1]->saddle_or_boundary() = true; + } + } + + assert(verify()); +} + +inline bool Mesh::verify() //verifies connectivity of the mesh and prints some debug info +{ + std::cout << std::endl; + // make sure that all vertices are mentioned at least once. + // though the loose vertex is not a bug, it most likely indicates that something is wrong with the mesh + std::vector map(m_vertices.size(), false); + for(unsigned i=0; iadjacent_vertices()[0]->id()] = true; + map[e->adjacent_vertices()[1]->id()] = true; + } + assert(std::find(map.begin(), map.end(), false) == map.end()); + + //make sure that the mesh is connected trough its edges + //if mesh has more than one connected component, it is most likely a bug + std::vector stack(1,&m_faces[0]); + stack.reserve(m_faces.size()); + + map.resize(m_faces.size()); + std::fill(map.begin(), map.end(), false); + map[0] = true; + + while(!stack.empty()) + { + face_pointer f = stack.back(); + stack.pop_back(); + + for(unsigned i=0; i<3; ++i) + { + edge_pointer e = f->adjacent_edges()[i]; + face_pointer f_adjacent = e->opposite_face(f); + if(f_adjacent && !map[f_adjacent->id()]) + { + map[f_adjacent->id()] = true; + stack.push_back(f_adjacent); + } + } + } + assert(std::find(map.begin(), map.end(), false) == map.end()); + + //print some mesh statistics that can be useful in debugging + // std::cout << "mesh has " << m_vertices.size() + // << " vertices, " << m_faces.size() + // << " faces, " << m_edges.size() + // << " edges\n"; + + unsigned total_boundary_edges = 0; + double longest_edge = 0; + double shortest_edge = 1e100; + for(unsigned i=0; iset(data); + unsigned type = (unsigned) data[3]; + unsigned id = (unsigned) data[4]; + + + if(type == 0) //vertex + { + point->base_element() = &mesh->vertices()[id]; + } + else if(type == 1) //edge + { + point->base_element() = &mesh->edges()[id]; + } + else //face + { + point->base_element() = &mesh->faces()[id]; + } +} + +inline void fill_surface_point_double(geodesic::SurfacePoint* point, + double* data, + long mesh_id) +{ + data[0] = point->x(); + data[1] = point->y(); + data[2] = point->z(); + data[4] = point->base_element()->id(); + + if(point->type() == VERTEX) //vertex + { + data[3] = 0; + } + else if(point->type() == EDGE) //edge + { + data[3] = 1; + } + else //face + { + data[3] = 2; + } +} + +class Interval; +class IntervalList; +typedef Interval* interval_pointer; +typedef IntervalList* list_pointer; + +class Interval //interval of the edge +{ +public: + + Interval(){}; + ~Interval(){}; + + enum DirectionType + { + FROM_FACE_0, + FROM_FACE_1, + FROM_SOURCE, + UNDEFINED_DIRECTION + }; + + double signal(double x) //geodesic distance function at point x + { + assert(x>=0.0 && x <= m_edge->length()); + + if(m_d == GEODESIC_INF) + { + return GEODESIC_INF; + } + else + { + double dx = x - m_pseudo_x; + if(m_pseudo_y == 0.0) + { + return m_d + std::abs(dx); + } + else + { + return m_d + sqrt(dx*dx + m_pseudo_y*m_pseudo_y); + } + } + } + + double max_distance(double end) + { + if(m_d == GEODESIC_INF) + { + return GEODESIC_INF; + } + else + { + double a = std::abs(m_start - m_pseudo_x); + double b = std::abs(end - m_pseudo_x); + + return a > b ? m_d + sqrt(a*a + m_pseudo_y*m_pseudo_y): + m_d + sqrt(b*b + m_pseudo_y*m_pseudo_y); + } + } + + void compute_min_distance(double stop) //compute min, given c,d theta, start, end. + { + assert(stop > m_start); + + if(m_d == GEODESIC_INF) + { + m_min = GEODESIC_INF; + } + else if(m_start > m_pseudo_x) + { + m_min = signal(m_start); + } + else if(stop < m_pseudo_x) + { + m_min = signal(stop); + } + else + { + assert(m_pseudo_y<=0); + m_min = m_d - m_pseudo_y; + } + } + //compare two intervals in the queue + bool operator()(interval_pointer const x, interval_pointer const y) const + { + if(x->min() != y->min()) + { + return x->min() < y->min(); + } + else if(x->start() != y->start()) + { + return x->start() < y->start(); + } + else + { + return x->edge()->id() < y->edge()->id(); + } + } + + double stop() //return the endpoint of the interval + { + return m_next ? m_next->start() : m_edge->length(); + } + + double hypotenuse(double a, double b) + { + return sqrt(a*a + b*b); + } + + void find_closest_point(double const x, + double const y, + double& offset, + double& distance); //find the point on the interval that is closest to the point (alpha, s) + + double& start(){return m_start;}; + double& d(){return m_d;}; + double& pseudo_x(){return m_pseudo_x;}; + double& pseudo_y(){return m_pseudo_y;}; + double& min(){return m_min;}; + interval_pointer& next(){return m_next;}; + edge_pointer& edge(){return m_edge;}; + DirectionType& direction(){return m_direction;}; + bool visible_from_source(){return m_direction == FROM_SOURCE;}; + unsigned& source_index(){return m_source_index;}; + + void initialize(edge_pointer edge, + SurfacePoint* point = NULL, + unsigned source_index = 0); + +protected: + double m_start; //initial point of the interval on the edge + double m_d; //distance from the source to the pseudo-source + double m_pseudo_x; //coordinates of the pseudo-source in the local coordinate system + double m_pseudo_y; //y-coordinate should be always negative + double m_min; //minimum distance on the interval + + interval_pointer m_next; //pointer to the next interval in the list + edge_pointer m_edge; //edge that the interval belongs to + unsigned m_source_index; //the source it belongs to + DirectionType m_direction; //where the interval is coming from +}; + +struct IntervalWithStop : public Interval +{ +public: + double& stop(){return m_stop;}; +protected: + double m_stop; +}; + +class IntervalList //list of the of intervals of the given edge +{ +public: + IntervalList(){m_first = NULL;}; + ~IntervalList(){}; + + void clear() + { + m_first = NULL; + }; + + void initialize(edge_pointer e) + { + m_edge = e; + m_first = NULL; + }; + + interval_pointer covering_interval(double offset) //returns the interval that covers the offset + { + assert(offset >= 0.0 && offset <= m_edge->length()); + + interval_pointer p = m_first; + while(p && p->stop() < offset) + { + p = p->next(); + } + + return p;// && p->start() <= offset ? p : NULL; + }; + + void find_closest_point(SurfacePoint* point, + double& offset, + double& distance, + interval_pointer& interval) + { + interval_pointer p = m_first; + distance = GEODESIC_INF; + interval = NULL; + + double x,y; + m_edge->local_coordinates(point, x, y); + + while(p) + { + if(p->min()find_closest_point(x, y, o, d); + if(d < distance) + { + distance = d; + offset = o; + interval = p; + } + } + p = p->next(); + } + }; + + unsigned number_of_intervals() + { + interval_pointer p = m_first; + unsigned count = 0; + while(p) + { + ++count; + p = p->next(); + } + return count; + } + + interval_pointer last() + { + interval_pointer p = m_first; + if(p) + { + while(p->next()) + { + p = p->next(); + } + } + return p; + } + + double signal(double x) + { + interval_pointer interval = covering_interval(x); + + return interval ? interval->signal(x) : GEODESIC_INF; + } + + interval_pointer& first(){return m_first;}; + edge_pointer& edge(){return m_edge;}; +private: + interval_pointer m_first; //pointer to the first member of the list + edge_pointer m_edge; //edge that owns this list +}; + +class SurfacePointWithIndex : public SurfacePoint +{ +public: + unsigned index(){return m_index;}; + + void initialize(SurfacePoint& p, unsigned index) + { + SurfacePoint::initialize(p); + m_index = index; + } + + bool operator()(SurfacePointWithIndex* x, SurfacePointWithIndex* y) const //used for sorting + { + assert(x->type() != UNDEFINED_POINT && y->type() !=UNDEFINED_POINT); + + if(x->type() != y->type()) + { + return x->type() < y->type(); + } + else + { + return x->base_element()->id() < y->base_element()->id(); + } + } + +private: + unsigned m_index; +}; + +class SortedSources : public std::vector +{ +private: + typedef std::vector sorted_vector_type; +public: + typedef sorted_vector_type::iterator sorted_iterator; + typedef std::pair sorted_iterator_pair; + + sorted_iterator_pair sources(base_pointer mesh_element) + { + m_search_dummy.base_element() = mesh_element; + + return equal_range(m_sorted.begin(), + m_sorted.end(), + &m_search_dummy, + m_compare_less); + } + + void initialize(std::vector& sources) //we initialize the sources by copie + { + resize(sources.size()); + m_sorted.resize(sources.size()); + for(unsigned i=0; ilength(); + if(std::abs(hs+hc) < local_epsilon) + { + if(rs<=m_start) + { + r = m_start; + d_out = signal(m_start) + std::abs(rs - m_start); + } + else if(rs>=end) + { + r = end; + d_out = signal(end) + fabs(end - rs); + } + else + { + r = rs; + d_out = signal(rs); + } + } + else + { + double ri = (rs*hc + hs*rc)/(hs+hc); + + if(riend) + { + r = end; + d_out = signal(end) + hypotenuse(end - rs, hs); + } + else + { + r = ri; + d_out = m_d + hypotenuse(rc - rs, hc + hs); + } + } + } + + +inline void Interval::initialize(edge_pointer edge, + SurfacePoint* source, + unsigned source_index) +{ + m_next = NULL; + //m_geodesic_previous = NULL; + m_direction = UNDEFINED_DIRECTION; + m_edge = edge; + m_source_index = source_index; + + m_start = 0.0; + //m_stop = edge->length(); + if(!source) + { + m_d = GEODESIC_INF; + m_min = GEODESIC_INF; + return; + } + m_d = 0; + + if(source->base_element()->type() == VERTEX) + { + if(source->base_element()->id() == edge->v0()->id()) + { + m_pseudo_x = 0.0; + m_pseudo_y = 0.0; + m_min = 0.0; + return; + } + else if(source->base_element()->id() == edge->v1()->id()) + { + m_pseudo_x = stop(); + m_pseudo_y = 0.0; + m_min = 0.0; + return; + } + } + + edge->local_coordinates(source, m_pseudo_x, m_pseudo_y); + m_pseudo_y = -m_pseudo_y; + + compute_min_distance(stop()); +} + + + +// #include "geodesic_algorithm_base.h" +class GeodesicAlgorithmBase +{ +public: + enum AlgorithmType + { + EXACT, + DIJKSTRA, + SUBDIVISION, + UNDEFINED_ALGORITHM + }; + + GeodesicAlgorithmBase(geodesic::Mesh* mesh): + m_type(UNDEFINED_ALGORITHM), + m_max_propagation_distance(1e100), + m_mesh(mesh) + {}; + + virtual ~GeodesicAlgorithmBase(){}; + + virtual void propagate(std::vector& sources, + double max_propagation_distance = GEODESIC_INF, //propagation algorithm stops after reaching the certain distance from the source + std::vector* stop_points = NULL) = 0; //or after ensuring that all the stop_points are covered + + virtual void trace_back(SurfacePoint& destination, //trace back piecewise-linear path + std::vector& path) = 0; + + void geodesic(SurfacePoint& source, + SurfacePoint& destination, + std::vector& path); //lazy people can find geodesic path with one function call + + void geodesic(std::vector& sources, + std::vector& destinations, + std::vector >& paths); //lazy people can find geodesic paths with one function call + + virtual unsigned best_source(SurfacePoint& point, //after propagation step is done, quickly find what source this point belongs to and what is the distance to this source + double& best_source_distance) = 0; + + virtual void print_statistics() //print info about timing and memory usage in the propagation step of the algorithm + { + std::cout << "propagation step took " << m_time_consumed << " seconds " << std::endl; + }; + + AlgorithmType type(){return m_type;}; + + virtual std::string name(); + + geodesic::Mesh* mesh(){return m_mesh;}; +protected: + + void set_stop_conditions(std::vector* stop_points, + double stop_distance); + double stop_distance() + { + return m_max_propagation_distance; + } + + AlgorithmType m_type; // type of the algorithm + + typedef std::pair stop_vertex_with_distace_type; + std::vector m_stop_vertices; // algorithm stops propagation after covering certain vertices + double m_max_propagation_distance; // or reaching the certain distance + + geodesic::Mesh* m_mesh; + + double m_time_consumed; //how much time does the propagation step takes + double m_propagation_distance_stopped; //at what distance (if any) the propagation algorithm stopped +}; + +inline double length(std::vector& path) +{ + double length = 0; + if(!path.empty()) + { + for(unsigned i=0; i& path) +{ + std::cout << "number of the points in the path = " << path.size() + << ", length of the path = " << length(path) + << std::endl; +} + +inline std::string GeodesicAlgorithmBase::name() +{ + switch(m_type) + { + case EXACT: + return "exact"; + case DIJKSTRA: + return "dijkstra"; + case SUBDIVISION: + return "subdivision"; + default: + case UNDEFINED_ALGORITHM: + return "undefined"; + } +} + +inline void GeodesicAlgorithmBase::geodesic(SurfacePoint& source, + SurfacePoint& destination, + std::vector& path) //lazy people can find geodesic path with one function call +{ + std::vector sources(1, source); + std::vector stop_points(1, destination); + double const max_propagation_distance = GEODESIC_INF; + + propagate(sources, + max_propagation_distance, + &stop_points); + + trace_back(destination, path); +} + +inline void GeodesicAlgorithmBase::geodesic(std::vector& sources, + std::vector& destinations, + std::vector >& paths) //lazy people can find geodesic paths with one function call +{ + double const max_propagation_distance = GEODESIC_INF; + + propagate(sources, + max_propagation_distance, + &destinations); //we use desinations as stop points + + paths.resize(destinations.size()); + + for(unsigned i=0; i* stop_points, + double stop_distance) +{ + m_max_propagation_distance = stop_distance; + + if(!stop_points) + { + m_stop_vertices.clear(); + return; + } + + m_stop_vertices.resize(stop_points->size()); + + std::vector possible_vertices; + for(unsigned i = 0; i < stop_points->size(); ++i) + { + SurfacePoint* point = &(*stop_points)[i]; + + possible_vertices.clear(); + m_mesh->closest_vertices(point, &possible_vertices); + + vertex_pointer closest_vertex = NULL; + double min_distance = 1e100; + for(unsigned j = 0; j < possible_vertices.size(); ++j) + { + double distance = point->distance(possible_vertices[j]); + if(distance < min_distance) + { + min_distance = distance; + closest_vertex = possible_vertices[j]; + } + } + assert(closest_vertex); + + m_stop_vertices[i].first = closest_vertex; + m_stop_vertices[i].second = min_distance; + } +} + + + +class GeodesicAlgorithmExact : public GeodesicAlgorithmBase +{ +public: + GeodesicAlgorithmExact(geodesic::Mesh* mesh): + GeodesicAlgorithmBase(mesh), + m_memory_allocator(mesh->edges().size(), mesh->edges().size()), + m_edge_interval_lists(mesh->edges().size()) + { + m_type = EXACT; + + for(unsigned i=0; iedges()[i]); + } + }; + + ~GeodesicAlgorithmExact(){}; + + void propagate(std::vector& sources, + double max_propagation_distance = GEODESIC_INF, //propagation algorithm stops after reaching the certain distance from the source + std::vector* stop_points = NULL); //or after ensuring that all the stop_points are covered + + void trace_back(SurfacePoint& destination, //trace back piecewise-linear path + std::vector& path); + + unsigned best_source(SurfacePoint& point, //quickly find what source this point belongs to and what is the distance to this source + double& best_source_distance); + + void print_statistics(); + +private: + typedef std::set IntervalQueue; + + void update_list_and_queue(list_pointer list, + IntervalWithStop* candidates, //up to two candidates + unsigned num_candidates); + + unsigned compute_propagated_parameters(double pseudo_x, + double pseudo_y, + double d, //parameters of the interval + double start, + double end, //start/end of the interval + double alpha, //corner angle + double L, //length of the new edge + bool first_interval, //if it is the first interval on the edge + bool last_interval, + bool turn_left, + bool turn_right, + IntervalWithStop* candidates); //if it is the last interval on the edge + + void construct_propagated_intervals(bool invert, + edge_pointer edge, + face_pointer face, //constructs iNew from the rest of the data + IntervalWithStop* candidates, + unsigned& num_candidates, + interval_pointer source_interval); + + double compute_positive_intersection(double start, + double pseudo_x, + double pseudo_y, + double sin_alpha, + double cos_alpha); //used in construct_propagated_intervals + + unsigned intersect_intervals(interval_pointer zero, + IntervalWithStop* one); //intersecting two intervals with up to three intervals in the end + + interval_pointer best_first_interval(SurfacePoint& point, + double& best_total_distance, + double& best_interval_position, + unsigned& best_source_index); + + bool check_stop_conditions(unsigned& index); + + void clear() + { + m_memory_allocator.clear(); + m_queue.clear(); + for(unsigned i=0; iid()]; + }; + + void set_sources(std::vector& sources) + { + m_sources.initialize(sources); + } + + void initialize_propagation_data(); + + void list_edges_visible_from_source(MeshElementBase* p, + std::vector& storage); //used in initialization + + long visible_from_source(SurfacePoint& point); //used in backtracing + + void best_point_on_the_edge_set(SurfacePoint& point, + std::vector const& storage, + interval_pointer& best_interval, + double& best_total_distance, + double& best_interval_position); + + void possible_traceback_edges(SurfacePoint& point, + std::vector& storage); + + bool erase_from_queue(interval_pointer p); + + IntervalQueue m_queue; //interval queue + + MemoryAllocator m_memory_allocator; //quickly allocate and deallocate intervals + std::vector m_edge_interval_lists; //every edge has its interval data + + enum MapType {OLD, NEW}; //used for interval intersection + MapType map[5]; + double start[6]; + interval_pointer i_new[5]; + + unsigned m_queue_max_size; //used for statistics + unsigned m_iterations; //used for statistics + + SortedSources m_sources; +}; + +inline void GeodesicAlgorithmExact::best_point_on_the_edge_set(SurfacePoint& point, + std::vector const& storage, + interval_pointer& best_interval, + double& best_total_distance, + double& best_interval_position) +{ + best_total_distance = 1e100; + for(unsigned i=0; ifind_closest_point(&point, + offset, + distance, + interval); + + if(distance < best_total_distance) + { + best_interval = interval; + best_total_distance = distance; + best_interval_position = offset; + } + } +} + +inline void GeodesicAlgorithmExact::possible_traceback_edges(SurfacePoint& point, + std::vector& storage) +{ + storage.clear(); + + if(point.type() == VERTEX) + { + vertex_pointer v = static_cast(point.base_element()); + for(unsigned i=0; iadjacent_faces().size(); ++i) + { + face_pointer f = v->adjacent_faces()[i]; + storage.push_back(f->opposite_edge(v)); + } + } + else if(point.type() == EDGE) + { + edge_pointer e = static_cast(point.base_element()); + for(unsigned i=0; iadjacent_faces().size(); ++i) + { + face_pointer f = e->adjacent_faces()[i]; + + storage.push_back(f->next_edge(e,e->v0())); + storage.push_back(f->next_edge(e,e->v1())); + } + } + else + { + face_pointer f = static_cast(point.base_element()); + storage.push_back(f->adjacent_edges()[0]); + storage.push_back(f->adjacent_edges()[1]); + storage.push_back(f->adjacent_edges()[2]); + } +} + + +inline long GeodesicAlgorithmExact::visible_from_source(SurfacePoint& point) //negative if not visible +{ + assert(point.type() != UNDEFINED_POINT); + + if(point.type() == EDGE) + { + edge_pointer e = static_cast(point.base_element()); + list_pointer list = interval_list(e); + double position = std::min(point.distance(e->v0()), e->length()); + interval_pointer interval = list->covering_interval(position); + //assert(interval); + if(interval && interval->visible_from_source()) + { + return (long)interval->source_index(); + } + else + { + return -1; + } + } + else if(point.type() == FACE) + { + return -1; + } + else if(point.type() == VERTEX) + { + vertex_pointer v = static_cast(point.base_element()); + for(unsigned i=0; iadjacent_edges().size(); ++i) + { + edge_pointer e = v->adjacent_edges()[i]; + list_pointer list = interval_list(e); + + double position = e->v0()->id() == v->id() ? 0.0 : e->length(); + interval_pointer interval = list->covering_interval(position); + if(interval && interval->visible_from_source()) + { + return (long)interval->source_index(); + } + } + + return -1; + } + + assert(0); + return 0; +} + +inline double GeodesicAlgorithmExact::compute_positive_intersection(double start, + double pseudo_x, + double pseudo_y, + double sin_alpha, + double cos_alpha) +{ + assert(pseudo_y < 0); + + double denominator = sin_alpha*(pseudo_x - start) - cos_alpha*pseudo_y; + if(denominator<0.0) + { + return -1.0; + } + + double numerator = -pseudo_y*start; + + if(numerator < 1e-30) + { + return 0.0; + } + + if(denominator < 1e-30) + { + return -1.0; + } + + return numerator/denominator; +} + +inline void GeodesicAlgorithmExact::list_edges_visible_from_source(MeshElementBase* p, + std::vector& storage) +{ + assert(p->type() != UNDEFINED_POINT); + + if(p->type() == FACE) + { + face_pointer f = static_cast(p); + for(unsigned i=0; i<3; ++i) + { + storage.push_back(f->adjacent_edges()[i]); + } + } + else if(p->type() == EDGE) + { + edge_pointer e = static_cast(p); + storage.push_back(e); + } + else //VERTEX + { + vertex_pointer v = static_cast(p); + for(unsigned i=0; iadjacent_edges().size(); ++i) + { + storage.push_back(v->adjacent_edges()[i]); + } + + } +} + +inline bool GeodesicAlgorithmExact::erase_from_queue(interval_pointer p) +{ + if(p->min() < GEODESIC_INF/10.0)// && p->min >= queue->begin()->first) + { + assert(m_queue.count(p)<=1); //the set is unique + + IntervalQueue::iterator it = m_queue.find(p); + + if(it != m_queue.end()) + { + m_queue.erase(it); + return true; + } + } + + return false; +} + +inline unsigned GeodesicAlgorithmExact::intersect_intervals(interval_pointer zero, + IntervalWithStop* one) //intersecting two intervals with up to three intervals in the end +{ + assert(zero->edge()->id() == one->edge()->id()); + assert(zero->stop() > one->start() && zero->start() < one->stop()); + assert(one->min() < GEODESIC_INF/10.0); + + double const local_epsilon = SMALLEST_INTERVAL_RATIO*one->edge()->length(); + + unsigned N=0; + if(zero->min() > GEODESIC_INF/10.0) + { + start[0] = zero->start(); + if(zero->start() < one->start() - local_epsilon) + { + map[0] = OLD; + start[1] = one->start(); + map[1] = NEW; + N = 2; + } + else + { + map[0] = NEW; + N = 1; + } + + if(zero->stop() > one->stop() + local_epsilon) + { + map[N] = OLD; //"zero" interval + start[N++] = one->stop(); + } + + start[N+1] = zero->stop(); + return N; + } + + double const local_small_epsilon = 1e-8*one->edge()->length(); + + double D = zero->d() - one->d(); + double x0 = zero->pseudo_x(); + double x1 = one->pseudo_x(); + double R0 = x0*x0 + zero->pseudo_y()*zero->pseudo_y(); + double R1 = x1*x1 + one->pseudo_y()*one->pseudo_y(); + + double inter[2]; //points of intersection + char Ninter=0; //number of the points of the intersection + + if(std::abs(D)local_small_epsilon) + { + inter[0] = (R1 - R0)/(2.*denom); //one solution + Ninter = 1; + } + } + else + { + double D2 = D*D; + double Q = 0.5*(R1-R0-D2); + double X = x0 - x1; + + double A = X*X - D2; + double B = Q*X + D2*x0; + double C = Q*Q - D2*R0; + + if (std::abs(A)local_small_epsilon) + { + inter[0] = -C/B; //one solution + Ninter = 1; + } + } + else + { + double det = B*B-A*C; + if(det>local_small_epsilon*local_small_epsilon) //two roots + { + det = sqrt(det); + if(A>0.0) //make sure that the roots are ordered + { + inter[0] = (-B - det)/A; + inter[1] = (-B + det)/A; + } + else + { + inter[0] = (-B + det)/A; + inter[1] = (-B - det)/A; + } + + if(inter[1] - inter[0] > local_small_epsilon) + { + Ninter = 2; + } + else + { + Ninter = 1; + } + } + else if(det>=0.0) //single root + { + inter[0] = -B/A; + Ninter = 1; + } + } + } + //---------------------------find possible intervals--------------------------------------- + double left = std::max(zero->start(), one->start()); //define left and right boundaries of the intersection of the intervals + double right = std::min(zero->stop(), one->stop()); + + double good_start[4]; //points of intersection within the (left, right) limits +"left" + "right" + good_start[0] = left; + char Ngood_start=1; //number of the points of the intersection + + for(char i=0; i left + local_epsilon && x < right - local_epsilon) + { + good_start[Ngood_start++] = x; + } + } + good_start[Ngood_start++] = right; + + MapType mid_map[3]; + for(char i=0; isignal(mid) <= one->signal(mid) ? OLD : NEW; + } + + //-----------------------------------output---------------------------------- + N = 0; + if(zero->start() < left - local_epsilon) //additional "zero" interval + { + if(mid_map[0] == OLD) //first interval in the map is already the old one + { + good_start[0] = zero->start(); + } + else + { + map[N] = OLD; //"zero" interval + start[N++] = zero->start(); + } + } + + for(long i=0;istop() > one->stop() + local_epsilon) + { + if(N==0 || map[N-1] == NEW) + { + map[N] = OLD; //"zero" interval + start[N++] = one->stop(); + } + } + + start[0] = zero->start(); // just to make sure that epsilons do not damage anything + //start[N] = zero->stop(); + + return N; +} + +inline void GeodesicAlgorithmExact::initialize_propagation_data() +{ + clear(); + + IntervalWithStop candidate; + std::vector edges_visible_from_source; + for(unsigned i=0; ibase_element(), + edges_visible_from_source); + + for(unsigned j=0; jlength(); + candidate.compute_min_distance(candidate.stop()); + candidate.direction() = Interval::FROM_SOURCE; + + update_list_and_queue(interval_list(e), &candidate, 1); + } + } +} + +inline void GeodesicAlgorithmExact::propagate(std::vector& sources, + double max_propagation_distance, //propagation algorithm stops after reaching the certain distance from the source + std::vector* stop_points) +{ + set_stop_conditions(stop_points, max_propagation_distance); + set_sources(sources); + initialize_propagation_data(); + + clock_t start = clock(); + + unsigned satisfied_index = 0; + + m_iterations = 0; //for statistics + m_queue_max_size = 0; + + IntervalWithStop candidates[2]; + + while(!m_queue.empty()) + { + m_queue_max_size = std::max(static_cast(m_queue.size()), m_queue_max_size); + + unsigned const check_period = 10; + if(++m_iterations % check_period == 0) //check if we covered all required vertices + { + if (check_stop_conditions(satisfied_index)) + { + break; + } + } + + interval_pointer min_interval = *m_queue.begin(); + m_queue.erase(m_queue.begin()); + edge_pointer edge = min_interval->edge(); + list_pointer list = interval_list(edge); + + assert(min_interval->d() < GEODESIC_INF); + + bool const first_interval = min_interval->start() == 0.0; + //bool const last_interval = min_interval->stop() == edge->length(); + bool const last_interval = min_interval->next() == NULL; + + bool const turn_left = edge->v0()->saddle_or_boundary(); + bool const turn_right = edge->v1()->saddle_or_boundary(); + + for(unsigned i=0; iadjacent_faces().size(); ++i) //two possible faces to propagate + { + if(!edge->is_boundary()) //just in case, always propagate boundary edges + { + if((i == 0 && min_interval->direction() == Interval::FROM_FACE_0) || + (i == 1 && min_interval->direction() == Interval::FROM_FACE_1)) + { + continue; + } + } + + face_pointer face = edge->adjacent_faces()[i]; //if we come from 1, go to 2 + edge_pointer next_edge = face->next_edge(edge,edge->v0()); + + unsigned num_propagated = compute_propagated_parameters(min_interval->pseudo_x(), + min_interval->pseudo_y(), + min_interval->d(), //parameters of the interval + min_interval->start(), + min_interval->stop(), //start/end of the interval + face->vertex_angle(edge->v0()), //corner angle + next_edge->length(), //length of the new edge + first_interval, //if it is the first interval on the edge + last_interval, + turn_left, + turn_right, + candidates); //if it is the last interval on the edge + bool propagate_to_right = true; + + if(num_propagated) + { + if(candidates[num_propagated-1].stop() != next_edge->length()) + { + propagate_to_right = false; + } + + bool const invert = next_edge->v0()->id() != edge->v0()->id(); //if the origins coinside, do not invert intervals + + construct_propagated_intervals(invert, //do not inverse + next_edge, + face, + candidates, + num_propagated, + min_interval); + + update_list_and_queue(interval_list(next_edge), + candidates, + num_propagated); + } + + if(propagate_to_right) + { + //propogation to the right edge + double length = edge->length(); + next_edge = face->next_edge(edge,edge->v1()); + + num_propagated = compute_propagated_parameters(length - min_interval->pseudo_x(), + min_interval->pseudo_y(), + min_interval->d(), //parameters of the interval + length - min_interval->stop(), + length - min_interval->start(), //start/end of the interval + face->vertex_angle(edge->v1()), //corner angle + next_edge->length(), //length of the new edge + last_interval, //if it is the first interval on the edge + first_interval, + turn_right, + turn_left, + candidates); //if it is the last interval on the edge + + if(num_propagated) + { + bool const invert = next_edge->v0()->id() != edge->v1()->id(); //if the origins coinside, do not invert intervals + + construct_propagated_intervals(invert, //do not inverse + next_edge, + face, + candidates, + num_propagated, + min_interval); + + update_list_and_queue(interval_list(next_edge), + candidates, + num_propagated); + } + } + } + } + + m_propagation_distance_stopped = m_queue.empty() ? GEODESIC_INF : (*m_queue.begin())->min(); + clock_t stop = clock(); + m_time_consumed = (static_cast(stop)-static_cast(start))/CLOCKS_PER_SEC; + +/* for(unsigned i=0; ifirst(); + assert(p->start() == 0.0); + while(p->next()) + { + assert(p->stop() == p->next()->start()); + assert(p->d() < GEODESIC_INF); + p = p->next(); + } + }*/ +} + + +inline bool GeodesicAlgorithmExact::check_stop_conditions(unsigned& index) +{ + double queue_distance = (*m_queue.begin())->min(); + if(queue_distance < stop_distance()) + { + return false; + } + + while(index < m_stop_vertices.size()) + { + vertex_pointer v = m_stop_vertices[index].first; + edge_pointer edge = v->adjacent_edges()[0]; //take any edge + + double distance = edge->v0()->id() == v->id() ? + interval_list(edge)->signal(0.0) : + interval_list(edge)->signal(edge->length()); + + if(queue_distance < distance + m_stop_vertices[index].second) + { + return false; + } + + ++index; + } + return true; +} + + +inline void GeodesicAlgorithmExact::update_list_and_queue(list_pointer list, + IntervalWithStop* candidates, //up to two candidates + unsigned num_candidates) +{ + assert(num_candidates <= 2); + //assert(list->first() != NULL); + edge_pointer edge = list->edge(); + double const local_epsilon = SMALLEST_INTERVAL_RATIO * edge->length(); + + if(list->first() == NULL) + { + interval_pointer* p = &list->first(); + IntervalWithStop* first; + IntervalWithStop* second; + + if(num_candidates == 1) + { + first = candidates; + second = candidates; + first->compute_min_distance(first->stop()); + } + else + { + if(candidates->start() <= (candidates+1)->start()) + { + first = candidates; + second = candidates+1; + } + else + { + first = candidates+1; + second = candidates; + } + assert(first->stop() == second->start()); + + first->compute_min_distance(first->stop()); + second->compute_min_distance(second->stop()); + } + + if(first->start() > 0.0) + { + *p = m_memory_allocator.allocate(); + (*p)->initialize(edge); + p = &(*p)->next(); + } + + *p = m_memory_allocator.allocate(); + memcpy(*p,first,sizeof(Interval)); + m_queue.insert(*p); + + if(num_candidates == 2) + { + p = &(*p)->next(); + *p = m_memory_allocator.allocate(); + memcpy(*p,second,sizeof(Interval)); + m_queue.insert(*p); + } + + if(second->stop() < edge->length()) + { + p = &(*p)->next(); + *p = m_memory_allocator.allocate(); + (*p)->initialize(edge); + (*p)->start() = second->stop(); + } + else + { + (*p)->next() = NULL; + } + return; + } + + bool propagate_flag; + + for(unsigned i=0; ifirst(); + assert(p->start() == 0.0); + + while(p != NULL && p->stop() - local_epsilon < q->start()) + { + p = p->next(); + } + + while(p != NULL && p->start() < q->stop() - local_epsilon) //go through all old intervals + { + unsigned const N = intersect_intervals(p, q); //interset two intervals + + if(N == 1) + { + if(map[0]==OLD) //if "p" is always better, we do not need to update anything) + { + if(previous) //close previous interval and put in into the queue + { + previous->next() = p; + previous->compute_min_distance(p->start()); + m_queue.insert(previous); + previous = NULL; + } + + p = p->next(); + + } + else if(previous) //extend previous interval to cover everything; remove p + { + previous->next() = p->next(); + erase_from_queue(p); + m_memory_allocator.deallocate(p); + + p = previous->next(); + } + else //p becomes "previous" + { + previous = p; + interval_pointer next = p->next(); + erase_from_queue(p); + + memcpy(previous,q,sizeof(Interval)); + + previous->start() = start[0]; + previous->next() = next; + + p = next; + } + continue; + } + + //update_flag = true; + + Interval swap(*p); //used for swapping information + propagate_flag = erase_from_queue(p); + + for(unsigned j=1; jnext() = p; + previous->compute_min_distance(previous->stop()); + m_queue.insert(previous); + previous = NULL; + } + i_new[0] = p; + p->next() = i_new[1]; + p->start() = start[0]; + } + else if(previous) //extend previous interval to cover everything; remove p + { + i_new[0] = previous; + previous->next() = i_new[1]; + m_memory_allocator.deallocate(p); + previous = NULL; + } + else //p becomes "previous" + { + i_new[0] = p; + memcpy(p,q,sizeof(Interval)); + + p->next() = i_new[1]; + p->start() = start[0]; + } + + assert(!previous); + + for(unsigned j=1; jnext() = swap.next(); + } + else + { + current_interval->next() = i_new[j+1]; + } + + current_interval->start() = start[j]; + } + + for(unsigned j=0; jcompute_min_distance(current_interval->stop()); //compute minimal distance + + if(map[j]==NEW || (map[j]==OLD && propagate_flag)) + { + m_queue.insert(current_interval); + } + } + } + + p = swap.next(); + } + + if(previous) //close previous interval and put in into the queue + { + previous->compute_min_distance(previous->stop()); + m_queue.insert(previous); + previous = NULL; + } + } +} + +inline unsigned GeodesicAlgorithmExact::compute_propagated_parameters(double pseudo_x, + double pseudo_y, + double d, //parameters of the interval + double begin, + double end, //start/end of the interval + double alpha, //corner angle + double L, //length of the new edge + bool first_interval, //if it is the first interval on the edge + bool last_interval, + bool turn_left, + bool turn_right, + IntervalWithStop* candidates) //if it is the last interval on the edge +{ + assert(pseudo_y<=0.0); + assert(dstart() = 0.0; + p->stop() = L; + p->d() = d - pseudo_x; + p->pseudo_x() = 0.0; + p->pseudo_y() = 0.0; + return 1; + } + else if(last_interval && pseudo_x >= end) + { + p->start() = 0.0; + p->stop() = L; + p->d() = d + pseudo_x-end; + p->pseudo_x() = end*cos(alpha); + p->pseudo_y() = -end*sin(alpha); + return 1; + } + else if(pseudo_x >= begin && pseudo_x <= end) + { + p->start() = 0.0; + p->stop() = L; + p->d() = d; + p->pseudo_x() = pseudo_x*cos(alpha); + p->pseudo_y() = -pseudo_x*sin(alpha); + return 1; + } + else + { + return 0; + } + } + + double sin_alpha = sin(alpha); + double cos_alpha = cos(alpha); + + //important: for the first_interval, this function returns zero only if the new edge is "visible" from the source + //if the new edge can be covered only after turn_over, the value is negative (-1.0) + double L1 = compute_positive_intersection(begin, + pseudo_x, + pseudo_y, + sin_alpha, + cos_alpha); + + if(L1 < 0 || L1 >= L) + { + if(first_interval && turn_left) + { + p->start() = 0.0; + p->stop() = L; + p->d() = d + sqrt(pseudo_x*pseudo_x + pseudo_y*pseudo_y); + p->pseudo_y() = 0.0; + p->pseudo_x() = 0.0; + return 1; + } + else + { + return 0; + } + } + + double L2 = compute_positive_intersection(end, + pseudo_x, + pseudo_y, + sin_alpha, + cos_alpha); + + if(L2 < 0 || L2 >= L) + { + p->start() = L1; + p->stop() = L; + p->d() = d; + p->pseudo_x() = cos_alpha*pseudo_x + sin_alpha*pseudo_y; + p->pseudo_y() = -sin_alpha*pseudo_x + cos_alpha*pseudo_y; + + return 1; + } + + p->start() = L1; + p->stop() = L2; + p->d() = d; + p->pseudo_x() = cos_alpha*pseudo_x + sin_alpha*pseudo_y; + p->pseudo_y() = -sin_alpha*pseudo_x + cos_alpha*pseudo_y; + assert(p->pseudo_y() <= 0.0); + + if(!(last_interval && turn_right)) + { + return 1; + } + else + { + p = candidates + 1; + + p->start() = L2; + p->stop() = L; + double dx = pseudo_x - end; + p->d() = d + sqrt(dx*dx + pseudo_y*pseudo_y); + p->pseudo_x() = end*cos_alpha; + p->pseudo_y() = -end*sin_alpha; + + return 2; + } +} + +inline void GeodesicAlgorithmExact::construct_propagated_intervals(bool invert, + edge_pointer edge, + face_pointer face, //constructs iNew from the rest of the data + IntervalWithStop* candidates, + unsigned& num_candidates, + interval_pointer source_interval) //up to two candidates +{ + double edge_length = edge->length(); + double local_epsilon = SMALLEST_INTERVAL_RATIO * edge_length; + + //kill very small intervals in order to avoid precision problems + if(num_candidates == 2) + { + double start = std::min(candidates->start(), (candidates+1)->start()); + double stop = std::max(candidates->stop(), (candidates+1)->stop()); + if(candidates->stop()-candidates->start() < local_epsilon) // kill interval 0 + { + *candidates = *(candidates+1); + num_candidates = 1; + candidates->start() = start; + candidates->stop() = stop; + } + else if ((candidates+1)->stop() - (candidates+1)->start() < local_epsilon) + { + num_candidates = 1; + candidates->start() = start; + candidates->stop() = stop; + } + } + + IntervalWithStop* first; + IntervalWithStop* second; + if(num_candidates == 1) + { + first = candidates; + second = candidates; + } + else + { + if(candidates->start() <= (candidates+1)->start()) + { + first = candidates; + second = candidates+1; + } + else + { + first = candidates+1; + second = candidates; + } + assert(first->stop() == second->start()); + } + + if(first->start() < local_epsilon) + { + first->start() = 0.0; + } + if(edge_length - second->stop() < local_epsilon) + { + second->stop() = edge_length; + } + + //invert intervals if necessary; fill missing data and set pointers correctly + Interval::DirectionType direction = edge->adjacent_faces()[0]->id() == face->id() ? + Interval::FROM_FACE_0 : + Interval::FROM_FACE_1; + + if(!invert) //in this case everything is straighforward, we do not have to invert the intervals + { + for(unsigned i=0; inext() = (i == num_candidates - 1) ? NULL : candidates + i + 1; + p->edge() = edge; + p->direction() = direction; + p->source_index() = source_interval->source_index(); + + p->min() = 0.0; //it will be changed later on + + assert(p->start() < p->stop()); + } + } + else //now we have to invert the intervals + { + for(unsigned i=0; inext() = (i == 0) ? NULL : candidates + i - 1; + p->edge() = edge; + p->direction() = direction; + p->source_index() = source_interval->source_index(); + + double length = edge_length; + p->pseudo_x() = length - p->pseudo_x(); + + double start = length - p->stop(); + p->stop() = length - p->start(); + p->start() = start; + + p->min() = 0; + + assert(p->start() < p->stop()); + assert(p->start() >= 0.0); + assert(p->stop() <= edge->length()); + } + } +} + + +inline unsigned GeodesicAlgorithmExact::best_source(SurfacePoint& point, //quickly find what source this point belongs to and what is the distance to this source + double& best_source_distance) +{ + double best_interval_position; + unsigned best_source_index; + + best_first_interval(point, + best_source_distance, + best_interval_position, + best_source_index); + + return best_source_index; +} + +inline interval_pointer GeodesicAlgorithmExact::best_first_interval(SurfacePoint& point, + double& best_total_distance, + double& best_interval_position, + unsigned& best_source_index) +{ + assert(point.type() != UNDEFINED_POINT); + + interval_pointer best_interval = NULL; + best_total_distance = GEODESIC_INF; + + if(point.type() == EDGE) + { + edge_pointer e = static_cast(point.base_element()); + list_pointer list = interval_list(e); + + best_interval_position = point.distance(e->v0()); + best_interval = list->covering_interval(best_interval_position); + if(best_interval) + { + //assert(best_interval && best_interval->d() < GEODESIC_INF); + best_total_distance = best_interval->signal(best_interval_position); + best_source_index = best_interval->source_index(); + } + } + else if(point.type() == FACE) + { + face_pointer f = static_cast(point.base_element()); + for(unsigned i=0; i<3; ++i) + { + edge_pointer e = f->adjacent_edges()[i]; + list_pointer list = interval_list(e); + + double offset; + double distance; + interval_pointer interval; + + list->find_closest_point(&point, + offset, + distance, + interval); + + if(interval && distance < best_total_distance) + { + best_interval = interval; + best_total_distance = distance; + best_interval_position = offset; + best_source_index = interval->source_index(); + } + } + + //check for all sources that might be located inside this face + SortedSources::sorted_iterator_pair local_sources = m_sources.sources(f); + for(SortedSources::sorted_iterator it=local_sources.first; it != local_sources.second; ++it) + { + SurfacePointWithIndex* source = *it; + double distance = point.distance(source); + if(distance < best_total_distance) + { + best_interval = NULL; + best_total_distance = distance; + best_interval_position = 0.0; + best_source_index = source->index(); + } + } + } + else if(point.type() == VERTEX) + { + vertex_pointer v = static_cast(point.base_element()); + for(unsigned i=0; iadjacent_edges().size(); ++i) + { + edge_pointer e = v->adjacent_edges()[i]; + list_pointer list = interval_list(e); + + double position = e->v0()->id() == v->id() ? 0.0 : e->length(); + interval_pointer interval = list->covering_interval(position); + if(interval) + { + double distance = interval->signal(position); + + if(distance < best_total_distance) + { + best_interval = interval; + best_total_distance = distance; + best_interval_position = position; + best_source_index = interval->source_index(); + } + } + } + } + + if(best_total_distance > m_propagation_distance_stopped) //result is unreliable + { + best_total_distance = GEODESIC_INF; + return NULL; + } + else + { + return best_interval; + } +} + +inline void GeodesicAlgorithmExact::trace_back(SurfacePoint& destination, //trace back piecewise-linear path + std::vector& path) +{ + path.clear(); + double best_total_distance; + double best_interval_position; + unsigned source_index = std::numeric_limits::max(); + interval_pointer best_interval = best_first_interval(destination, + best_total_distance, + best_interval_position, + source_index); + + if(best_total_distance >= GEODESIC_INF/2.0) //unable to find the right path + { + return; + } + + path.push_back(destination); + + if(best_interval) //if we did not hit the face source immediately + { + std::vector possible_edges; + possible_edges.reserve(10); + + while(visible_from_source(path.back()) < 0) //while this point is not in the direct visibility of some source (if we are inside the FACE, we obviously hit the source) + { + SurfacePoint& q = path.back(); + + possible_traceback_edges(q, possible_edges); + + interval_pointer interval; + double total_distance; + double position; + + best_point_on_the_edge_set(q, + possible_edges, + interval, + total_distance, + position); + + //std::cout << total_distance + length(path) << std::endl; + assert(total_distancesource_index(); + + edge_pointer e = interval->edge(); + double local_epsilon = SMALLEST_INTERVAL_RATIO*e->length(); + if(position < local_epsilon) + { + path.push_back(SurfacePoint(e->v0())); + } + else if(position > e->length()-local_epsilon) + { + path.push_back(SurfacePoint(e->v1())); + } + else + { + double normalized_position = position/e->length(); + path.push_back(SurfacePoint(e, normalized_position)); + } + } + } + + SurfacePoint& source = static_cast(m_sources[source_index]); + if(path.back().distance(&source) > 0) + { + path.push_back(source); + } +} + +inline void GeodesicAlgorithmExact::print_statistics() +{ + GeodesicAlgorithmBase::print_statistics(); + + unsigned interval_counter = 0; + for(unsigned i=0; i +IGL_INLINE void igl::exact_geodesic( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + const Eigen::MatrixBase &VS, + const Eigen::MatrixBase &FS, + const Eigen::MatrixBase &VT, + const Eigen::MatrixBase &FT, + Eigen::PlainObjectBase &D) +{ + assert(V.cols() == 3 && F.cols() == 3 && "Only support 3D triangle mesh"); + assert(VS.cols() ==1 && FS.cols() == 1 && VT.cols() == 1 && FT.cols() ==1 && "Only support one dimensional inputs"); + std::vector points(V.rows() * V.cols()); + std::vector faces(F.rows() * F.cols()); + for (int i = 0; i < points.size(); i++) + { + points[i] = V(i / 3, i % 3); + } + for (int i = 0; i < faces.size(); i++) + { + faces[i] = F(i / 3, i % 3); + } + + igl::geodesic::Mesh mesh; + mesh.initialize_mesh_data(points, faces); + igl::geodesic::GeodesicAlgorithmExact exact_algorithm(&mesh); + + std::vector source(VS.rows() + FS.rows()); + std::vector target(VT.rows() + FT.rows()); + for (int i = 0; i < VS.rows(); i++) + { + source[i] = (igl::geodesic::SurfacePoint(&mesh.vertices()[VS(i)])); + } + for (int i = 0; i < FS.rows(); i++) + { + source[i] = (igl::geodesic::SurfacePoint(&mesh.faces()[FS(i)])); + } + + for (int i = 0; i < VT.rows(); i++) + { + target[i] = (igl::geodesic::SurfacePoint(&mesh.vertices()[VT(i)])); + } + for (int i = 0; i < FT.rows(); i++) + { + target[i] = (igl::geodesic::SurfacePoint(&mesh.faces()[FT(i)])); + } + + exact_algorithm.propagate(source); + std::vector path; + D.resize(target.size(), 1); + for (int i = 0; i < target.size(); i++) + { + exact_algorithm.trace_back(target[i], path); + D(i) = igl::geodesic::length(path); + } +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::exact_geodesic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix>(Eigen::MatrixBase> const &, Eigen::MatrixBase> const &, Eigen::MatrixBase> const &, Eigen::MatrixBase> const &, Eigen::MatrixBase> const &, Eigen::MatrixBase> const &, Eigen::PlainObjectBase> &); +template void igl::exact_geodesic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/exact_geodesic.h b/src/igl/exact_geodesic.h new file mode 100644 index 000000000..cec59de69 --- /dev/null +++ b/src/igl/exact_geodesic.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Zhongshi Jiang +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_EXACT_GEODESIC_H +#define IGL_EXACT_GEODESIC_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // Exact geodesic algorithm for triangular mesh with the implementation from https://code.google.com/archive/p/geodesic/, + // and the algorithm first described by Mitchell, Mount and Papadimitriou in 1987 + // + // Inputs: + // V #V by 3 list of 3D vertex positions + // F #F by 3 list of mesh faces + // VS #VS by 1 vector specifying indices of source vertices + // FS #FS by 1 vector specifying indices of source faces + // VT #VT by 1 vector specifying indices of target vertices + // FT #FT by 1 vector specifying indices of target faces + // Output: + // D #VT+#FT by 1 vector of geodesic distances of each target w.r.t. the nearest one in the source set + // + // Note: + // Specifying a face as target/source means its center. + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedVS, + typename DerivedFS, + typename DerivedVT, + typename DerivedFT, + typename DerivedD> + IGL_INLINE void exact_geodesic( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + const Eigen::MatrixBase &VS, + const Eigen::MatrixBase &FS, + const Eigen::MatrixBase &VT, + const Eigen::MatrixBase &FT, + Eigen::PlainObjectBase &D); +} + +#ifndef IGL_STATIC_LIBRARY +# include "exact_geodesic.cpp" +#endif + +#endif \ No newline at end of file diff --git a/src/igl/example_fun.cpp b/src/igl/example_fun.cpp new file mode 100644 index 000000000..9796c68e3 --- /dev/null +++ b/src/igl/example_fun.cpp @@ -0,0 +1,23 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "example_fun.h" +#include + +template +IGL_INLINE bool igl::example_fun(const Printable & input) +{ + using namespace std; + cout<<"example_fun: "<(const double& input); +template bool igl::example_fun(const int& input); +#endif diff --git a/src/igl/example_fun.h b/src/igl/example_fun.h new file mode 100644 index 000000000..16c9ec645 --- /dev/null +++ b/src/igl/example_fun.h @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EXAMPLE_FUN_H +#define IGL_EXAMPLE_FUN_H + +#include "igl_inline.h" + +namespace igl +{ + // This is an example of a function, it takes a templated parameter and + // shovels it into cout + // + // Templates: + // T type that supports + // Input: + // input some input of a Printable type + // Returns true for the sake of returning something + template + IGL_INLINE bool example_fun(const Printable & input); +} + +#ifndef IGL_STATIC_LIBRARY +# include "example_fun.cpp" +#endif + +#endif diff --git a/src/igl/exterior_edges.cpp b/src/igl/exterior_edges.cpp new file mode 100644 index 000000000..04c1e9b8a --- /dev/null +++ b/src/igl/exterior_edges.cpp @@ -0,0 +1,106 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "exterior_edges.h" +#include "oriented_facets.h" +#include "sort.h" +#include "unique_rows.h" + +#include +#include +#include +#include + +//template inline int sgn(T val) { +// return (T(0) < val) - (val < T(0)); +//} + +//static void mod2(std::pair, int>& p) +//{ +// using namespace std; +// // Be sure that sign of mod matches sign of argument +// p.second = p.second%2 ? sgn(p.second) : 0; +//} + +//// http://stackoverflow.com/a/5517869/148668 +//struct Compare +//{ +// int i; +// Compare(const int& i) : i(i) {} +//}; +//bool operator==(const std::pair,int>&p, const Compare& c) +//{ +// return c.i == p.second; +//} +//bool operator==(const Compare& c, const std::pair, int> &p) +//{ +// return c.i == p.second; +//} + +IGL_INLINE void igl::exterior_edges( + const Eigen::MatrixXi & F, + Eigen::MatrixXi & E) +{ + using namespace Eigen; + using namespace std; + assert(F.cols() == 3); + const size_t m = F.rows(); + MatrixXi all_E,sall_E,sort_order; + // Sort each edge by index + oriented_facets(F,all_E); + sort(all_E,2,true,sall_E,sort_order); + // Find unique edges + MatrixXi uE; + VectorXi IA,EMAP; + unique_rows(sall_E,uE,IA,EMAP); + VectorXi counts = VectorXi::Zero(uE.rows()); + for(size_t a = 0;a<3*m;a++) + { + counts(EMAP(a)) += (sort_order(a)==0?1:-1); + } + + E.resize(all_E.rows(),2); + { + int e = 0; + const size_t nue = uE.rows(); + // Append each unique edge with a non-zero amount of signed occurrences + for(size_t ue = 0; ue 0) + { + i = uE(ue,0); + j = uE(ue,1); + } + // Append edge for every repeated entry + const int abs_count = abs(count); + for(int k = 0;k +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EXTERIOR_EDGES_H +#define IGL_EXTERIOR_EDGES_H +#include "igl_inline.h" +#include +namespace igl +{ + // EXTERIOR_EDGES Determines boundary "edges" and also edges with an + // odd number of occurrences where seeing edge (i,j) counts as +1 and seeing + // the opposite edge (j,i) counts as -1 + // + // Inputs: + // F #F by simplex_size list of "faces" + // Outputs: + // E #E by simplex_size-1 list of exterior edges + // + IGL_INLINE void exterior_edges( + const Eigen::MatrixXi & F, + Eigen::MatrixXi & E); + // Inline version + IGL_INLINE Eigen::MatrixXi exterior_edges( const Eigen::MatrixXi & F); +} +#ifndef IGL_STATIC_LIBRARY +# include "exterior_edges.cpp" +#endif + +#endif diff --git a/src/igl/extract_manifold_patches.cpp b/src/igl/extract_manifold_patches.cpp new file mode 100644 index 000000000..92269e2c8 --- /dev/null +++ b/src/igl/extract_manifold_patches.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "extract_manifold_patches.h" +#include "unique_edge_map.h" +#include +#include +#include + +template< + typename DerivedF, + typename DerivedEMAP, + typename uE2EType, + typename DerivedP> +IGL_INLINE size_t igl::extract_manifold_patches( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& EMAP, + const std::vector >& uE2E, + Eigen::PlainObjectBase& P) +{ + assert(F.cols() == 3); + const size_t num_faces = F.rows(); + + auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; }; + auto face_and_corner_index_to_edge_index = [&](size_t fi, size_t ci) { + return ci*num_faces + fi; + }; + auto is_manifold_edge = [&](size_t fi, size_t ci) -> bool { + const size_t ei = face_and_corner_index_to_edge_index(fi, ci); + return uE2E[EMAP(ei, 0)].size() == 2; + }; + auto get_adj_face_index = [&](size_t fi, size_t ci) -> size_t { + const size_t ei = face_and_corner_index_to_edge_index(fi, ci); + const auto& adj_faces = uE2E[EMAP(ei, 0)]; + assert(adj_faces.size() == 2); + if (adj_faces[0] == ei) { + return edge_index_to_face_index(adj_faces[1]); + } else { + assert(adj_faces[1] == ei); + return edge_index_to_face_index(adj_faces[0]); + } + }; + + typedef typename DerivedP::Scalar Scalar; + const Scalar INVALID = std::numeric_limits::max(); + P.resize(num_faces,1); + P.setConstant(INVALID); + size_t num_patches = 0; + for (size_t i=0; i Q; + Q.push(i); + P(i,0) = num_patches; + while (!Q.empty()) { + const size_t fid = Q.front(); + Q.pop(); + for (size_t j=0; j<3; j++) { + if (is_manifold_edge(fid, j)) { + const size_t adj_fid = get_adj_face_index(fid, j); + if (P(adj_fid,0) == INVALID) { + Q.push(adj_fid); + P(adj_fid,0) = num_patches; + } + } + } + } + num_patches++; + } + assert((P.array() != INVALID).all()); + + return num_patches; +} + +template< + typename DerivedF, + typename DerivedP> +IGL_INLINE size_t igl::extract_manifold_patches( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& P) +{ + Eigen::MatrixXi E, uE; + Eigen::VectorXi EMAP; + std::vector > uE2E; + igl::unique_edge_map(F, E, uE, EMAP, uE2E); + return igl::extract_manifold_patches(F, EMAP, uE2E, P); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template unsigned long igl::extract_manifold_patches, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template size_t igl::extract_manifold_patches, Eigen::Matrix, unsigned long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template unsigned long igl::extract_manifold_patches, Eigen::Matrix, unsigned long, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template unsigned __int64 igl::extract_manifold_patches, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> &); +template unsigned __int64 igl::extract_manifold_patches, class Eigen::Matrix, unsigned __int64, class Eigen::Matrix>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> const &, class std::vector>, class std::allocator>>> const &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/extract_manifold_patches.h b/src/igl/extract_manifold_patches.h new file mode 100644 index 000000000..6bc25d537 --- /dev/null +++ b/src/igl/extract_manifold_patches.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_EXTRACT_MANIFOLD_PATCHES +#define IGL_EXTRACT_MANIFOLD_PATCHES + +#include "igl_inline.h" +#include +#include + +namespace igl { + // Extract a set of maximal patches from a given mesh. + // A maximal patch is a subset of the input faces that are connected via + // manifold edges; a patch is as large as possible. + // + // Inputs: + // F #F by 3 list representing triangles. + // EMAP #F*3 list of indices of unique undirected edges. + // uE2E #uE list of lists of indices into E of coexisting edges. + // + // Output: + // P #F list of patch incides. + // + // Returns: + // number of manifold patches. + template < + typename DerivedF, + typename DerivedEMAP, + typename uE2EType, + typename DerivedP> + IGL_INLINE size_t extract_manifold_patches( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& EMAP, + const std::vector >& uE2E, + Eigen::PlainObjectBase& P); + template < + typename DerivedF, + typename DerivedP> + IGL_INLINE size_t extract_manifold_patches( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& P); +} +#ifndef IGL_STATIC_LIBRARY +# include "extract_manifold_patches.cpp" +#endif + +#endif diff --git a/src/igl/extract_non_manifold_edge_curves.cpp b/src/igl/extract_non_manifold_edge_curves.cpp new file mode 100644 index 000000000..c4a5274a6 --- /dev/null +++ b/src/igl/extract_non_manifold_edge_curves.cpp @@ -0,0 +1,123 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "extract_non_manifold_edge_curves.h" +#include +#include +#include +#include +#include + +template< +typename DerivedF, +typename DerivedEMAP, +typename uE2EType > +IGL_INLINE void igl::extract_non_manifold_edge_curves( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& /*EMAP*/, + const std::vector >& uE2E, + std::vector >& curves) { + const size_t num_faces = F.rows(); + assert(F.cols() == 3); + //typedef std::pair Edge; + auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; }; + auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; }; + auto get_edge_end_points = [&](size_t ei, size_t& s, size_t& d) { + const size_t fi = edge_index_to_face_index(ei); + const size_t ci = edge_index_to_corner_index(ei); + s = F(fi, (ci+1)%3); + d = F(fi, (ci+2)%3); + }; + + curves.clear(); + const size_t num_unique_edges = uE2E.size(); + std::unordered_multimap vertex_edge_adjacency; + std::vector non_manifold_edges; + for (size_t i=0; i 0); + assert(vertex_edge_adjacency.count(d) > 0); + } + + auto expand_forward = [&](std::list& edge_curve, + size_t& front_vertex, size_t& end_vertex) { + while(vertex_edge_adjacency.count(front_vertex) == 2 && + front_vertex != end_vertex) { + auto adj_edges = vertex_edge_adjacency.equal_range(front_vertex); + for (auto itr = adj_edges.first; itr!=adj_edges.second; itr++) { + const size_t uei = itr->second; + assert(uE2E.at(uei).size() != 2); + const size_t ei = uE2E[uei][0]; + if (uei == edge_curve.back()) continue; + size_t s,d; + get_edge_end_points(ei, s, d); + edge_curve.push_back(uei); + if (s == front_vertex) { + front_vertex = d; + } else if (d == front_vertex) { + front_vertex = s; + } else { + throw "Invalid vertex/edge adjacency!"; + } + break; + } + } + }; + + auto expand_backward = [&](std::list& edge_curve, + size_t& front_vertex, size_t& end_vertex) { + while(vertex_edge_adjacency.count(front_vertex) == 2 && + front_vertex != end_vertex) { + auto adj_edges = vertex_edge_adjacency.equal_range(front_vertex); + for (auto itr = adj_edges.first; itr!=adj_edges.second; itr++) { + const size_t uei = itr->second; + assert(uE2E.at(uei).size() != 2); + const size_t ei = uE2E[uei][0]; + if (uei == edge_curve.front()) continue; + size_t s,d; + get_edge_end_points(ei, s, d); + edge_curve.push_front(uei); + if (s == front_vertex) { + front_vertex = d; + } else if (d == front_vertex) { + front_vertex = s; + } else { + throw "Invalid vertex/edge adjcency!"; + } + break; + } + } + }; + + std::vector visited(num_unique_edges, false); + for (const size_t i : non_manifold_edges) { + if (visited[i]) continue; + std::list edge_curve; + edge_curve.push_back(i); + + const auto& adj_edges = uE2E[i]; + assert(adj_edges.size() != 2); + const size_t ei = adj_edges[0]; + size_t s,d; + get_edge_end_points(ei, s, d); + + expand_forward(edge_curve, d, s); + expand_backward(edge_curve, s, d); + curves.emplace_back(edge_curve.begin(), edge_curve.end()); + for (auto itr = edge_curve.begin(); itr!=edge_curve.end(); itr++) { + visited[*itr] = true; + } + } + +} diff --git a/src/igl/extract_non_manifold_edge_curves.h b/src/igl/extract_non_manifold_edge_curves.h new file mode 100644 index 000000000..173e0c5a2 --- /dev/null +++ b/src/igl/extract_non_manifold_edge_curves.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NON_MANIFOLD_EDGE_CURVES +#define IGL_NON_MANIFOLD_EDGE_CURVES + +#include "igl_inline.h" +#include +#include + +namespace igl { + // Extract non-manifold curves from a given mesh. + // A non-manifold curves are a set of connected non-manifold edges that + // does not touch other non-manifold edges except at the end points. + // They are also maximal in the sense that they cannot be expanded by + // including more edges. + // + // Assumes the input mesh have all self-intersection resolved. See + // ``igl::cgal::remesh_self_intersection`` for more details. + // + // Inputs: + // F #F by 3 list representing triangles. + // EMAP #F*3 list of indices of unique undirected edges. + // uE2E #uE list of lists of indices into E of coexisting edges. + // + // Output: + // curves An array of arries of unique edge indices. + template< + typename DerivedF, + typename DerivedEMAP, + typename uE2EType> + IGL_INLINE void extract_non_manifold_edge_curves( + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& EMAP, + const std::vector >& uE2E, + std::vector >& curves); +} + +#ifndef IGL_STATIC_LIBRARY +# include "extract_non_manifold_edge_curves.cpp" +#endif + +#endif diff --git a/src/igl/face_areas.cpp b/src/igl/face_areas.cpp new file mode 100644 index 000000000..74bf7fce0 --- /dev/null +++ b/src/igl/face_areas.cpp @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "face_areas.h" +#include "edge_lengths.h" +#include "doublearea.h" + +template +IGL_INLINE void igl::face_areas( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& A) +{ + assert(T.cols() == 4); + DerivedA L; + edge_lengths(V,T,L); + return face_areas(L,A); +} + +template +IGL_INLINE void igl::face_areas( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase& A) +{ + return face_areas( + L,std::numeric_limits::quiet_NaN(),A); +} + +template +IGL_INLINE void igl::face_areas( + const Eigen::MatrixBase& L, + const typename DerivedL::Scalar doublearea_nan_replacement, + Eigen::PlainObjectBase& A) +{ + using namespace Eigen; + assert(L.cols() == 6); + const int m = L.rows(); + // (unsigned) face Areas (opposite vertices: 1 2 3 4) + Matrix + A0(m,1), A1(m,1), A2(m,1), A3(m,1); + Matrix + L0(m,3), L1(m,3), L2(m,3), L3(m,3); + L0<, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/face_areas.h b/src/igl/face_areas.h new file mode 100644 index 000000000..9676deadc --- /dev/null +++ b/src/igl/face_areas.h @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FACE_AREAS_H +#define IGL_FACE_AREAS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Constructs a list of face areas of faces opposite each index in a tet list + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // T #T by 3 list of tet mesh indices into V + // Outputs: + // A #T by 4 list of face areas corresponding to faces opposite vertices + // 0,1,2,3 + // + template + IGL_INLINE void face_areas( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& A); + // Compute tet-mesh face areas from edge lengths. + // + // Inputs: + // L #T by 6 list of tet-mesh edge lengths corresponding to edges + // [3 0],[3 1],[3 2],[1 2],[2 0],[0 1] + // Outputs: + // A #T by 4 list of face areas corresponding to faces opposite vertices + // 0,1,2,3: i.e. made of edges [123],[024],[015],[345] + // + // + template + IGL_INLINE void face_areas( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase& A); + // doublearea_nan_replacement See doublearea.h + template + IGL_INLINE void face_areas( + const Eigen::MatrixBase& L, + const typename DerivedL::Scalar doublearea_nan_replacement, + Eigen::PlainObjectBase& A); +} + +#ifndef IGL_STATIC_LIBRARY +# include "face_areas.cpp" +#endif + +#endif + + diff --git a/src/igl/face_occurrences.cpp b/src/igl/face_occurrences.cpp new file mode 100644 index 000000000..4ef43f822 --- /dev/null +++ b/src/igl/face_occurrences.cpp @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "face_occurrences.h" + +#include +#include "sort.h" +#include + +template +IGL_INLINE void igl::face_occurrences( + const std::vector > & F, + std::vector & C) +{ + using namespace std; + + // Get a list of sorted faces + vector > sortedF = F; + for(int i = 0; i < (int)F.size();i++) + { + sort(sortedF[i].begin(),sortedF[i].end()); + } + // Count how many times each sorted face occurs + map,int> counts; + for(int i = 0; i < (int)sortedF.size();i++) + { + if(counts.find(sortedF[i]) == counts.end()) + { + // initialize to count of 1 + counts[sortedF[i]] = 1; + }else + { + // increment count + counts[sortedF[i]]++; + assert(counts[sortedF[i]] == 2 && "Input should be manifold"); + } + } + + // Resize output to fit number of ones + C.resize(F.size()); + for(int i = 0;i< (int)F.size();i++) + { + // sorted face should definitely be in counts map + assert(counts.find(sortedF[i]) != counts.end()); + C[i] = counts[sortedF[i]]; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::face_occurrences(std::vector >, std::allocator > > > const&, std::vector >&); +template void igl::face_occurrences(std::vector >, std::allocator > > > const&, std::vector >&); +#endif diff --git a/src/igl/face_occurrences.h b/src/igl/face_occurrences.h new file mode 100644 index 000000000..de47b504c --- /dev/null +++ b/src/igl/face_occurrences.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FACE_OCCURRENCES +#define IGL_FACE_OCCURRENCES +#include "igl_inline.h" + +#include +namespace igl +{ + // Count the occruances of each face (row) in a list of face indices + // (irrespecitive of order) + // Inputs: + // F #F by simplex-size + // Outputs + // C #F list of counts + // Known bug: triangles/tets only (where ignoring order still gives simplex) + template + IGL_INLINE void face_occurrences( + const std::vector > & F, + std::vector & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "face_occurrences.cpp" +#endif + +#endif + + diff --git a/src/igl/faces_first.cpp b/src/igl/faces_first.cpp new file mode 100644 index 000000000..9c0fa0d0b --- /dev/null +++ b/src/igl/faces_first.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "faces_first.h" + +#include +#include + +template +IGL_INLINE void igl::faces_first( + const MatV & V, + const MatF & F, + MatV & RV, + MatF & RF, + VecI & IM) +{ + assert(&V != &RV); + assert(&F != &RF); + using namespace std; + using namespace Eigen; + vector in_face(V.rows()); + for(int i = 0; i +IGL_INLINE void igl::faces_first( + MatV & V, + MatF & F, + VecI & IM) +{ + MatV RV; + // Copying F may not be needed, seems RF = F is safe (whereas RV = V is not) + MatF RF; + igl::faces_first(V,F,RV,RF,IM); + V = RV; + F = RF; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::faces_first, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +#endif diff --git a/src/igl/faces_first.h b/src/igl/faces_first.h new file mode 100644 index 000000000..92d1d5565 --- /dev/null +++ b/src/igl/faces_first.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FACES_FIRST_H +#define IGL_FACES_FIRST_H +#include "igl_inline.h" +namespace igl +{ + // FACES_FIRST Reorder vertices so that vertices in face list come before + // vertices that don't appear in the face list. This is especially useful if + // the face list contains only surface faces and you want surface vertices + // listed before internal vertices + // + // [RV,RT,RF,IM] = faces_first(V,T,F); + // + // Templates: + // MatV matrix for vertex positions, e.g. MatrixXd + // MatF matrix for face indices, e.g. MatrixXi + // VecI vector for index map, e.g. VectorXi + // Input: + // V # vertices by 3 vertex positions + // F # faces by 3 list of face indices + // Output: + // RV # vertices by 3 vertex positions, order such that if the jth vertex is + // some face in F, and the kth vertex is not then j comes before k + // RF # faces by 3 list of face indices, reindexed to use RV + // IM #V by 1 list of indices such that: RF = IM(F) and RT = IM(T) + // and RV(IM,:) = V + // + // + // Example: + // // Tet mesh in (V,T,F) + // faces_first(V,F,IM); + // T = T.unaryExpr(bind1st(mem_fun( static_cast(&VectorXi::operator())), + // &IM)).eval(); + template + IGL_INLINE void faces_first( + const MatV & V, + const MatF & F, + MatV & RV, + MatF & RF, + VecI & IM); + // Virtual "in place" wrapper + template + IGL_INLINE void faces_first( + MatV & V, + MatF & F, + VecI & IM); +} + +#ifndef IGL_STATIC_LIBRARY +# include "faces_first.cpp" +#endif + +#endif diff --git a/src/igl/facet_components.cpp b/src/igl/facet_components.cpp new file mode 100644 index 000000000..9df7583e3 --- /dev/null +++ b/src/igl/facet_components.cpp @@ -0,0 +1,92 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "facet_components.h" +#include +#include +#include +template +IGL_INLINE void igl::facet_components( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C) +{ + using namespace std; + typedef typename DerivedF::Index Index; + vector > > TT; + vector > > TTi; + triangle_triangle_adjacency(F,TT,TTi); + Eigen::VectorXi counts; + return facet_components(TT,C,counts); +} + +template < + typename TTIndex, + typename DerivedC, + typename Derivedcounts> +IGL_INLINE void igl::facet_components( + const std::vector > > & TT, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & counts) +{ + using namespace std; + typedef TTIndex Index; + const Index m = TT.size(); + C.resize(m,1); + vector seen(m,false); + Index id = 0; + vector vcounts; + for(Index g = 0;g Q; + Q.push(g); + while(!Q.empty()) + { + const Index f = Q.front(); + Q.pop(); + if(seen[f]) + { + continue; + } + seen[f] = true; + vcounts[id]++; + C(f,0) = id; + // Face f's neighbor lists opposite opposite each corner + for(const auto & c : TT[f]) + { + // Each neighbor + for(const auto & n : c) + { + if(!seen[n]) + { + Q.push(n); + } + } + } + } + id++; + } + assert((size_t) id == vcounts.size()); + const size_t ncc = vcounts.size(); + assert((size_t)C.maxCoeff()+1 == ncc); + counts.resize(ncc,1); + for(size_t i = 0;i, Eigen::Matrix >(std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::facet_components, Eigen::Matrix >(std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::facet_components, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/facet_components.h b/src/igl/facet_components.h new file mode 100644 index 000000000..8fe28583c --- /dev/null +++ b/src/igl/facet_components.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef FACET_COMPONENTS_H +#define FACET_COMPONENTS_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Compute connected components of facets based on edge-edge adjacency. + // + // Inputs: + // F #F by 3 list of triangle indices + // Outputs: + // C #F list of connected component ids + template + IGL_INLINE void facet_components( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C); + // Inputs: + // TT #TT by 3 list of list of adjacency triangles (see + // triangle_triangle_adjacency.h) + // Outputs: + // C #F list of connected component ids + // counts #C list of number of facets in each components + template < + typename TTIndex, + typename DerivedC, + typename Derivedcounts> + IGL_INLINE void facet_components( + const std::vector > > & TT, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & counts); +} +#ifndef IGL_STATIC_LIBRARY +# include "facet_components.cpp" +#endif +#endif diff --git a/src/igl/false_barycentric_subdivision.cpp b/src/igl/false_barycentric_subdivision.cpp new file mode 100644 index 000000000..8ea807a50 --- /dev/null +++ b/src/igl/false_barycentric_subdivision.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "false_barycentric_subdivision.h" + +#include "verbose.h" +#include +#include + +template +IGL_INLINE void igl::false_barycentric_subdivision( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & VD, + Eigen::PlainObjectBase & FD) +{ + using namespace Eigen; + // Compute face barycenter + Eigen::MatrixXd BC; + igl::barycenter(V,F,BC); + + // Add the barycenters to the vertices + VD.resize(V.rows()+F.rows(),3); + VD.block(0,0,V.rows(),3) = V; + VD.block(V.rows(),0,F.rows(),3) = BC; + + // Each face is split four ways + FD.resize(F.rows()*3,3); + + for (unsigned i=0; i, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::false_barycentric_subdivision, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/false_barycentric_subdivision.h b/src/igl/false_barycentric_subdivision.h new file mode 100644 index 000000000..98426b572 --- /dev/null +++ b/src/igl/false_barycentric_subdivision.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ADD_BARYCENTER_H +#define IGL_ADD_BARYCENTER_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Refine the mesh by adding the barycenter of each face + // Inputs: + // V #V by 3 coordinates of the vertices + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // VD #V + #F by 3 coordinate of the vertices of the dual mesh + // The added vertices are added at the end of VD (should not be + // same references as (V,F) + // FD #F*3 by 3 faces of the dual mesh + // + template + IGL_INLINE void false_barycentric_subdivision( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & VD, + Eigen::PlainObjectBase & FD); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "false_barycentric_subdivision.cpp" +#endif + +#endif diff --git a/src/igl/fast_winding_number.cpp b/src/igl/fast_winding_number.cpp new file mode 100644 index 000000000..a69785feb --- /dev/null +++ b/src/igl/fast_winding_number.cpp @@ -0,0 +1,324 @@ +#include "fast_winding_number.h" +#include "octree.h" +#include "knn.h" +#include "parallel_for.h" +#include "PI.h" +#include + +namespace igl { + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const int expansion_order, + Eigen::PlainObjectBase& CM, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& EC) + { + typedef typename DerivedP::Scalar real_p; + typedef typename DerivedN::Scalar real_n; + typedef typename DerivedA::Scalar real_a; + typedef typename DerivedCM::Scalar real_cm; + typedef typename DerivedR::Scalar real_r; + typedef typename DerivedEC::Scalar real_ec; + + typedef Eigen::Matrix RowVec3p; + + int m = CH.size(); + int num_terms; + + assert(expansion_order < 3 && expansion_order >= 0 && "m must be less than n"); + if(expansion_order == 0){ + num_terms = 3; + } else if(expansion_order ==1){ + num_terms = 3 + 9; + } else if(expansion_order == 2){ + num_terms = 3 + 9 + 27; + } + + R.resize(m); + CM.resize(m,3); + EC.resize(m,num_terms); + EC.setZero(m,num_terms); + std::function< void(const int) > helper; + helper = [&helper, + &P,&N,&A,&expansion_order,&point_indices,&CH,&EC,&R,&CM] + (const int index)-> void + { + Eigen::Matrix masscenter; + masscenter << 0,0,0; + Eigen::Matrix zeroth_expansion; + zeroth_expansion << 0,0,0; + real_p areatotal = 0.0; + for(int j = 0; j < point_indices.at(index).size(); j++){ + int curr_point_index = point_indices.at(index).at(j); + + areatotal += A(curr_point_index); + masscenter += A(curr_point_index)*P.row(curr_point_index); + zeroth_expansion += A(curr_point_index)*N.row(curr_point_index); + } + + masscenter = masscenter/areatotal; + CM.row(index) = masscenter; + EC.block(index,0,1,3) = zeroth_expansion; + + real_r max_norm = 0; + real_r curr_norm; + + for(int i = 0; i < point_indices.at(index).size(); i++){ + //Get max distance from center of mass: + int curr_point_index = point_indices.at(index).at(i); + Eigen::Matrix point = + P.row(curr_point_index)-masscenter; + curr_norm = point.norm(); + if(curr_norm > max_norm){ + max_norm = curr_norm; + } + + //Calculate higher order terms if necessary + Eigen::Matrix TempCoeffs; + if(EC.cols() >= (3+9)){ + TempCoeffs = A(curr_point_index)*point.transpose()* + N.row(curr_point_index); + EC.block(index,3,1,9) += + Eigen::Map >(TempCoeffs.data(), + TempCoeffs.size()); + } + + if(EC.cols() == (3+9+27)){ + for(int k = 0; k < 3; k++){ + TempCoeffs = 0.5 * point(k) * (A(curr_point_index)* + point.transpose()*N.row(curr_point_index)); + EC.block(index,12+9*k,1,9) += Eigen::Map< + Eigen::Matrix >(TempCoeffs.data(), + TempCoeffs.size()); + } + } + } + + R(index) = max_norm; + if(CH(index,0) != -1) + { + for(int i = 0; i < 8; i++){ + int child = CH(index,i); + helper(child); + } + } + }; + helper(0); + } + + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CM, + const Eigen::MatrixBase& R, + const Eigen::MatrixBase& EC, + const Eigen::MatrixBase& Q, + const BetaType beta, + Eigen::PlainObjectBase& WN){ + + typedef typename DerivedP::Scalar real_p; + typedef typename DerivedN::Scalar real_n; + typedef typename DerivedA::Scalar real_a; + typedef typename DerivedCM::Scalar real_cm; + typedef typename DerivedR::Scalar real_r; + typedef typename DerivedEC::Scalar real_ec; + typedef typename DerivedQ::Scalar real_q; + typedef typename DerivedWN::Scalar real_wn; + + typedef Eigen::Matrix RowVec; + typedef Eigen::Matrix EC_3by3; + + auto direct_eval = [](const RowVec & loc, + const Eigen::Matrix & anorm){ + real_wn wn = (loc(0)*anorm(0)+loc(1)*anorm(1)+loc(2)*anorm(2)) + /(4.0*igl::PI*std::pow(loc.norm(),3)); + if(std::isnan(wn)){ + return 0.5; + }else{ + return wn; + } + }; + + auto expansion_eval = [&direct_eval](const RowVec & loc, + const Eigen::RowVectorXd & EC){ + real_wn wn = direct_eval(loc,EC.head<3>()); + double r = loc.norm(); + if(EC.size()>3){ + Eigen::Matrix SecondDerivative = + Eigen::Matrix::Identity()/(4.0*igl::PI*std::pow(r,3)); + SecondDerivative += -3.0*loc.transpose()*loc/(4.0*igl::PI*std::pow(r,5)); + Eigen::Matrix derivative_vector = + Eigen::Map >(SecondDerivative.data(), + SecondDerivative.size()); + wn += derivative_vector.cwiseProduct(EC.segment<9>(3)).sum(); + } + if(EC.size()>3+9){ + Eigen::Matrix ThirdDerivative; + for(int i = 0; i < 3; i++){ + ThirdDerivative = + 15.0*loc(i)*loc.transpose()*loc/(4.0*igl::PI*std::pow(r,7)); + Eigen::Matrix Diagonal; + Diagonal << loc(i), 0, 0, + 0, loc(i), 0, + 0, 0, loc(i); + Eigen::Matrix RowCol; + RowCol.setZero(3,3); + RowCol.row(i) = loc; + Eigen::Matrix RowColT = RowCol.transpose(); + RowCol = RowCol + RowColT; + ThirdDerivative += + -3.0/(4.0*igl::PI*std::pow(r,5))*(RowCol+Diagonal); + Eigen::Matrix derivative_vector = + Eigen::Map >(ThirdDerivative.data(), + ThirdDerivative.size()); + wn += derivative_vector.cwiseProduct( + EC.segment<9>(12 + i*9)).sum(); + } + } + return wn; + }; + + int m = Q.rows(); + WN.resize(m,1); + + std::function< real_wn(const RowVec, const std::vector) > helper; + helper = [&helper, + &P,&N,&A, + &point_indices,&CH, + &CM,&R,&EC,&beta, + &direct_eval,&expansion_eval] + (const RowVec query, const std::vector near_indices)-> real_wn + { + std::vector new_near_indices; + real_wn wn = 0; + for(int i = 0; i < near_indices.size(); i++){ + int index = near_indices.at(i); + //Leaf Case, Brute force + if(CH(index,0) == -1){ + for(int j = 0; j < point_indices.at(index).size(); j++){ + int curr_row = point_indices.at(index).at(j); + wn += direct_eval(P.row(curr_row)-query, + N.row(curr_row)*A(curr_row)); + } + } + //Non-Leaf Case + else { + for(int child = 0; child < 8; child++){ + int child_index = CH(index,child); + if(point_indices.at(child_index).size() > 0){ + if((CM.row(child_index)-query).norm() > beta*R(child_index)){ + if(CH(child_index,0) == -1){ + for(int j=0;j 0){ + wn += helper(query,new_near_indices); + } + return wn; + }; + + + if(beta > 0){ + std::vector near_indices_start = {0}; + igl::parallel_for(m,[&](int iter){ + WN(iter) = helper(Q.row(iter),near_indices_start); + },1000); + } else { + igl::parallel_for(m,[&](int iter){ + double wn = 0; + for(int j = 0; j + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + const int expansion_order, + const BetaType beta, + Eigen::PlainObjectBase& WN + ){ + typedef typename DerivedWN::Scalar real; + + std::vector > point_indices; + Eigen::Matrix CH; + Eigen::Matrix CN; + Eigen::Matrix W; + + octree(P,point_indices,CH,CN,W); + + Eigen::Matrix EC; + Eigen::Matrix CM; + Eigen::Matrix R; + + fast_winding_number(P,N,A,point_indices,CH,expansion_order,CM,R,EC); + fast_winding_number(P,N,A,point_indices,CH,CM,R,EC,Q,beta,WN); + } + + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN + ){ + fast_winding_number(P,N,A,Q,2,2.0,WN); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/igl/fast_winding_number.h b/src/igl/fast_winding_number.h new file mode 100644 index 000000000..6d4bc1ed7 --- /dev/null +++ b/src/igl/fast_winding_number.h @@ -0,0 +1,122 @@ +#ifndef IGL_FAST_WINDING_NUMBER +#define IGL_FAST_WINDING_NUMBER +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Generate the precomputation for the fast winding number for point data + // [Barill et. al 2018]. + // + // Given a set of 3D points P, with normals N, areas A, along with octree + // data, and an expansion order, we define a taylor series expansion at each + // octree cell. + // + // The octree data is designed to come from igl::octree, and the areas + // (if not obtained at scan time), may be calculated using + // igl::copyleft::cgal::point_areas. + // + // Inputs: + // P #P by 3 list of point locations + // N #P by 3 list of point normals + // A #P by 1 list of point areas + // point_indices a vector of vectors, where the ith entry is a vector of + // the indices into P that are the ith octree cell's points + // CH #OctreeCells by 8, where the ith row is the indices of + // the ith octree cell's children + // expansion_order the order of the taylor expansion. We support 0,1,2. + // Outputs: + // CM #OctreeCells by 3 list of each cell's center of mass + // R #OctreeCells by 1 list of each cell's maximum distance of any point + // to the center of mass + // EC #OctreeCells by #TaylorCoefficients list of expansion coefficients. + // (Note that #TaylorCoefficients = ∑_{i=1}^{expansion_order} 3^i) + // + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const int exansion_order, + Eigen::PlainObjectBase& CM, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& EC); + + // Evaluate the fast winding number for point data, having already done the + // the precomputation + // + // Inputs: + // P #P by 3 list of point locations + // N #P by 3 list of point normals + // A #P by 1 list of point areas + // point_indices a vector of vectors, where the ith entry is a vector of + // the indices into P that are the ith octree cell's points + // CH #OctreeCells by 8, where the ith row is the indices of + // the ith octree cell's children + // CM #OctreeCells by 3 list of each cell's center of mass + // R #OctreeCells by 1 list of each cell's maximum distance of any point + // to the center of mass + // EC #OctreeCells by #TaylorCoefficients list of expansion coefficients. + // (Note that #TaylorCoefficients = ∑_{i=1}^{expansion_order} 3^i) + // Q #Q by 3 list of query points for the winding number + // beta This is a Barnes-Hut style accuracy term that separates near feild + // from far field. The higher the beta, the more accurate and slower + // the evaluation. We reccommend using a beta value of 2. Note that + // for a beta value ≤ 0, we use the direct evaluation, rather than + // the fast approximation + // Outputs: + // WN #Q by 1 list of windinng number values at each query point + // + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CM, + const Eigen::MatrixBase& R, + const Eigen::MatrixBase& EC, + const Eigen::MatrixBase& Q, + const BetaType beta, + Eigen::PlainObjectBase& WN); + + // Evaluate the fast winding number for point data, with default expansion + // order and beta (both are set to 2). + // + // This function performes the precomputation and evaluation all in one. + // If you need to acess the precomuptation for repeated evaluations, use the + // two functions designed for exposed precomputation (described above). + // + // Inputs: + // P #P by 3 list of point locations + // N #P by 3 list of point normals + // A #P by 1 list of point areas + // Q #Q by 3 list of query points for the winding number + // beta This is a Barnes-Hut style accuracy term that separates near feild + // from far field. The higher the beta, the more accurate and slower + // the evaluation. We reccommend using a beta value of 2. + // expansion_order the order of the taylor expansion. We support 0,1,2. + // Outputs: + // WN #Q by 1 list of windinng number values at each query point + // + template + IGL_INLINE void fast_winding_number(const Eigen::MatrixBase& P, + const Eigen::MatrixBase& N, + const Eigen::MatrixBase& A, + const Eigen::MatrixBase& Q, + Eigen::PlainObjectBase& WN + ); +} +#ifndef IGL_STATIC_LIBRARY +# include "fast_winding_number.cpp" +#endif + +#endif + diff --git a/src/igl/file_contents_as_string.cpp b/src/igl/file_contents_as_string.cpp new file mode 100644 index 000000000..fd945dff2 --- /dev/null +++ b/src/igl/file_contents_as_string.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "file_contents_as_string.h" + +#include +#include +#include + +IGL_INLINE bool igl::file_contents_as_string( + const std::string file_name, + std::string & content) +{ + std::ifstream ifs(file_name.c_str()); + // Check that opening the stream worked successfully + if(!ifs.good()) + { + fprintf( + stderr, + "IOError: file_contents_as_string() cannot open %s\n", + file_name.c_str()); + return false; + } + // Stream file contents into string + content = std::string( + (std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + return true; +} + +IGL_INLINE std::string igl::file_contents_as_string( + const std::string file_name) +{ + std::string content; +#ifndef NDEBUG + bool ret = +#endif + file_contents_as_string(file_name,content); + assert(ret && "file_contents_as_string failed to read string from file"); + return content; +} diff --git a/src/igl/file_contents_as_string.h b/src/igl/file_contents_as_string.h new file mode 100644 index 000000000..22ce1fc53 --- /dev/null +++ b/src/igl/file_contents_as_string.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FILE_CONTENTS_AS_STRING_H +#define IGL_FILE_CONTENTS_AS_STRING_H +#include "igl_inline.h" + +#include +namespace igl +{ + // Read a files contents as plain text into a given string + // Inputs: + // file_name path to file to be read + // Outputs: + // content output string containing contents of the given file + // Returns true on succes, false on error + IGL_INLINE bool file_contents_as_string( + const std::string file_name, + std::string & content); + IGL_INLINE std::string file_contents_as_string( + const std::string file_name); +} + +#ifndef IGL_STATIC_LIBRARY +# include "file_contents_as_string.cpp" +#endif + +#endif diff --git a/src/igl/file_dialog_open.cpp b/src/igl/file_dialog_open.cpp new file mode 100644 index 000000000..66c2aca0e --- /dev/null +++ b/src/igl/file_dialog_open.cpp @@ -0,0 +1,87 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "file_dialog_open.h" +#include +#include + +#ifdef _WIN32 + #include + #undef max + #undef min + + #include +#endif + +IGL_INLINE std::string igl::file_dialog_open() +{ + const int FILE_DIALOG_MAX_BUFFER = 1024; + char buffer[FILE_DIALOG_MAX_BUFFER]; + +#ifdef __APPLE__ + // For apple use applescript hack + FILE * output = popen( + "osascript -e \"" + " tell application \\\"System Events\\\"\n" + " activate\n" + " set existing_file to choose file\n" + " end tell\n" + " set existing_file_path to (POSIX path of (existing_file))\n" + "\" 2>/dev/null | tr -d '\n' ","r"); + while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ) + { + } +#elif defined _WIN32 + + // Use native windows file dialog box + // (code contributed by Tino Weinkauf) + + OPENFILENAME ofn; // common dialog box structure + char szFile[260]; // buffer for file name + + // Initialize OPENFILENAME + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = NULL; + ofn.lpstrFile = new char[100]; + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = "*.*\0";//off\0*.off\0obj\0*.obj\0mp\0*.mp\0"; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = NULL; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + // Display the Open dialog box. + int pos = 0; + if (GetOpenFileName(&ofn)==TRUE) + { + while(ofn.lpstrFile[pos] != '\0') + { + buffer[pos] = (char)ofn.lpstrFile[pos]; + pos++; + } + } + buffer[pos] = 0; +#else + + // For linux use zenity + FILE * output = popen("/usr/bin/zenity --file-selection","r"); + while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ) + { + } + + if (strlen(buffer) > 0) + { + buffer[strlen(buffer)-1] = 0; + } +#endif + return std::string(buffer); +} diff --git a/src/igl/file_dialog_open.h b/src/igl/file_dialog_open.h new file mode 100644 index 000000000..1b17ea4c6 --- /dev/null +++ b/src/igl/file_dialog_open.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FILE_DIALOG_OPEN_H +#define IGL_FILE_DIALOG_OPEN_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Returns a string with a path to an existing file + // The string is returned empty if no file is selected + // (on Linux machines, it assumes that Zenity is installed) + // + // Usage: + // std::string str = get_open_file_path(); + IGL_INLINE std::string file_dialog_open(); +} + +#ifndef IGL_STATIC_LIBRARY +# include "file_dialog_open.cpp" +#endif + +#endif + diff --git a/src/igl/file_dialog_save.cpp b/src/igl/file_dialog_save.cpp new file mode 100644 index 000000000..90687fa5c --- /dev/null +++ b/src/igl/file_dialog_save.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "file_dialog_save.h" +#include +#include + +#ifdef _WIN32 + #include + #include +#endif + +IGL_INLINE std::string igl::file_dialog_save() +{ + const int FILE_DIALOG_MAX_BUFFER = 1024; + char buffer[FILE_DIALOG_MAX_BUFFER]; +#ifdef __APPLE__ + // For apple use applescript hack + // There is currently a bug in Applescript that strips extensions off + // of chosen existing files in the "choose file name" dialog + // I'm assuming that will be fixed soon + FILE * output = popen( + "osascript -e \"" + " tell application \\\"System Events\\\"\n" + " activate\n" + " set existing_file to choose file name\n" + " end tell\n" + " set existing_file_path to (POSIX path of (existing_file))\n" + "\" 2>/dev/null | tr -d '\n' ","r"); + while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ) + { + } +#elif defined _WIN32 + + // Use native windows file dialog box + // (code contributed by Tino Weinkauf) + + OPENFILENAME ofn; // common dialog box structure + char szFile[260]; // buffer for file name + + // Initialize OPENFILENAME + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = NULL;//hwnd; + ofn.lpstrFile = new char[100]; + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = ""; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = NULL; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + // Display the Open dialog box. + int pos = 0; + if (GetSaveFileName(&ofn)==TRUE) + { + while(ofn.lpstrFile[pos] != '\0') + { + buffer[pos] = (char)ofn.lpstrFile[pos]; + pos++; + } + buffer[pos] = 0; + } + +#else + // For every other machine type use zenity + FILE * output = popen("/usr/bin/zenity --file-selection --save","r"); + while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ) + { + } + + if (strlen(buffer) > 0) + { + buffer[strlen(buffer)-1] = 0; + } +#endif + return std::string(buffer); +} diff --git a/src/igl/file_dialog_save.h b/src/igl/file_dialog_save.h new file mode 100644 index 000000000..f04075758 --- /dev/null +++ b/src/igl/file_dialog_save.h @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FILE_DIALOG_SAVE_H +#define IGL_FILE_DIALOG_SAVE_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Returns a string with a path to a new/existing file + // The string is returned empty if no file is selected + // (on Linux machines, it assumes that Zenity is installed) + // + // Usage: + // char buffer[FILE_DIALOG_MAX_BUFFER]; + // get_save_file_path(buffer); + IGL_INLINE std::string file_dialog_save(); +} + +#ifndef IGL_STATIC_LIBRARY +# include "file_dialog_save.cpp" +#endif + +#endif + diff --git a/src/igl/file_exists.cpp b/src/igl/file_exists.cpp new file mode 100644 index 000000000..f1a30630b --- /dev/null +++ b/src/igl/file_exists.cpp @@ -0,0 +1,16 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "file_exists.h" + +#include + +IGL_INLINE bool igl::file_exists(const std::string filename) +{ + struct stat status; + return (stat(filename.c_str(),&status)==0); +} diff --git a/src/igl/file_exists.h b/src/igl/file_exists.h new file mode 100644 index 000000000..e011d977e --- /dev/null +++ b/src/igl/file_exists.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FILE_EXISTS_H +#define IGL_FILE_EXISTS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Check if a file or directory exists like PHP's file_exists function: + // http://php.net/manual/en/function.file-exists.php + // Input: + // filename path to file + // Returns true if file exists and is readable and false if file doesn't + // exist or *is not readable* + IGL_INLINE bool file_exists(const std::string filename); +} + +#ifndef IGL_STATIC_LIBRARY +# include "file_exists.cpp" +#endif + +#endif diff --git a/src/igl/find.cpp b/src/igl/find.cpp new file mode 100644 index 000000000..65e7580ca --- /dev/null +++ b/src/igl/find.cpp @@ -0,0 +1,138 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "find.h" + +#include "verbose.h" +#include + +template < + typename T, + typename DerivedI, + typename DerivedJ, + typename DerivedV> +IGL_INLINE void igl::find( + const Eigen::SparseMatrix& X, + Eigen::DenseBase & I, + Eigen::DenseBase & J, + Eigen::DenseBase & V) +{ + // Resize outputs to fit nonzeros + I.derived().resize(X.nonZeros(),1); + J.derived().resize(X.nonZeros(),1); + V.derived().resize(X.nonZeros(),1); + + int i = 0; + // Iterate over outside + for(int k=0; k::InnerIterator it (X,k); it; ++it) + { + V(i) = it.value(); + I(i) = it.row(); + J(i) = it.col(); + i++; + } + } +} + +template < + typename DerivedX, + typename DerivedI, + typename DerivedJ, + typename DerivedV> +IGL_INLINE void igl::find( + const Eigen::DenseBase& X, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & V) +{ + const int nnz = X.count(); + I.resize(nnz,1); + J.resize(nnz,1); + V.resize(nnz,1); + { + int k = 0; + for(int j = 0;j +IGL_INLINE void igl::find( + const Eigen::DenseBase& X, + Eigen::PlainObjectBase & I) +{ + const int nnz = X.count(); + I.resize(nnz,1); + { + int k = 0; + for(int j = 0;j +IGL_INLINE void igl::find( + const Eigen::SparseVector& X, + Eigen::Matrix & I, + Eigen::Matrix & V) +{ + // Resize outputs to fit nonzeros + I.resize(X.nonZeros()); + V.resize(X.nonZeros()); + + int i = 0; + // loop over non-zeros + for(typename Eigen::SparseVector::InnerIterator it(X); it; ++it) + { + I(i) = it.index(); + V(i) = it.value(); + i++; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation + +template void igl::find, Eigen::Matrix, Eigen::Array >(Eigen::SparseMatrix const&, Eigen::DenseBase >&, Eigen::DenseBase >&, Eigen::DenseBase >&); +template void igl::find, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::find, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::DenseBase >&, Eigen::DenseBase >&, Eigen::DenseBase >&); +template void igl::find, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::DenseBase >&, Eigen::DenseBase >&, Eigen::DenseBase >&); +template void igl::find, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::DenseBase >&, Eigen::DenseBase >&, Eigen::DenseBase >&); +template void igl::find, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::find, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::DenseBase >&, Eigen::DenseBase >&, Eigen::DenseBase >&); +#if EIGEN_VERSION_AT_LEAST(3,3,0) +#else +template void igl::find, Eigen::PartialReduxExpr, Eigen::internal::member_count, 1> const, Eigen::CwiseNullaryOp, Eigen::Array > const>, Eigen::Matrix >(Eigen::DenseBase, Eigen::PartialReduxExpr, Eigen::internal::member_count, 1> const, Eigen::CwiseNullaryOp, Eigen::Array > const> > const&, Eigen::PlainObjectBase >&); +template void igl::find, Eigen::Array const, Eigen::CwiseNullaryOp, Eigen::Array > const>, Eigen::Matrix >(Eigen::DenseBase, Eigen::Array const, Eigen::CwiseNullaryOp, Eigen::Array > const> > const&, Eigen::PlainObjectBase >&); +#endif + +#endif diff --git a/src/igl/find.h b/src/igl/find.h new file mode 100644 index 000000000..85eb65770 --- /dev/null +++ b/src/igl/find.h @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FIND_H +#define IGL_FIND_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Find the non-zero entries and there respective indices in a sparse matrix. + // Like matlab's [I,J,V] = find(X) + // + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // X m by n matrix whose entries are to be found + // Outputs: + // I nnz vector of row indices of non zeros entries in X + // J nnz vector of column indices of non zeros entries in X + // V nnz vector of type T non-zeros entries in X + // + template < + typename T, + typename DerivedI, + typename DerivedJ, + typename DerivedV> + IGL_INLINE void find( + const Eigen::SparseMatrix& X, + Eigen::DenseBase & I, + Eigen::DenseBase & J, + Eigen::DenseBase & V); + template < + typename DerivedX, + typename DerivedI, + typename DerivedJ, + typename DerivedV> + IGL_INLINE void find( + const Eigen::DenseBase& X, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & V); + template < + typename DerivedX, + typename DerivedI> + IGL_INLINE void find( + const Eigen::DenseBase& X, + Eigen::PlainObjectBase & I); + // Find the non-zero entries and there respective indices in a sparse vector. + // Similar to matlab's [I,J,V] = find(X), but instead of [I,J] being + // subscripts into X, since X is a vector we just return I, a list of indices + // into X + // + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // X vector whose entries are to be found + // Outputs: + // I nnz vector of indices of non zeros entries in X + // V nnz vector of type T non-zeros entries in X + template + IGL_INLINE void find( + const Eigen::SparseVector& X, + Eigen::Matrix & I, + Eigen::Matrix & V); +} + +#ifndef IGL_STATIC_LIBRARY +# include "find.cpp" +#endif + +#endif diff --git a/src/igl/find_cross_field_singularities.cpp b/src/igl/find_cross_field_singularities.cpp new file mode 100644 index 000000000..31a5bb714 --- /dev/null +++ b/src/igl/find_cross_field_singularities.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "find_cross_field_singularities.h" + +#include +#include +#include +#include +#include +#include + + +template +IGL_INLINE void igl::find_cross_field_singularities(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &Handle_MMatch, + Eigen::PlainObjectBase &isSingularity, + Eigen::PlainObjectBase &singularityIndex) +{ + std::vector V_border = igl::is_border_vertex(V,F); + + std::vector > VF; + std::vector > VFi; + igl::vertex_triangle_adjacency(V,F,VF,VFi); + + + isSingularity.setZero(V.rows(),1); + singularityIndex.setZero(V.rows(),1); + for (unsigned int vid=0;vid +IGL_INLINE void igl::find_cross_field_singularities(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + Eigen::PlainObjectBase &isSingularity, + Eigen::PlainObjectBase &singularityIndex, + bool isCombed) +{ + Eigen::Matrix Handle_MMatch; + + igl::cross_field_missmatch(V, F, PD1, PD2, isCombed, Handle_MMatch); + igl::find_cross_field_singularities(V, F, Handle_MMatch, isSingularity, singularityIndex); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, +Eigen::PlainObjectBase >&, bool); +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, bool); +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::find_cross_field_singularities, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/find_cross_field_singularities.h b/src/igl/find_cross_field_singularities.h new file mode 100644 index 000000000..caded2019 --- /dev/null +++ b/src/igl/find_cross_field_singularities.h @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_FIND_CROSS_FIELD_SINGULARITIES_H +#define IGL_FIND_CROSS_FIELD_SINGULARITIES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Computes singularities of a cross field, assumed combed + + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (quad) indices + // Handle_MMatch #F by 3 eigen Matrix containing the integer missmatch of the cross field + // across all face edges + // Output: + // isSingularity #V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex + // singularityIndex #V by 1 integer eigen Vector containing the singularity indices + // + template + IGL_INLINE void find_cross_field_singularities(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &Handle_MMatch, + Eigen::PlainObjectBase &isSingularity, + Eigen::PlainObjectBase &singularityIndex); + + // Wrapper that calculates the missmatch if it is not provided. + // Note that the field in PD1 and PD2 MUST BE combed (see igl::comb_cross_field). + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (quad) indices + // PD1 #F by 3 eigen Matrix of the first per face cross field vector + // PD2 #F by 3 eigen Matrix of the second per face cross field vector + // Output: + // isSingularity #V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex + // singularityIndex #V by 1 integer eigen Vector containing the singularity indices + // + template + IGL_INLINE void find_cross_field_singularities(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const Eigen::PlainObjectBase &PD2, + Eigen::PlainObjectBase &isSingularity, + Eigen::PlainObjectBase &singularityIndex, + bool isCombed = false); +} +#ifndef IGL_STATIC_LIBRARY +#include "find_cross_field_singularities.cpp" +#endif + +#endif diff --git a/src/igl/find_zero.cpp b/src/igl/find_zero.cpp new file mode 100644 index 000000000..d0a7b35e6 --- /dev/null +++ b/src/igl/find_zero.cpp @@ -0,0 +1,48 @@ +#include "find_zero.h" +#include "for_each.h" +#include "any.h" + +template +IGL_INLINE void igl::find_zero( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & I) +{ + assert((dim == 1 || dim == 2) && "dim must be 2 or 1"); + // Get size of input + int m = A.rows(); + int n = A.cols(); + // I starts by containing guess where 0 might be + I = DerivedI::Zero(dim==1?n:m); + Eigen::Array found = + Eigen::Array::Zero(dim==1?n:m); + const auto func = [&I,&found,&dim](int i, int j, const int v) + { + if(dim == 2) + { + std::swap(i,j); + } + // Coded as if dim == 1, assuming swap for dim == 2 + // Have we already found a zero? + if(!found(j)) + { + if(I(j) != i || v == 0) + { + // either there was an implicit zero between the last element and this + // one, or this one is zero + found(j) = true; + }else + { + // If not found, then guess that next element will be zero + I(j) = I(j)+1; + } + } + }; + for_each(A,func); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::find_zero >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/find_zero.h b/src/igl/find_zero.h new file mode 100644 index 000000000..0739be1cf --- /dev/null +++ b/src/igl/find_zero.h @@ -0,0 +1,28 @@ +#ifndef IGL_FIND_ZERO_H +#define IGL_FIND_ZERO_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Find the first zero (whether implicit or explicitly stored) in the + // rows/columns of a matrix. + // Inputs: + // A m by n sparse matrix + // dim dimension along which to check for any (1 or 2) + // Output: + // I n-long vector (if dim == 1) {m means no zeros found} + // or + // I m-long vector (if dim == 2) {n means no zeros found} + // + template + IGL_INLINE void find_zero( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "find_zero.cpp" +#endif +#endif + diff --git a/src/igl/fit_plane.cpp b/src/igl/fit_plane.cpp new file mode 100644 index 000000000..83f11738f --- /dev/null +++ b/src/igl/fit_plane.cpp @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "fit_plane.h" +#include + +IGL_INLINE void igl::fit_plane( + const Eigen::MatrixXd & V, + Eigen::RowVector3d & N, + Eigen::RowVector3d & C) +{ + assert(V.rows()>0); + + Eigen::Vector3d sum = V.colwise().sum(); + + Eigen::Vector3d center = sum.array()/(double(V.rows())); + + C = center; + + double sumXX=0.0f,sumXY=0.0f,sumXZ=0.0f; + double sumYY=0.0f,sumYZ=0.0f; + double sumZZ=0.0f; + + for(int i=0;i es(m); + + N = es.eigenvectors().col(0); +} + +#ifdef IGL_STATIC_LIBRARY +#endif + + + diff --git a/src/igl/fit_plane.h b/src/igl/fit_plane.h new file mode 100644 index 000000000..f69ce811b --- /dev/null +++ b/src/igl/fit_plane.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FIT_PLANE_H +#define IGL_FIT_PLANE_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // This function fits a plane to a point cloud. + // + // Input: + // V #Vx3 matrix. The 3D point cloud, one row for each vertex. + // Output: + // N 1x3 Vector. The normal of the fitted plane. + // C 1x3 Vector. A point that lies in the fitted plane. + // From http://missingbytes.blogspot.com/2012/06/fitting-plane-to-point-cloud.html + + IGL_INLINE void fit_plane( + const Eigen::MatrixXd & V, + Eigen::RowVector3d & N, + Eigen::RowVector3d & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "fit_plane.cpp" +#endif + +#endif diff --git a/src/igl/fit_rotations.cpp b/src/igl/fit_rotations.cpp new file mode 100644 index 000000000..927e8992e --- /dev/null +++ b/src/igl/fit_rotations.cpp @@ -0,0 +1,225 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "fit_rotations.h" +#include "polar_svd3x3.h" +#include "repmat.h" +#include "verbose.h" +#include "polar_dec.h" +#include "polar_svd.h" +#include "C_STR.h" +#include + +template +IGL_INLINE void igl::fit_rotations( + const Eigen::PlainObjectBase & S, + const bool single_precision, + Eigen::PlainObjectBase & R) +{ + using namespace std; + const int dim = S.cols(); + const int nr = S.rows()/dim; + assert(nr * dim == S.rows()); + assert(dim == 3); + + // resize output + R.resize(dim,dim*nr); // hopefully no op (should be already allocated) + + //std::cout<<"S=["< si;// = Eigen::Matrix3d::Identity(); + // loop over number of rotations we're computing + for(int r = 0;r Mat3; + typedef Eigen::Matrix Vec3; + Mat3 ri; + if(single_precision) + { + polar_svd3x3(si, ri); + }else + { + Mat3 ti,ui,vi; + Vec3 _; + igl::polar_svd(si,ri,ti,ui,_,vi); + } + assert(ri.determinant() >= 0); + R.block(0,r*dim,dim,dim) = ri.block(0,0,dim,dim).transpose(); + //cout< +IGL_INLINE void igl::fit_rotations_planar( + const Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & R) +{ + using namespace std; + const int dim = S.cols(); + const int nr = S.rows()/dim; + //assert(dim == 2 && "_planar input should be 2D"); + assert(nr * dim == S.rows()); + + // resize output + R.resize(dim,dim*nr); // hopefully no op (should be already allocated) + + Eigen::Matrix si; + // loop over number of rotations we're computing + for(int r = 0;r Mat2; + typedef Eigen::Matrix Vec2; + Mat2 ri,ti,ui,vi; + Vec2 _; + igl::polar_svd(si,ri,ti,ui,_,vi); +#ifndef FIT_ROTATIONS_ALLOW_FLIPS + // Check for reflection + if(ri.determinant() < 0) + { + vi.col(1) *= -1.; + ri = ui * vi.transpose(); + } + assert(ri.determinant() >= 0); +#endif + + // Not sure why polar_dec computes transpose... + R.block(0,r*dim,dim,dim).setIdentity(); + R.block(0,r*dim,2,2) = ri.transpose(); + } +} + + +#ifdef __SSE__ +IGL_INLINE void igl::fit_rotations_SSE( + const Eigen::MatrixXf & S, + Eigen::MatrixXf & R) +{ + const int cStep = 4; + + assert(S.cols() == 3); + const int dim = 3; //S.cols(); + const int nr = S.rows()/dim; + assert(nr * dim == S.rows()); + + // resize output + R.resize(dim,dim*nr); // hopefully no op (should be already allocated) + + Eigen::Matrix siBig; + // using SSE decompose cStep matrices at a time: + int r = 0; + for( ; r= nr) numMats = nr - r; + // build siBig: + for (int k=0; k ri; + polar_svd3x3_sse(siBig, ri); + + for (int k=0; k= 0); + + // Not sure why polar_dec computes transpose... + for (int k=0; k(); + Eigen::MatrixXf Rf; + fit_rotations_SSE(Sf,Rf); + R = Rf.cast(); +} +#endif + +#ifdef __AVX__ +IGL_INLINE void igl::fit_rotations_AVX( + const Eigen::MatrixXf & S, + Eigen::MatrixXf & R) +{ + const int cStep = 8; + + assert(S.cols() == 3); + const int dim = 3; //S.cols(); + const int nr = S.rows()/dim; + assert(nr * dim == S.rows()); + + // resize output + R.resize(dim,dim*nr); // hopefully no op (should be already allocated) + + Eigen::Matrix siBig; + // using SSE decompose cStep matrices at a time: + int r = 0; + for( ; r= nr) numMats = nr - r; + // build siBig: + for (int k=0; k ri; + polar_svd3x3_avx(siBig, ri); + + for (int k=0; k= 0); + + // Not sure why polar_dec computes transpose... + for (int k=0; k, Eigen::Matrix >(Eigen::PlainObjectBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::fit_rotations_planar, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::fit_rotations_planar, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::fit_rotations,Eigen::Matrix >(Eigen::PlainObjectBase > const &,bool,Eigen::PlainObjectBase > &); +#endif diff --git a/src/igl/fit_rotations.h b/src/igl/fit_rotations.h new file mode 100644 index 000000000..1a18fd322 --- /dev/null +++ b/src/igl/fit_rotations.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FIT_ROTATIONS_H +#define IGL_FIT_ROTATIONS_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Known issues: This seems to be implemented in Eigen/Geometry: + // Eigen::umeyama + // + // FIT_ROTATIONS Given an input mesh and new positions find rotations for + // every covariance matrix in a stack of covariance matrices + // + // Inputs: + // S nr*dim by dim stack of covariance matrices + // single_precision whether to use single precision (faster) + // Outputs: + // R dim by dim * nr list of rotations + // + template + IGL_INLINE void fit_rotations( + const Eigen::PlainObjectBase & S, + const bool single_precision, + Eigen::PlainObjectBase & R); + + // FIT_ROTATIONS Given an input mesh and new positions find 2D rotations for + // every vertex that best maps its one ring to the new one ring + // + // Inputs: + // S nr*dim by dim stack of covariance matrices, third column and every + // third row will be ignored + // Outputs: + // R dim by dim * nr list of rotations, third row and third column of each + // rotation will just be identity + // + template + IGL_INLINE void fit_rotations_planar( + const Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & R); +#ifdef __SSE__ + IGL_INLINE void fit_rotations_SSE( const Eigen::MatrixXf & S, Eigen::MatrixXf & R); + IGL_INLINE void fit_rotations_SSE( const Eigen::MatrixXd & S, Eigen::MatrixXd & R); +#endif +#ifdef __AVX__ + IGL_INLINE void fit_rotations_AVX( const Eigen::MatrixXf & S, Eigen::MatrixXf & R); +#endif +} + +#ifndef IGL_STATIC_LIBRARY +# include "fit_rotations.cpp" +#endif + +#endif diff --git a/src/igl/flip_avoiding_line_search.cpp b/src/igl/flip_avoiding_line_search.cpp new file mode 100644 index 000000000..8554303c2 --- /dev/null +++ b/src/igl/flip_avoiding_line_search.cpp @@ -0,0 +1,320 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "flip_avoiding_line_search.h" +#include "line_search.h" +#include "PI.h" + +#include +#include + +namespace igl +{ + namespace flip_avoiding + { + //--------------------------------------------------------------------------- + // x - array of size 3 + // In case 3 real roots: => x[0], x[1], x[2], return 3 + // 2 real roots: x[0], x[1], return 2 + // 1 real root : x[0], x[1] ± i*x[2], return 1 + // http://math.ivanovo.ac.ru/dalgebra/Khashin/poly/index.html + IGL_INLINE int SolveP3(std::vector& x,double a,double b,double c) + { // solve cubic equation x^3 + a*x^2 + b*x + c + using namespace std; + double a2 = a*a; + double q = (a2 - 3*b)/9; + double r = (a*(2*a2-9*b) + 27*c)/54; + double r2 = r*r; + double q3 = q*q*q; + double A,B; + if(r2 1) t= 1; + t=acos(t); + a/=3; q=-2*sqrt(q); + x[0]=q*cos(t/3)-a; + x[1]=q*cos((t+(2*igl::PI))/3)-a; + x[2]=q*cos((t-(2*igl::PI))/3)-a; + return(3); + } + else + { + A =-pow(fabs(r)+sqrt(r2-q3),1./3); + if( r<0 ) A=-A; + B = A==0? 0 : B=q/A; + + a/=3; + x[0] =(A+B)-a; + x[1] =-0.5*(A+B)-a; + x[2] = 0.5*sqrt(3.)*(A-B); + if(fabs(x[2])<1e-14) + { + x[2]=x[1]; return(2); + } + return(1); + } + } + + IGL_INLINE double get_smallest_pos_quad_zero(double a,double b, double c) + { + using namespace std; + double t1, t2; + if(std::abs(a) > 1.0e-10) + { + double delta_in = pow(b, 2) - 4 * a * c; + if(delta_in <= 0) + { + return INFINITY; + } + + double delta = sqrt(delta_in); // delta >= 0 + if(b >= 0) // avoid subtracting two similar numbers + { + double bd = - b - delta; + t1 = 2 * c / bd; + t2 = bd / (2 * a); + } + else + { + double bd = - b + delta; + t1 = bd / (2 * a); + t2 = (2 * c) / bd; + } + + assert (std::isfinite(t1)); + assert (std::isfinite(t2)); + + if(a < 0) std::swap(t1, t2); // make t1 > t2 + // return the smaller positive root if it exists, otherwise return infinity + if(t1 > 0) + { + return t2 > 0 ? t2 : t1; + } + else + { + return INFINITY; + } + } + else + { + if(b == 0) return INFINITY; // just to avoid divide-by-zero + t1 = -c / b; + return t1 > 0 ? t1 : INFINITY; + } + } + + IGL_INLINE double get_min_pos_root_2D(const Eigen::MatrixXd& uv, + const Eigen::MatrixXi& F, + Eigen::MatrixXd& d, + int f) + { + using namespace std; + /* + Finding the smallest timestep t s.t a triangle get degenerated (<=> det = 0) + The following code can be derived by a symbolic expression in matlab: + + Symbolic matlab: + U11 = sym('U11'); + U12 = sym('U12'); + U21 = sym('U21'); + U22 = sym('U22'); + U31 = sym('U31'); + U32 = sym('U32'); + + V11 = sym('V11'); + V12 = sym('V12'); + V21 = sym('V21'); + V22 = sym('V22'); + V31 = sym('V31'); + V32 = sym('V32'); + + t = sym('t'); + + U1 = [U11,U12]; + U2 = [U21,U22]; + U3 = [U31,U32]; + + V1 = [V11,V12]; + V2 = [V21,V22]; + V3 = [V31,V32]; + + A = [(U2+V2*t) - (U1+ V1*t)]; + B = [(U3+V3*t) - (U1+ V1*t)]; + C = [A;B]; + + solve(det(C), t); + cf = coeffs(det(C),t); % Now cf(1),cf(2),cf(3) holds the coefficients for the polynom. at order c,b,a + */ + + int v1 = F(f,0); int v2 = F(f,1); int v3 = F(f,2); + // get quadratic coefficients (ax^2 + b^x + c) + const double& U11 = uv(v1,0); + const double& U12 = uv(v1,1); + const double& U21 = uv(v2,0); + const double& U22 = uv(v2,1); + const double& U31 = uv(v3,0); + const double& U32 = uv(v3,1); + + const double& V11 = d(v1,0); + const double& V12 = d(v1,1); + const double& V21 = d(v2,0); + const double& V22 = d(v2,1); + const double& V31 = d(v3,0); + const double& V32 = d(v3,1); + + double a = V11*V22 - V12*V21 - V11*V32 + V12*V31 + V21*V32 - V22*V31; + double b = U11*V22 - U12*V21 - U21*V12 + U22*V11 - U11*V32 + U12*V31 + U31*V12 - U32*V11 + U21*V32 - U22*V31 - U31*V22 + U32*V21; + double c = U11*U22 - U12*U21 - U11*U32 + U12*U31 + U21*U32 - U22*U31; + + return get_smallest_pos_quad_zero(a,b,c); + } + + IGL_INLINE double get_min_pos_root_3D(const Eigen::MatrixXd& uv, + const Eigen::MatrixXi& F, + Eigen::MatrixXd& direc, + int f) + { + using namespace std; + /* + Searching for the roots of: + +-1/6 * |ax ay az 1| + |bx by bz 1| + |cx cy cz 1| + |dx dy dz 1| + Every point ax,ay,az has a search direction a_dx,a_dy,a_dz, and so we add those to the matrix, and solve the cubic to find the step size t for a 0 volume + Symbolic matlab: + syms a_x a_y a_z a_dx a_dy a_dz % tetrahedera point and search direction + syms b_x b_y b_z b_dx b_dy b_dz + syms c_x c_y c_z c_dx c_dy c_dz + syms d_x d_y d_z d_dx d_dy d_dz + syms t % Timestep var, this is what we're looking for + + + a_plus_t = [a_x,a_y,a_z] + t*[a_dx,a_dy,a_dz]; + b_plus_t = [b_x,b_y,b_z] + t*[b_dx,b_dy,b_dz]; + c_plus_t = [c_x,c_y,c_z] + t*[c_dx,c_dy,c_dz]; + d_plus_t = [d_x,d_y,d_z] + t*[d_dx,d_dy,d_dz]; + + vol_mat = [a_plus_t,1;b_plus_t,1;c_plus_t,1;d_plus_t,1] + //cf = coeffs(det(vol_det),t); % Now cf(1),cf(2),cf(3),cf(4) holds the coefficients for the polynom + [coefficients,terms] = coeffs(det(vol_det),t); % terms = [ t^3, t^2, t, 1], Coefficients hold the coeff we seek + */ + int v1 = F(f,0); int v2 = F(f,1); int v3 = F(f,2); int v4 = F(f,3); + const double& a_x = uv(v1,0); + const double& a_y = uv(v1,1); + const double& a_z = uv(v1,2); + const double& b_x = uv(v2,0); + const double& b_y = uv(v2,1); + const double& b_z = uv(v2,2); + const double& c_x = uv(v3,0); + const double& c_y = uv(v3,1); + const double& c_z = uv(v3,2); + const double& d_x = uv(v4,0); + const double& d_y = uv(v4,1); + const double& d_z = uv(v4,2); + + const double& a_dx = direc(v1,0); + const double& a_dy = direc(v1,1); + const double& a_dz = direc(v1,2); + const double& b_dx = direc(v2,0); + const double& b_dy = direc(v2,1); + const double& b_dz = direc(v2,2); + const double& c_dx = direc(v3,0); + const double& c_dy = direc(v3,1); + const double& c_dz = direc(v3,2); + const double& d_dx = direc(v4,0); + const double& d_dy = direc(v4,1); + const double& d_dz = direc(v4,2); + + // Find solution for: a*t^3 + b*t^2 + c*d +d = 0 + double a = a_dx*b_dy*c_dz - a_dx*b_dz*c_dy - a_dy*b_dx*c_dz + a_dy*b_dz*c_dx + a_dz*b_dx*c_dy - a_dz*b_dy*c_dx - a_dx*b_dy*d_dz + a_dx*b_dz*d_dy + a_dy*b_dx*d_dz - a_dy*b_dz*d_dx - a_dz*b_dx*d_dy + a_dz*b_dy*d_dx + a_dx*c_dy*d_dz - a_dx*c_dz*d_dy - a_dy*c_dx*d_dz + a_dy*c_dz*d_dx + a_dz*c_dx*d_dy - a_dz*c_dy*d_dx - b_dx*c_dy*d_dz + b_dx*c_dz*d_dy + b_dy*c_dx*d_dz - b_dy*c_dz*d_dx - b_dz*c_dx*d_dy + b_dz*c_dy*d_dx; + + double b = a_dy*b_dz*c_x - a_dy*b_x*c_dz - a_dz*b_dy*c_x + a_dz*b_x*c_dy + a_x*b_dy*c_dz - a_x*b_dz*c_dy - a_dx*b_dz*c_y + a_dx*b_y*c_dz + a_dz*b_dx*c_y - a_dz*b_y*c_dx - a_y*b_dx*c_dz + a_y*b_dz*c_dx + a_dx*b_dy*c_z - a_dx*b_z*c_dy - a_dy*b_dx*c_z + a_dy*b_z*c_dx + a_z*b_dx*c_dy - a_z*b_dy*c_dx - a_dy*b_dz*d_x + a_dy*b_x*d_dz + a_dz*b_dy*d_x - a_dz*b_x*d_dy - a_x*b_dy*d_dz + a_x*b_dz*d_dy + a_dx*b_dz*d_y - a_dx*b_y*d_dz - a_dz*b_dx*d_y + a_dz*b_y*d_dx + a_y*b_dx*d_dz - a_y*b_dz*d_dx - a_dx*b_dy*d_z + a_dx*b_z*d_dy + a_dy*b_dx*d_z - a_dy*b_z*d_dx - a_z*b_dx*d_dy + a_z*b_dy*d_dx + a_dy*c_dz*d_x - a_dy*c_x*d_dz - a_dz*c_dy*d_x + a_dz*c_x*d_dy + a_x*c_dy*d_dz - a_x*c_dz*d_dy - a_dx*c_dz*d_y + a_dx*c_y*d_dz + a_dz*c_dx*d_y - a_dz*c_y*d_dx - a_y*c_dx*d_dz + a_y*c_dz*d_dx + a_dx*c_dy*d_z - a_dx*c_z*d_dy - a_dy*c_dx*d_z + a_dy*c_z*d_dx + a_z*c_dx*d_dy - a_z*c_dy*d_dx - b_dy*c_dz*d_x + b_dy*c_x*d_dz + b_dz*c_dy*d_x - b_dz*c_x*d_dy - b_x*c_dy*d_dz + b_x*c_dz*d_dy + b_dx*c_dz*d_y - b_dx*c_y*d_dz - b_dz*c_dx*d_y + b_dz*c_y*d_dx + b_y*c_dx*d_dz - b_y*c_dz*d_dx - b_dx*c_dy*d_z + b_dx*c_z*d_dy + b_dy*c_dx*d_z - b_dy*c_z*d_dx - b_z*c_dx*d_dy + b_z*c_dy*d_dx; + + double c = a_dz*b_x*c_y - a_dz*b_y*c_x - a_x*b_dz*c_y + a_x*b_y*c_dz + a_y*b_dz*c_x - a_y*b_x*c_dz - a_dy*b_x*c_z + a_dy*b_z*c_x + a_x*b_dy*c_z - a_x*b_z*c_dy - a_z*b_dy*c_x + a_z*b_x*c_dy + a_dx*b_y*c_z - a_dx*b_z*c_y - a_y*b_dx*c_z + a_y*b_z*c_dx + a_z*b_dx*c_y - a_z*b_y*c_dx - a_dz*b_x*d_y + a_dz*b_y*d_x + a_x*b_dz*d_y - a_x*b_y*d_dz - a_y*b_dz*d_x + a_y*b_x*d_dz + a_dy*b_x*d_z - a_dy*b_z*d_x - a_x*b_dy*d_z + a_x*b_z*d_dy + a_z*b_dy*d_x - a_z*b_x*d_dy - a_dx*b_y*d_z + a_dx*b_z*d_y + a_y*b_dx*d_z - a_y*b_z*d_dx - a_z*b_dx*d_y + a_z*b_y*d_dx + a_dz*c_x*d_y - a_dz*c_y*d_x - a_x*c_dz*d_y + a_x*c_y*d_dz + a_y*c_dz*d_x - a_y*c_x*d_dz - a_dy*c_x*d_z + a_dy*c_z*d_x + a_x*c_dy*d_z - a_x*c_z*d_dy - a_z*c_dy*d_x + a_z*c_x*d_dy + a_dx*c_y*d_z - a_dx*c_z*d_y - a_y*c_dx*d_z + a_y*c_z*d_dx + a_z*c_dx*d_y - a_z*c_y*d_dx - b_dz*c_x*d_y + b_dz*c_y*d_x + b_x*c_dz*d_y - b_x*c_y*d_dz - b_y*c_dz*d_x + b_y*c_x*d_dz + b_dy*c_x*d_z - b_dy*c_z*d_x - b_x*c_dy*d_z + b_x*c_z*d_dy + b_z*c_dy*d_x - b_z*c_x*d_dy - b_dx*c_y*d_z + b_dx*c_z*d_y + b_y*c_dx*d_z - b_y*c_z*d_dx - b_z*c_dx*d_y + b_z*c_y*d_dx; + + double d = a_x*b_y*c_z - a_x*b_z*c_y - a_y*b_x*c_z + a_y*b_z*c_x + a_z*b_x*c_y - a_z*b_y*c_x - a_x*b_y*d_z + a_x*b_z*d_y + a_y*b_x*d_z - a_y*b_z*d_x - a_z*b_x*d_y + a_z*b_y*d_x + a_x*c_y*d_z - a_x*c_z*d_y - a_y*c_x*d_z + a_y*c_z*d_x + a_z*c_x*d_y - a_z*c_y*d_x - b_x*c_y*d_z + b_x*c_z*d_y + b_y*c_x*d_z - b_y*c_z*d_x - b_z*c_x*d_y + b_z*c_y*d_x; + + if (std::abs(a)<=1.e-10) + { + return get_smallest_pos_quad_zero(b,c,d); + } + b/=a; c/=a; d/=a; // normalize it all + std::vector res(3); + int real_roots_num = SolveP3(res,b,c,d); + switch (real_roots_num) + { + case 1: + return (res[0] >= 0) ? res[0]:INFINITY; + case 2: + { + double max_root = std::max(res[0],res[1]); double min_root = std::min(res[0],res[1]); + if (min_root > 0) return min_root; + if (max_root > 0) return max_root; + return INFINITY; + } + case 3: + default: + { + std::sort(res.begin(),res.end()); + if (res[0] > 0) return res[0]; + if (res[1] > 0) return res[1]; + if (res[2] > 0) return res[2]; + return INFINITY; + } + } + } + + IGL_INLINE double compute_max_step_from_singularities(const Eigen::MatrixXd& uv, + const Eigen::MatrixXi& F, + Eigen::MatrixXd& d) + { + using namespace std; + double max_step = INFINITY; + + // The if statement is outside the for loops to avoid branching/ease parallelizing + if (uv.cols() == 2) + { + for (int f = 0; f < F.rows(); f++) + { + double min_positive_root = get_min_pos_root_2D(uv,F,d,f); + max_step = std::min(max_step, min_positive_root); + } + } + else + { // volumetric deformation + for (int f = 0; f < F.rows(); f++) + { + double min_positive_root = get_min_pos_root_3D(uv,F,d,f); + max_step = std::min(max_step, min_positive_root); + } + } + return max_step; + } + } +} + +IGL_INLINE double igl::flip_avoiding_line_search( + const Eigen::MatrixXi F, + Eigen::MatrixXd& cur_v, + Eigen::MatrixXd& dst_v, + std::function energy, + double cur_energy) +{ + using namespace std; + Eigen::MatrixXd d = dst_v - cur_v; + + double min_step_to_singularity = igl::flip_avoiding::compute_max_step_from_singularities(cur_v,F,d); + double max_step_size = std::min(1., min_step_to_singularity*0.8); + + return igl::line_search(cur_v,d,max_step_size, energy, cur_energy); +} + +#ifdef IGL_STATIC_LIBRARY +#endif diff --git a/src/igl/flip_avoiding_line_search.h b/src/igl/flip_avoiding_line_search.h new file mode 100644 index 000000000..29eb9dee0 --- /dev/null +++ b/src/igl/flip_avoiding_line_search.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FLIP_AVOIDING_LINE_SEARCH_H +#define IGL_FLIP_AVOIDING_LINE_SEARCH_H +#include "igl_inline.h" +#include "PI.h" + +#include + +namespace igl +{ + // A bisection line search for a mesh based energy that avoids triangle flips as suggested in + // "Bijective Parameterization with Free Boundaries" (Smith J. and Schaefer S., 2015). + // + // The user specifies an initial vertices position (that has no flips) and target one (that my have flipped triangles). + // This method first computes the largest step in direction of the destination vertices that does not incur flips, + // and then minimizes a given energy using this maximal step and a bisection linesearch (see igl::line_search). + // + // Supports both triangle and tet meshes. + // + // Inputs: + // F #F by 3/4 list of mesh faces or tets + // cur_v #V by dim list of variables + // dst_v #V by dim list of target vertices. This mesh may have flipped triangles + // energy A function to compute the mesh-based energy (return an energy that is bigger than 0) + // cur_energy(OPTIONAL) The energy at the given point. Helps save redundant computations. + // This is optional. If not specified, the function will compute it. + // Outputs: + // cur_v #V by dim list of variables at the new location + // Returns the energy at the new point + IGL_INLINE double flip_avoiding_line_search( + const Eigen::MatrixXi F, + Eigen::MatrixXd& cur_v, + Eigen::MatrixXd& dst_v, + std::function energy, + double cur_energy = -1); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "flip_avoiding_line_search.cpp" +#endif + +#endif diff --git a/src/igl/flip_edge.cpp b/src/igl/flip_edge.cpp new file mode 100644 index 000000000..b34581449 --- /dev/null +++ b/src/igl/flip_edge.cpp @@ -0,0 +1,153 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "flip_edge.h" + +template < + typename DerivedF, + typename DerivedE, + typename DeriveduE, + typename DerivedEMAP, + typename uE2EType> +IGL_INLINE void igl::flip_edge( + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & uE, + Eigen::PlainObjectBase & EMAP, + std::vector > & uE2E, + const size_t uei) +{ + typedef typename DerivedF::Scalar Index; + const size_t num_faces = F.rows(); + assert(F.cols() == 3); + // v1 v1 + // /|\ / \ + // / | \ /f1 \ + // v3 /f2|f1\ v4 => v3 /_____\ v4 + // \ | / \ f2 / + // \ | / \ / + // \|/ \ / + // v2 v2 + auto& half_edges = uE2E[uei]; + if (half_edges.size() != 2) { + throw "Cannot flip non-manifold or boundary edge"; + } + + const size_t f1 = half_edges[0] % num_faces; + const size_t f2 = half_edges[1] % num_faces; + const size_t c1 = half_edges[0] / num_faces; + const size_t c2 = half_edges[1] / num_faces; + assert(c1 < 3); + assert(c2 < 3); + + assert(f1 != f2); + const size_t v1 = F(f1, (c1+1)%3); + const size_t v2 = F(f1, (c1+2)%3); + const size_t v4 = F(f1, c1); + const size_t v3 = F(f2, c2); + assert(F(f2, (c2+2)%3) == v1); + assert(F(f2, (c2+1)%3) == v2); + + const size_t e_12 = half_edges[0]; + const size_t e_24 = f1 + ((c1 + 1) % 3) * num_faces; + const size_t e_41 = f1 + ((c1 + 2) % 3) * num_faces; + const size_t e_21 = half_edges[1]; + const size_t e_13 = f2 + ((c2 + 1) % 3) * num_faces; + const size_t e_32 = f2 + ((c2 + 2) % 3) * num_faces; + assert(E(e_12, 0) == v1); + assert(E(e_12, 1) == v2); + assert(E(e_24, 0) == v2); + assert(E(e_24, 1) == v4); + assert(E(e_41, 0) == v4); + assert(E(e_41, 1) == v1); + assert(E(e_21, 0) == v2); + assert(E(e_21, 1) == v1); + assert(E(e_13, 0) == v1); + assert(E(e_13, 1) == v3); + assert(E(e_32, 0) == v3); + assert(E(e_32, 1) == v2); + + const size_t ue_24 = EMAP(e_24); + const size_t ue_41 = EMAP(e_41); + const size_t ue_13 = EMAP(e_13); + const size_t ue_32 = EMAP(e_32); + + F(f1, 0) = v1; + F(f1, 1) = v3; + F(f1, 2) = v4; + F(f2, 0) = v2; + F(f2, 1) = v4; + F(f2, 2) = v3; + + uE(uei, 0) = v3; + uE(uei, 1) = v4; + + const size_t new_e_34 = f1; + const size_t new_e_41 = f1 + num_faces; + const size_t new_e_13 = f1 + num_faces*2; + const size_t new_e_43 = f2; + const size_t new_e_32 = f2 + num_faces; + const size_t new_e_24 = f2 + num_faces*2; + + E(new_e_34, 0) = v3; + E(new_e_34, 1) = v4; + E(new_e_41, 0) = v4; + E(new_e_41, 1) = v1; + E(new_e_13, 0) = v1; + E(new_e_13, 1) = v3; + E(new_e_43, 0) = v4; + E(new_e_43, 1) = v3; + E(new_e_32, 0) = v3; + E(new_e_32, 1) = v2; + E(new_e_24, 0) = v2; + E(new_e_24, 1) = v4; + + EMAP(new_e_34) = uei; + EMAP(new_e_43) = uei; + EMAP(new_e_41) = ue_41; + EMAP(new_e_13) = ue_13; + EMAP(new_e_32) = ue_32; + EMAP(new_e_24) = ue_24; + + auto replace = [](std::vector& array, Index old_v, Index new_v) { + std::replace(array.begin(), array.end(), old_v, new_v); + }; + replace(uE2E[uei], e_12, new_e_34); + replace(uE2E[uei], e_21, new_e_43); + replace(uE2E[ue_13], e_13, new_e_13); + replace(uE2E[ue_32], e_32, new_e_32); + replace(uE2E[ue_24], e_24, new_e_24); + replace(uE2E[ue_41], e_41, new_e_41); + +#ifndef NDEBUG + auto sanity_check = [&](size_t ue) { + const auto& adj_faces = uE2E[ue]; + if (adj_faces.size() == 2) { + const size_t first_f = adj_faces[0] % num_faces; + const size_t first_c = adj_faces[0] / num_faces; + const size_t second_f = adj_faces[1] % num_faces; + const size_t second_c = adj_faces[1] / num_faces; + const size_t vertex_0 = F(first_f, (first_c+1) % 3); + const size_t vertex_1 = F(first_f, (first_c+2) % 3); + assert(vertex_0 == F(second_f, (second_c+2) % 3)); + assert(vertex_1 == F(second_f, (second_c+1) % 3)); + } + }; + + sanity_check(uei); + sanity_check(ue_13); + sanity_check(ue_32); + sanity_check(ue_24); + sanity_check(ue_41); +#endif +} + + +#ifdef IGL_STATIC_LIBRARY +template void igl::flip_edge, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&, unsigned long); +#endif \ No newline at end of file diff --git a/src/igl/flip_edge.h b/src/igl/flip_edge.h new file mode 100644 index 000000000..3c43198a6 --- /dev/null +++ b/src/igl/flip_edge.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_FLIP_EDGE_H +#define IGL_FLIP_EDGE_H + +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Flip an edge in a triangle mesh. The edge specified by uei must have + // exactly **two** adjacent faces. Violation will result in exception. + // Another warning: edge flipping could convert manifold mesh into + // non-manifold. + // + // Inputs: + // F #F by 3 list of triangles. + // E #F*3 by 2 list of all of directed edges + // uE #uE by 2 list of unique undirected edges + // EMAP #F*3 list of indices into uE, mapping each directed edge to unique + // undirected edge + // uE2E #uE list of lists of indices into E of coexisting edges + // ue index into uE the edge to be flipped. + // + // Output: + // Updated F, E, uE, EMAP and uE2E. + template < + typename DerivedF, + typename DerivedE, + typename DeriveduE, + typename DerivedEMAP, + typename uE2EType> + IGL_INLINE void flip_edge( + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & uE, + Eigen::PlainObjectBase & EMAP, + std::vector > & uE2E, + const size_t uei); +} + +#ifndef IGL_STATIC_LIBRARY +# include "flip_edge.cpp" +#endif +#endif diff --git a/src/igl/flipped_triangles.cpp b/src/igl/flipped_triangles.cpp new file mode 100644 index 000000000..ea76e5c84 --- /dev/null +++ b/src/igl/flipped_triangles.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "flipped_triangles.h" + +#include "list_to_matrix.h" +#include +template +IGL_INLINE void igl::flipped_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & X) +{ + assert(V.cols() == 2 && "V should contain 2D positions"); + std::vector flip_idx; + for (int i = 0; i < F.rows(); i++) + { + // https://www.cs.cmu.edu/~quake/robust.html + typedef Eigen::Matrix RowVector2S; + RowVector2S v1_n = V.row(F(i,0)); + RowVector2S v2_n = V.row(F(i,1)); + RowVector2S v3_n = V.row(F(i,2)); + Eigen::Matrix T2_Homo; + T2_Homo.col(0) << v1_n(0),v1_n(1),1.; + T2_Homo.col(1) << v2_n(0),v2_n(1),1.; + T2_Homo.col(2) << v3_n(0),v3_n(1),1.; + double det = T2_Homo.determinant(); + assert(det == det && "det should not be NaN"); + if (det < 0) + { + flip_idx.push_back(i); + } + } + igl::list_to_matrix(flip_idx,X); +} + +template +IGL_INLINE Eigen::VectorXi igl::flipped_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F) +{ + Eigen::VectorXi X; + flipped_triangles(V,F,X); + return X; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::flipped_triangles, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix igl::flipped_triangles, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/flipped_triangles.h b/src/igl/flipped_triangles.h new file mode 100644 index 000000000..b06d9329b --- /dev/null +++ b/src/igl/flipped_triangles.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FLIPPED_TRIANGLES_H +#define IGL_FLIPPED_TRIANGLES_H +#include "igl_inline.h" + +#include +namespace igl +{ + // Finds the ids of the flipped triangles of the mesh V,F in the UV mapping uv + // + // Inputs: + // V #V by 2 list of mesh vertex positions + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // X #flipped list of containing the indices into F of the flipped triangles + // Wrapper with return type + template + IGL_INLINE void flipped_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & X); + template + IGL_INLINE Eigen::VectorXi flipped_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "flipped_triangles.cpp" +#endif + +#endif diff --git a/src/igl/flood_fill.cpp b/src/igl/flood_fill.cpp new file mode 100644 index 000000000..a6c91361d --- /dev/null +++ b/src/igl/flood_fill.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "flood_fill.h" +#include + +template +IGL_INLINE void igl::flood_fill( + const Eigen::MatrixBase& res, + Eigen::PlainObjectBase & S) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedS::Scalar Scalar; + const auto flood = [&res,&S] ( + const int xi, + const int yi, + const int zi, + const int signed_xi, + const int signed_yi, + const int signed_zi, + const Scalar s) + { + // flood fill this value back on this row + for(int bxi = xi;signed_xi<--bxi;) + { + S(bxi+res(0)*(yi + res(1)*zi)) = s; + } + // flood fill this value back on any previous rows + for(int byi = yi;signed_yi<--byi;) + { + for(int xi = 0;xi::quiet_NaN(); + for(int zi = 0;zi, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::flood_fill, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::flood_fill, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::flood_fill, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::flood_fill, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/flood_fill.h b/src/igl/flood_fill.h new file mode 100644 index 000000000..7eef01b3c --- /dev/null +++ b/src/igl/flood_fill.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FLOOD_FILL_H +#define IGL_FLOOD_FILL_H +#include "igl_inline.h" +#include +namespace igl +{ + // Given a 3D array with sparse non-nan (number?) data fill in the NaNs via + // flood fill. This should ensure that, e.g., if data near 0 always has a band + // (surface) of numbered and signed data, then components of nans will be + // correctly signed. + // + // Inputs: + // res 3-long list of dimensions of grid + // S res(0)*res(1)*res(2) list of scalar values (with (many) nans), see + // output + // Outputs: + // S flood fill data in place + template + IGL_INLINE void flood_fill( + const Eigen::MatrixBase& res, + Eigen::PlainObjectBase & S); +} +#ifndef IGL_STATIC_LIBRARY +# include "flood_fill.cpp" +#endif +#endif diff --git a/src/igl/floor.cpp b/src/igl/floor.cpp new file mode 100644 index 000000000..7538b7e01 --- /dev/null +++ b/src/igl/floor.cpp @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "floor.h" +#include +#include + +template < typename DerivedX, typename DerivedY> +IGL_INLINE void igl::floor( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y) +{ + using namespace std; + //Y = DerivedY::Zero(m,n); +//#pragma omp parallel for + //for(int i = 0;iScalar{return std::floor(x);}).template cast(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::floor, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::floor, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::floor, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/floor.h b/src/igl/floor.h new file mode 100644 index 000000000..a84d52068 --- /dev/null +++ b/src/igl/floor.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FLOOR_H +#define IGL_FLOOR_H +#include "igl_inline.h" +#include +namespace igl +{ + // Floor a given matrix to nearest integers + // + // Inputs: + // X m by n matrix of scalars + // Outputs: + // Y m by n matrix of floored integers + template < typename DerivedX, typename DerivedY> + IGL_INLINE void floor( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "floor.cpp" +#endif + +#endif diff --git a/src/igl/for_each.h b/src/igl/for_each.h new file mode 100644 index 000000000..6ba03ba1c --- /dev/null +++ b/src/igl/for_each.h @@ -0,0 +1,78 @@ +#ifndef IGL_FOR_EACH_H +#define IGL_FOR_EACH_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // FOR_EACH Call a given function for each non-zero (i.e., explicit value + // might actually be ==0) in a Sparse Matrix A _in order (of storage)_. This is + // useless unless func has _side-effects_. + // + // Inputs: + // A m by n SparseMatrix + // func function handle with prototype "compatible with" `void (Index i, + // Index j, Scalar & v)`. Return values will be ignored. + // + // See also: std::for_each + template + inline void for_each( + const Eigen::SparseMatrix & A, + const Func & func); + template + inline void for_each( + const Eigen::DenseBase & A, + const Func & func); +} + +// Implementation + +template +inline void igl::for_each( + const Eigen::SparseMatrix & A, + const Func & func) +{ + // Can **not** use parallel for because this must be _in order_ + // Iterate over outside + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + func(it.row(),it.col(),it.value()); + } + } +} + +template +inline void igl::for_each( + const Eigen::DenseBase & A, + const Func & func) +{ + // Can **not** use parallel for because this must be _in order_ + if(A.IsRowMajor) + { + for(typename DerivedA::Index i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "forward_kinematics.h" +#include +#include + +IGL_INLINE void igl::forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + const std::vector & dT, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ, + std::vector & vT) +{ + using namespace std; + using namespace Eigen; + const int m = BE.rows(); + assert(m == P.rows()); + assert(m == (int)dQ.size()); + assert(m == (int)dT.size()); + vector computed(m,false); + vQ.resize(m); + vT.resize(m); + // Dynamic programming + function fk_helper = [&] (int b) + { + if(!computed[b]) + { + if(P(b) < 0) + { + // base case for roots + vQ[b] = dQ[b]; + const Vector3d r = C.row(BE(b,0)).transpose(); + vT[b] = r-dQ[b]*r + dT[b]; + }else + { + // Otherwise first compute parent's + const int p = P(b); + fk_helper(p); + vQ[b] = vQ[p] * dQ[b]; + const Vector3d r = C.row(BE(b,0)).transpose(); + vT[b] = vT[p] - vQ[b]*r + vQ[p]*(r + dT[b]); + } + computed[b] = true; + } + }; + for(int b = 0;b > & dQ, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ, + std::vector & vT) +{ + std::vector dT(BE.rows(),Eigen::Vector3d(0,0,0)); + return forward_kinematics(C,BE,P,dQ,dT,vQ,vT); +} + +IGL_INLINE void igl::forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + const std::vector & dT, + Eigen::MatrixXd & T) +{ + using namespace Eigen; + using namespace std; + vector< Quaterniond,aligned_allocator > vQ; + vector< Vector3d> vT; + forward_kinematics(C,BE,P,dQ,dT,vQ,vT); + const int dim = C.cols(); + T.resize(BE.rows()*(dim+1),dim); + for(int e = 0;e > & dQ, + Eigen::MatrixXd & T) +{ + std::vector dT(BE.rows(),Eigen::Vector3d(0,0,0)); + return forward_kinematics(C,BE,P,dQ,dT,T); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/forward_kinematics.h b/src/igl/forward_kinematics.h new file mode 100644 index 000000000..2b3d2db6d --- /dev/null +++ b/src/igl/forward_kinematics.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FORWARD_KINEMATICS_H +#define IGL_FORWARD_KINEMATICS_H +#include "igl_inline.h" +#include +#include +#include +#include + +namespace igl +{ + // Given a skeleton and a set of relative bone rotations compute absolute + // rigid transformations for each bone. + // + // Inputs: + // C #C by dim list of joint positions + // BE #BE by 2 list of bone edge indices + // P #BE list of parent indices into BE + // dQ #BE list of relative rotations + // dT #BE list of relative translations + // Outputs: + // vQ #BE list of absolute rotations + // vT #BE list of absolute translations + IGL_INLINE void forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + const std::vector & dT, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ, + std::vector & vT); + // Wrapper assuming each dT[i] == {0,0,0} + IGL_INLINE void forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & vQ, + std::vector & vT); + + // Outputs: + // T #BE*(dim+1) by dim stack of transposed transformation matrices + IGL_INLINE void forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + const std::vector & dT, + Eigen::MatrixXd & T); + IGL_INLINE void forward_kinematics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > & dQ, + Eigen::MatrixXd & T); + +}; + +#ifndef IGL_STATIC_LIBRARY +# include "forward_kinematics.cpp" +#endif +#endif diff --git a/src/igl/frame_field_deformer.cpp b/src/igl/frame_field_deformer.cpp new file mode 100644 index 000000000..c22200f1b --- /dev/null +++ b/src/igl/frame_field_deformer.cpp @@ -0,0 +1,411 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "frame_field_deformer.h" + +#include +#include +#include + +#include +#include +#include + +namespace igl +{ + +class Frame_field_deformer +{ +public: + + IGL_INLINE Frame_field_deformer(); + IGL_INLINE ~Frame_field_deformer(); + + // Initialize the optimizer + IGL_INLINE void init(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F, const Eigen::MatrixXd& _D1, const Eigen::MatrixXd& _D2, double _Lambda, double _perturb_rotations, int _fixed = 1); + + // Run N optimization steps + IGL_INLINE void optimize(int N, bool reset = false); + + // Reset optimization + IGL_INLINE void reset_opt(); + + // Precomputation of all components + IGL_INLINE void precompute_opt(); + + // Precomputation for deformation energy + IGL_INLINE void precompute_ARAP(Eigen::SparseMatrix & Lff, Eigen::MatrixXd & LfcVc); + + // Precomputation for regularization + IGL_INLINE void precompute_SMOOTH(Eigen::SparseMatrix & MS, Eigen::MatrixXd & bS); + + // extracts a r x c block from sparse matrix mat into sparse matrix m1 + // (r0,c0) is upper left entry of block + IGL_INLINE void extractBlock(Eigen::SparseMatrix & mat, int r0, int c0, int r, int c, Eigen::SparseMatrix & m1); + + // computes optimal rotations for faces of m wrt current coords in mw.V + // returns a 3x3 matrix + IGL_INLINE void compute_optimal_rotations(); + + // global optimization step - linear system + IGL_INLINE void compute_optimal_positions(); + + // compute the output XField from deformation gradient + IGL_INLINE void computeXField(std::vector< Eigen::Matrix > & XF); + + // computes in WW the ideal warp at each tri to make the frame field a cross + IGL_INLINE void compute_idealWarp(std::vector< Eigen::Matrix > & WW); + + // -------------------------------- Variables ---------------------------------------------------- + + // Mesh I/O: + + Eigen::MatrixXd V; // Original mesh - vertices + Eigen::MatrixXi F; // Original mesh - faces + + std::vector > VT; // Vertex to triangle topology + std::vector > VTi; // Vertex to triangle topology + + Eigen::MatrixXd V_w; // Warped mesh - vertices + + std::vector< Eigen::Matrix > FF; // frame field FF in 3D (parallel to m.F) + std::vector< Eigen::Matrix > WW; // warping matrices to make a cross field (parallel to m.F) + std::vector< Eigen::Matrix > XF; // pseudo-cross field from solution (parallel to m.F) + + int fixed; + + double perturb_rotations; // perturbation to rotation matrices + + // Numerics + int nfree,nconst; // number of free/constrained vertices in the mesh - default all-but-1/1 + Eigen::MatrixXd C; // cotangent matrix of m + Eigen::SparseMatrix L; // Laplacian matrix of m + + Eigen::SparseMatrix M; // matrix for global optimization - pre-conditioned + Eigen::MatrixXd RHS; // pre-computed part of known term in global optimization + std::vector< Eigen::Matrix > RW; // optimal rotation-warping matrices (parallel to m.F) -- INCORPORATES WW + Eigen::SimplicialCholesky > solver; // solver for linear system in global opt. + + // Parameters +private: + double Lambda = 0.1; // weight of energy regularization + +}; + + IGL_INLINE Frame_field_deformer::Frame_field_deformer() {} + + IGL_INLINE Frame_field_deformer::~Frame_field_deformer() {} + + IGL_INLINE void Frame_field_deformer::init(const Eigen::MatrixXd& _V, + const Eigen::MatrixXi& _F, + const Eigen::MatrixXd& _D1, + const Eigen::MatrixXd& _D2, + double _Lambda, + double _perturb_rotations, + int _fixed) +{ + V = _V; + F = _F; + + assert(_D1.rows() == _D2.rows()); + + FF.clear(); + for (unsigned i=0; i < _D1.rows(); ++i) + { + Eigen::Matrix ff; + ff.col(0) = _D1.row(i); + ff.col(1) = _D2.row(i); + FF.push_back(ff); + } + + fixed = _fixed; + Lambda = _Lambda; + perturb_rotations = _perturb_rotations; + + reset_opt(); + precompute_opt(); +} + + +IGL_INLINE void Frame_field_deformer::optimize(int N, bool reset) +{ + //Reset optimization + if (reset) + reset_opt(); + + // Iterative Local/Global optimization + for (int i=0; i MA; // internal matrix for ARAP-warping energy + MatrixXd LfcVc; // RHS (partial) for ARAP-warping energy + SparseMatrix MS; // internal matrix for smoothing energy + MatrixXd bS; // RHS (full) for smoothing energy + + precompute_ARAP(MA,LfcVc); // precompute terms for the ARAP-warp part + precompute_SMOOTH(MS,bS); // precompute terms for the smoothing part + compute_idealWarp(WW); // computes the ideal warps + RW.resize(F.rows()); // init rotation matrices - global + + M = (1-Lambda)*MA + Lambda*MS; // matrix for linear system - global + + RHS = (1-Lambda)*LfcVc + Lambda*bS; // RHS (partial) for linear system - global + solver.compute(M); // system pre-conditioning + if (solver.info()!=Eigen::Success) {fprintf(stderr,"Decomposition failed in pre-conditioning!\n"); exit(-1);} + + fprintf(stdout,"Preconditioning done.\n"); + +} + +IGL_INLINE void Frame_field_deformer::precompute_ARAP(Eigen::SparseMatrix & Lff, Eigen::MatrixXd & LfcVc) +{ + using namespace Eigen; + fprintf(stdout,"Precomputing ARAP terms\n"); + SparseMatrix LL = -4*L; + Lff = SparseMatrix(nfree,nfree); + extractBlock(LL,0,0,nfree,nfree,Lff); + SparseMatrix Lfc = SparseMatrix(nfree,nconst); + extractBlock(LL,0,nfree,nfree,nconst,Lfc); + LfcVc = - Lfc * V_w.block(nfree,0,nconst,3); +} + +IGL_INLINE void Frame_field_deformer::precompute_SMOOTH(Eigen::SparseMatrix & MS, Eigen::MatrixXd & bS) +{ + using namespace Eigen; + fprintf(stdout,"Precomputing SMOOTH terms\n"); + + SparseMatrix LL = 4*L*L; + + // top-left + MS = SparseMatrix(nfree,nfree); + extractBlock(LL,0,0,nfree,nfree,MS); + + // top-right + SparseMatrix Mfc = SparseMatrix(nfree,nconst); + extractBlock(LL,0,nfree,nfree,nconst,Mfc); + + MatrixXd MfcVc = Mfc * V_w.block(nfree,0,nconst,3); + bS = (LL*V).block(0,0,nfree,3)-MfcVc; + +} + + IGL_INLINE void Frame_field_deformer::extractBlock(Eigen::SparseMatrix & mat, int r0, int c0, int r, int c, Eigen::SparseMatrix & m1) +{ + std::vector > tripletList; + for (int k=c0; k::InnerIterator it(mat,k); it; ++it) + { + if (it.row()>=r0 && it.row()(it.row()-r0,it.col()-c0,it.value())); + } + m1.setFromTriplets(tripletList.begin(), tripletList.end()); +} + +IGL_INLINE void Frame_field_deformer::compute_optimal_rotations() +{ + using namespace Eigen; + Matrix r,S,P,PP,D; + + for (int i=0;i > svd(S, Eigen::ComputeFullU | Eigen::ComputeFullV ); + Matrix su = svd.matrixU(); + Matrix sv = svd.matrixV(); + r = su*sv.transpose(); + + if (r.determinant()<0) // correct reflections + { + su(0,2)=-su(0,2); su(1,2)=-su(1,2); su(2,2)=-su(2,2); + r = su*sv.transpose(); + } + RW[i] = r*WW[i]; // RW INCORPORATES IDEAL WARP WW!!! + } +} + +IGL_INLINE void Frame_field_deformer::compute_optimal_positions() +{ + using namespace Eigen; + // compute variable RHS of ARAP-warp part of the system + MatrixXd b(nfree,3); // fx3 known term of the system + MatrixXd X; // result + int t; // triangles incident to edge (i,j) + int vi,i1,i2; // index of vertex i wrt tri t0 + + for (int i=0;i > & XF) +{ + using namespace Eigen; + Matrix P,PP,DG; + XF.resize(F.rows()); + + for (int i=0;i > & WW) +{ + using namespace Eigen; + + WW.resize(F.rows()); + for (int i=0;i<(int)FF.size();i++) + { + Vector3d v0,v1,v2; + v0 = FF[i].col(0); + v1 = FF[i].col(1); + v2=v0.cross(v1); v2.normalize(); // normal + + Matrix3d A,AI; // compute affine map A that brings: + A << v0[0], v1[0], v2[0], // first vector of FF to x unary vector + v0[1], v1[1], v2[1], // second vector of FF to xy plane + v0[2], v1[2], v2[2]; // triangle normal to z unary vector + AI = A.inverse(); + + // polar decomposition to discard rotational component (unnecessary but makes it easier) + Eigen::JacobiSVD > svd(AI, Eigen::ComputeFullU | Eigen::ComputeFullV ); + //Matrix au = svd.matrixU(); + Matrix av = svd.matrixV(); + DiagonalMatrix as(svd.singularValues()); + WW[i] = av*as*av.transpose(); + } +} + +} + + +IGL_INLINE void igl::frame_field_deformer( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::MatrixXd& FF1, + const Eigen::MatrixXd& FF2, + Eigen::MatrixXd& V_d, + Eigen::MatrixXd& FF1_d, + Eigen::MatrixXd& FF2_d, + const int iterations, + const double lambda, + const bool perturb_initial_guess) +{ + using namespace Eigen; + // Solvers + Frame_field_deformer deformer; + + // Init optimizer + deformer.init(V, F, FF1, FF2, lambda, perturb_initial_guess ? 0.1 : 0); + + // Optimize + deformer.optimize(iterations,true); + + // Copy positions + V_d = deformer.V_w; + + // Allocate + FF1_d.resize(F.rows(),3); + FF2_d.resize(F.rows(),3); + + // Copy frame field + for(unsigned i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FRAME_FIELD_DEFORMER_H +#define IGL_FRAME_FIELD_DEFORMER_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Deform a mesh to transform the given per-face frame field to be as close + // as possible to a cross field, in the least square sense. + // + // Inputs: + // V #V by 3 coordinates of the vertices + // F #F by 3 list of mesh faces (must be triangles) + // FF1 #F by 3 first representative vector of the frame field + // FF2 #F by 3 second representative vector of the frame field + // lambda laplacian regularization parameter 0=no regularization 1=full regularization + // + // Outputs: + // V_d #F by 3 deformed, first representative vector + // V_d #F by 3 deformed, first representative vector + // V_d #F by 3 deformed, first representative vector + // + IGL_INLINE void frame_field_deformer( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::MatrixXd& FF1, + const Eigen::MatrixXd& FF2, + Eigen::MatrixXd& V_d, + Eigen::MatrixXd& FF1_d, + Eigen::MatrixXd& FF2_d, + const int iterations = 50, + const double lambda = 0.1, + const bool perturb_initial_guess = true); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "frame_field_deformer.cpp" +#endif + +#endif diff --git a/src/igl/frame_to_cross_field.cpp b/src/igl/frame_to_cross_field.cpp new file mode 100644 index 000000000..e4d6bc713 --- /dev/null +++ b/src/igl/frame_to_cross_field.cpp @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "frame_to_cross_field.h" +#include +#include + +IGL_INLINE void igl::frame_to_cross_field( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::MatrixXd& FF1, + const Eigen::MatrixXd& FF2, + Eigen::MatrixXd& X) +{ + using namespace Eigen; + + // Generate local basis + MatrixXd B1, B2, B3; + + igl::local_basis(V,F,B1,B2,B3); + + // Project the frame fields in the local basis + MatrixXd d1, d2; + d1.resize(F.rows(),2); + d2.resize(F.rows(),2); + + d1 << igl::dot_row(B1,FF1), igl::dot_row(B2,FF1); + d2 << igl::dot_row(B1,FF2), igl::dot_row(B2,FF2); + + X.resize(F.rows(), 3); + + for (int i=0;i > svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV ); + Matrix2d C = svd.matrixU() * svd.matrixV().transpose(); + + Vector2d v = C.col(0); + X.row(i) = v(0) * B1.row(i) + v(1) * B2.row(i); + } +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/frame_to_cross_field.h b/src/igl/frame_to_cross_field.h new file mode 100644 index 000000000..f9bdd3d5f --- /dev/null +++ b/src/igl/frame_to_cross_field.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FRAME_TO_CROSS_FIELD_H +#define IGL_FRAME_TO_CROSS_FIELD_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Convert a frame field into its closest cross field + // Inputs: + // V #V by 3 coordinates of the vertices + // F #F by 3 list of mesh faces (must be triangles) + // FF1 #F by 3 the first representative vector of the frame field (up to permutation and sign) + // FF2 #F by 3 the second representative vector of the frame field (up to permutation and sign) + // + // Outputs: + // X #F by 3 representative vector of the closest cross field + // + IGL_INLINE void frame_to_cross_field( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::MatrixXd& FF1, + const Eigen::MatrixXd& FF2, + Eigen::MatrixXd& X); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "frame_to_cross_field.cpp" +#endif + +#endif diff --git a/src/igl/frustum.cpp b/src/igl/frustum.cpp new file mode 100644 index 000000000..2926106e6 --- /dev/null +++ b/src/igl/frustum.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "frustum.h" +template < typename DerivedP> +IGL_INLINE void igl::frustum( + const typename DerivedP::Scalar left, + const typename DerivedP::Scalar right, + const typename DerivedP::Scalar bottom, + const typename DerivedP::Scalar top, + const typename DerivedP::Scalar nearVal, + const typename DerivedP::Scalar farVal, + Eigen::PlainObjectBase & P) +{ + P.setConstant(4,4,0.); + P(0,0) = (2.0 * nearVal) / (right - left); + P(1,1) = (2.0 * nearVal) / (top - bottom); + P(0,2) = (right + left) / (right - left); + P(1,2) = (top + bottom) / (top - bottom); + P(2,2) = -(farVal + nearVal) / (farVal - nearVal); + P(3,2) = -1.0; + P(2,3) = -(2.0 * farVal * nearVal) / (farVal - nearVal); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::frustum >(Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/frustum.h b/src/igl/frustum.h new file mode 100644 index 000000000..4a92e1a81 --- /dev/null +++ b/src/igl/frustum.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_FRUSTUM_H +#define IGL_FRUSTUM_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Implementation of the deprecated glFrustum function. + // + // Inputs: + // left coordinate of left vertical clipping plane + // right coordinate of right vertical clipping plane + // bottom coordinate of bottom vertical clipping plane + // top coordinate of top vertical clipping plane + // nearVal distance to near plane + // farVal distance to far plane + // Outputs: + // P 4x4 perspective matrix + template < typename DerivedP> + IGL_INLINE void frustum( + const typename DerivedP::Scalar left, + const typename DerivedP::Scalar right, + const typename DerivedP::Scalar bottom, + const typename DerivedP::Scalar top, + const typename DerivedP::Scalar nearVal, + const typename DerivedP::Scalar farVal, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "frustum.cpp" +#endif + +#endif + + diff --git a/src/igl/gaussian_curvature.cpp b/src/igl/gaussian_curvature.cpp new file mode 100644 index 000000000..ca05129df --- /dev/null +++ b/src/igl/gaussian_curvature.cpp @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "gaussian_curvature.h" +#include "internal_angles.h" +#include "PI.h" +#include +template +IGL_INLINE void igl::gaussian_curvature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & K) +{ + using namespace Eigen; + using namespace std; + // internal corner angles + Matrix< + typename DerivedV::Scalar, + DerivedF::RowsAtCompileTime, + DerivedF::ColsAtCompileTime> A; + internal_angles(V,F,A); + K.resize(V.rows(),1); + K.setConstant(V.rows(),1,2.*PI); + assert(A.rows() == F.rows()); + assert(A.cols() == F.cols()); + assert(K.rows() == V.rows()); + assert(F.maxCoeff() < V.rows()); + assert(K.cols() == 1); + const int Frows = F.rows(); + //K_G(x_i) = (2π - ∑θj) +//#ifndef IGL_GAUSSIAN_CURVATURE_OMP_MIN_VALUE +//# define IGL_GAUSSIAN_CURVATURE_OMP_MIN_VALUE 1000 +//#endif +//#pragma omp parallel for if (Frows>IGL_GAUSSIAN_CURVATURE_OMP_MIN_VALUE) + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::gaussian_curvature, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/gaussian_curvature.h b/src/igl/gaussian_curvature.h new file mode 100644 index 000000000..d8ddbc8ad --- /dev/null +++ b/src/igl/gaussian_curvature.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GAUSSIAN_CURVATURE_H +#define IGL_GAUSSIAN_CURVATURE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute discrete local integral gaussian curvature (angle deficit, without + // averaging by local area). + // + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // Output: + // K #V by 1 eigen Matrix of discrete gaussian curvature values + // + template + IGL_INLINE void gaussian_curvature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & K); +} + +#ifndef IGL_STATIC_LIBRARY +# include "gaussian_curvature.cpp" +#endif + +#endif + diff --git a/src/igl/get_seconds.cpp b/src/igl/get_seconds.cpp new file mode 100644 index 000000000..fac4a3a69 --- /dev/null +++ b/src/igl/get_seconds.cpp @@ -0,0 +1,15 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "get_seconds.h" +#include +IGL_INLINE double igl::get_seconds() +{ + return + std::chrono::duration( + std::chrono::system_clock::now().time_since_epoch()).count(); +} diff --git a/src/igl/get_seconds.h b/src/igl/get_seconds.h new file mode 100644 index 000000000..411d6e41d --- /dev/null +++ b/src/igl/get_seconds.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GET_SECONDS_H +#define IGL_GET_SECONDS_H +#include "igl_inline.h" + +namespace igl +{ + // Return the current time in seconds since program start + // + // Example: + // const auto & tictoc = []() + // { + // static double t_start = igl::get_seconds(); + // double diff = igl::get_seconds()-t_start; + // t_start += diff; + // return diff; + // }; + // tictoc(); + // ... // part 1 + // cout<<"part 1: "< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "get_seconds_hires.h" + +#if _WIN32 +# include +# include +IGL_INLINE double igl::get_seconds_hires() +{ + LARGE_INTEGER li_freq, li_current; + const bool ret = QueryPerformanceFrequency(&li_freq); + const bool ret2 = QueryPerformanceCounter(&li_current); + assert(ret && ret2); + assert(li_freq.QuadPart > 0); + return double(li_current.QuadPart) / double(li_freq.QuadPart); +} +#else +# include "get_seconds.h" +IGL_INLINE double igl::get_seconds_hires() +{ + // Sorry I've no idea how performance counters work on Mac... + return igl::get_seconds(); +} +#endif diff --git a/src/igl/get_seconds_hires.h b/src/igl/get_seconds_hires.h new file mode 100644 index 000000000..abe8956b5 --- /dev/null +++ b/src/igl/get_seconds_hires.h @@ -0,0 +1,22 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GET_SECONDS_HIRES_H +#define IGL_GET_SECONDS_HIRES_H +#include "igl_inline.h" + +namespace igl +{ + // Return the current time in seconds using performance counters + IGL_INLINE double get_seconds_hires(); +} + +#ifndef IGL_STATIC_LIBRARY +# include "get_seconds_hires.cpp" +#endif + +#endif diff --git a/src/igl/grad.cpp b/src/igl/grad.cpp new file mode 100644 index 000000000..cc86e9e81 --- /dev/null +++ b/src/igl/grad.cpp @@ -0,0 +1,243 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "grad.h" +#include +#include + +#include "PI.h" +#include "per_face_normals.h" +#include "volume.h" +#include "doublearea.h" + +template +IGL_INLINE void grad_tet(const Eigen::PlainObjectBase&V, + const Eigen::PlainObjectBase&T, + Eigen::SparseMatrix &G, + bool uniform) { + using namespace Eigen; + assert(T.cols() == 4); + const int n = V.rows(); int m = T.rows(); + + /* + F = [ ... + T(:,1) T(:,2) T(:,3); ... + T(:,1) T(:,3) T(:,4); ... + T(:,1) T(:,4) T(:,2); ... + T(:,2) T(:,4) T(:,3)]; */ + MatrixXi F(4*m,3); + for (int i = 0; i < m; i++) { + F.row(0*m + i) << T(i,0), T(i,1), T(i,2); + F.row(1*m + i) << T(i,0), T(i,2), T(i,3); + F.row(2*m + i) << T(i,0), T(i,3), T(i,1); + F.row(3*m + i) << T(i,1), T(i,3), T(i,2); + } + // compute volume of each tet + Eigen::Matrix vol; + igl::volume(V,T,vol); + + Eigen::Matrix A(F.rows()); + Eigen::Matrix N(F.rows(),3); + if (!uniform) { + // compute tetrahedron face normals + igl::per_face_normals(V,F,N); int norm_rows = N.rows(); + for (int i = 0; i < norm_rows; i++) + N.row(i) /= N.row(i).norm(); + igl::doublearea(V,F,A); A/=2.; + } else { + // Use a uniform tetrahedra as a reference, with the same volume as the original one: + // + // Use normals of the uniform tet (V = h*[0,0,0;1,0,0;0.5,sqrt(3)/2.,0;0.5,sqrt(3)/6.,sqrt(2)/sqrt(3)]) + // 0 0 1.0000 + // 0.8165 -0.4714 -0.3333 + // 0 0.9428 -0.3333 + // -0.8165 -0.4714 -0.3333 + for (int i = 0; i < m; i++) { + N.row(0*m+i) << 0,0,1; + double a = sqrt(2)*std::cbrt(3*vol(i)); // area of a face in a uniform tet with volume = vol(i) + A(0*m+i) = (pow(a,2)*sqrt(3))/4.; + } + for (int i = 0; i < m; i++) { + N.row(1*m+i) << 0.8165,-0.4714,-0.3333; + double a = sqrt(2)*std::cbrt(3*vol(i)); + A(1*m+i) = (pow(a,2)*sqrt(3))/4.; + } + for (int i = 0; i < m; i++) { + N.row(2*m+i) << 0,0.9428,-0.3333; + double a = sqrt(2)*std::cbrt(3*vol(i)); + A(2*m+i) = (pow(a,2)*sqrt(3))/4.; + } + for (int i = 0; i < m; i++) { + N.row(3*m+i) << -0.8165,-0.4714,-0.3333; + double a = sqrt(2)*std::cbrt(3*vol(i)); + A(3*m+i) = (pow(a,2)*sqrt(3))/4.; + } + + } + + /* G = sparse( ... + [0*m + repmat(1:m,1,4) ... + 1*m + repmat(1:m,1,4) ... + 2*m + repmat(1:m,1,4)], ... + repmat([T(:,4);T(:,2);T(:,3);T(:,1)],3,1), ... + repmat(A./(3*repmat(vol,4,1)),3,1).*N(:), ... + 3*m,n);*/ + std::vector > G_t; + for (int i = 0; i < 4*m; i++) { + int T_j; // j indexes : repmat([T(:,4);T(:,2);T(:,3);T(:,1)],3,1) + switch (i/m) { + case 0: + T_j = 3; + break; + case 1: + T_j = 1; + break; + case 2: + T_j = 2; + break; + case 3: + T_j = 0; + break; + } + int i_idx = i%m; + int j_idx = T(i_idx,T_j); + + double val_before_n = A(i)/(3*vol(i_idx)); + G_t.push_back(Triplet(0*m+i_idx, j_idx, val_before_n * N(i,0))); + G_t.push_back(Triplet(1*m+i_idx, j_idx, val_before_n * N(i,1))); + G_t.push_back(Triplet(2*m+i_idx, j_idx, val_before_n * N(i,2))); + } + G.resize(3*m,n); + G.setFromTriplets(G_t.begin(), G_t.end()); +} + +template +IGL_INLINE void grad_tri(const Eigen::PlainObjectBase&V, + const Eigen::PlainObjectBase&F, + Eigen::SparseMatrix &G, + bool uniform) +{ + Eigen::Matrix + eperp21(F.rows(),3), eperp13(F.rows(),3); + + for (int i=0;i v32 = V.row(i3) - V.row(i2); + Eigen::Matrix v13 = V.row(i1) - V.row(i3); + Eigen::Matrix v21 = V.row(i2) - V.row(i1); + Eigen::Matrix n = v32.cross(v13); + // area of parallelogram is twice area of triangle + // area of parallelogram is || v1 x v2 || + // This does correct l2 norm of rows, so that it contains #F list of twice + // triangle areas + double dblA = std::sqrt(n.dot(n)); + Eigen::Matrix u(0,0,1); + if (!uniform) { + // now normalize normals to get unit normals + u = n / dblA; + } else { + // Abstract equilateral triangle v1=(0,0), v2=(h,0), v3=(h/2, (sqrt(3)/2)*h) + + // get h (by the area of the triangle) + double h = sqrt( (dblA)/sin(igl::PI / 3.0)); // (h^2*sin(60))/2. = Area => h = sqrt(2*Area/sin_60) + + Eigen::Matrix v1,v2,v3; + v1 << 0,0,0; + v2 << h,0,0; + v3 << h/2.,(sqrt(3)/2.)*h,0; + + // now fix v32,v13,v21 and the normal + v32 = v3-v2; + v13 = v1-v3; + v21 = v2-v1; + n = v32.cross(v13); + } + + // rotate each vector 90 degrees around normal + double norm21 = std::sqrt(v21.dot(v21)); + double norm13 = std::sqrt(v13.dot(v13)); + eperp21.row(i) = u.cross(v21); + eperp21.row(i) = eperp21.row(i) / std::sqrt(eperp21.row(i).dot(eperp21.row(i))); + eperp21.row(i) *= norm21 / dblA; + eperp13.row(i) = u.cross(v13); + eperp13.row(i) = eperp13.row(i) / std::sqrt(eperp13.row(i).dot(eperp13.row(i))); + eperp13.row(i) *= norm13 / dblA; + } + + std::vector rs; + rs.reserve(F.rows()*4*3); + std::vector cs; + cs.reserve(F.rows()*4*3); + std::vector vs; + vs.reserve(F.rows()*4*3); + + // row indices + for(int r=0;r<3;r++) + { + for(int j=0;j<4;j++) + { + for(int i=r*F.rows();i<(r+1)*F.rows();i++) rs.push_back(i); + } + } + + // column indices + for(int r=0;r<3;r++) + { + for(int i=0;i > triplets; + for (int i=0;i<(int)vs.size();++i) + { + triplets.push_back(Eigen::Triplet(rs[i],cs[i],vs[i])); + } + G.setFromTriplets(triplets.begin(), triplets.end()); +} + +template +IGL_INLINE void igl::grad(const Eigen::PlainObjectBase&V, + const Eigen::PlainObjectBase&F, + Eigen::SparseMatrix &G, + bool uniform) +{ + assert(F.cols() == 3 || F.cols() == 4); + if (F.cols() == 3) + return grad_tri(V,F,G,uniform); + if (F.cols() == 4) + return grad_tet(V,F,G,uniform); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::grad, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix::Scalar, 0, int>&, bool); +template void igl::grad, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix::Scalar, 0, int>&, bool); +#endif diff --git a/src/igl/grad.h b/src/igl/grad.h new file mode 100644 index 000000000..df375ba61 --- /dev/null +++ b/src/igl/grad.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GRAD_MAT_H +#define IGL_GRAD_MAT_H +#include "igl_inline.h" + +#include +#include + +namespace igl { + // GRAD + // G = grad(V,F) + // + // Compute the numerical gradient operator + // + // Inputs: + // V #vertices by 3 list of mesh vertex positions + // F #faces by 3 list of mesh face indices [or a #faces by 4 list of tetrahedral indices] + // uniform boolean (default false) - Use a uniform mesh instead of the vertices V + // Outputs: + // G #faces*dim by #V Gradient operator + // + + // Gradient of a scalar function defined on piecewise linear elements (mesh) + // is constant on each triangle [tetrahedron] i,j,k: + // grad(Xijk) = (Xj-Xi) * (Vi - Vk)^R90 / 2A + (Xk-Xi) * (Vj - Vi)^R90 / 2A + // where Xi is the scalar value at vertex i, Vi is the 3D position of vertex + // i, and A is the area of triangle (i,j,k). ^R90 represent a rotation of + // 90 degrees + // +template +IGL_INLINE void grad(const Eigen::PlainObjectBase&V, + const Eigen::PlainObjectBase&F, + Eigen::SparseMatrix &G, + bool uniform = false); +} +#ifndef IGL_STATIC_LIBRARY +# include "grad.cpp" +#endif + +#endif diff --git a/src/igl/grid.cpp b/src/igl/grid.cpp new file mode 100644 index 000000000..f572a0571 --- /dev/null +++ b/src/igl/grid.cpp @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "grid.h" + +template < + typename Derivedres, + typename DerivedGV> +IGL_INLINE void igl::grid( + const Eigen::MatrixBase & res, + Eigen::PlainObjectBase & GV) +{ + using namespace Eigen; + typedef typename DerivedGV::Scalar Scalar; + GV.resize(res(0)*res(1)*res(2),3); + for(int zi = 0;ziScalar{return di/(Scalar)(res(d)-1);}; + const Scalar z = lerp(zi,2); + for(int yi = 0;yi(x,y,z); + } + } + } +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::grid, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::grid, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::grid, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::grid, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::grid, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/grid.h b/src/igl/grid.h new file mode 100644 index 000000000..27894f628 --- /dev/null +++ b/src/igl/grid.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GRID_H +#define IGL_GRID_H +#include "igl_inline.h" +#include +namespace igl +{ + // Construct vertices of a regular grid, suitable for input to + // `igl::marching_cubes` + // + // Inputs: + // res 3-long list of number of vertices along the x y and z dimensions + // Outputs: + // GV res(0)*res(1)*res(2) by 3 list of mesh vertex positions. + // + template < + typename Derivedres, + typename DerivedGV> + IGL_INLINE void grid( + const Eigen::MatrixBase & res, + Eigen::PlainObjectBase & GV); +} +#ifndef IGL_STATIC_LIBRARY +# include "grid.cpp" +#endif +#endif diff --git a/src/igl/grid_search.cpp b/src/igl/grid_search.cpp new file mode 100644 index 000000000..93cbc0dde --- /dev/null +++ b/src/igl/grid_search.cpp @@ -0,0 +1,64 @@ +#include "grid_search.h" +#include +#include + +template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB, + typename DerivedI> +IGL_INLINE Scalar igl::grid_search( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const Eigen::MatrixBase & I, + DerivedX & X) +{ + Scalar fval = std::numeric_limits::max(); + const int dim = LB.size(); + assert(UB.size() == dim && "UB should match LB size"); + assert(I.size() == dim && "I should match LB size"); + X.resize(dim); + + // Working X value + DerivedX Xrun(dim); + std::function looper; + int calls = 0; + looper = [&]( + const int d, + DerivedX & Xrun) + { + assert(d < dim); + Eigen::Matrix vals = + Eigen::Matrix::LinSpaced(I(d),LB(d),UB(d)); + for(int c = 0;c, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::function&)>, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix&); +template float igl::grid_search, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::function&)>, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix&); +#endif diff --git a/src/igl/grid_search.h b/src/igl/grid_search.h new file mode 100644 index 000000000..83a86663e --- /dev/null +++ b/src/igl/grid_search.h @@ -0,0 +1,42 @@ +#ifndef IGL_GRID_SEARCH_H +#define IGL_GRID_SEARCH_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Solve the problem: + // + // minimize f(x) + // subject to lb ≤ x ≤ ub + // + // by exhaustive grid search. + // + // Inputs: + // f function to minimize + // LB #X vector of finite lower bounds + // UB #X vector of finite upper bounds + // I #X vector of number of steps for each variable + // Outputs: + // X #X optimal parameter vector + // Returns f(X) + // + template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB, + typename DerivedI> + IGL_INLINE Scalar grid_search( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const Eigen::MatrixBase & I, + DerivedX & X); +} + +#ifndef IGL_STATIC_LIBRARY +# include "grid_search.cpp" +#endif + +#endif diff --git a/src/igl/group_sum_matrix.cpp b/src/igl/group_sum_matrix.cpp new file mode 100644 index 000000000..d3cf0bff2 --- /dev/null +++ b/src/igl/group_sum_matrix.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "group_sum_matrix.h" + +template +IGL_INLINE void igl::group_sum_matrix( + const Eigen::Matrix & G, + const int k, + Eigen::SparseMatrix& A) +{ + // number of vertices + int n = G.rows(); + assert(k > G.maxCoeff()); + + A.resize(k,n); + + // builds A such that A(i,j) = 1 where i corresponds to group i and j + // corresponds to vertex j + + // Loop over vertices + for(int j = 0;j +IGL_INLINE void igl::group_sum_matrix( + const Eigen::Matrix & G, + Eigen::SparseMatrix& A) +{ + return group_sum_matrix(G,G.maxCoeff()+1,A); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::group_sum_matrix(Eigen::Matrix const&, int, Eigen::SparseMatrix&); +template void igl::group_sum_matrix(Eigen::Matrix const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/group_sum_matrix.h b/src/igl/group_sum_matrix.h new file mode 100644 index 000000000..805544b0b --- /dev/null +++ b/src/igl/group_sum_matrix.h @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_GROUP_SUM_MATRIX_H +#define IGL_GROUP_SUM_MATRIX_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // GROUP_SUM_MATRIX Builds a matrix A such that A*V computes the sum of + // vertices in each group specified by G + // + // group_sum_matrix(G,k,A); + // + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // G #V list of group indices (0 to k-1) for each vertex, such that vertex i + // is assigned to group G(i) + // k #groups, good choice is max(G)+1 + // Outputs: + // A #groups by #V sparse matrix such that A*V = group_sums + // + template + IGL_INLINE void group_sum_matrix( + const Eigen::Matrix & G, + const int k, + Eigen::SparseMatrix& A); + // Wrapper with k = max(G)+1 + template + IGL_INLINE void group_sum_matrix( + const Eigen::Matrix & G, + Eigen::SparseMatrix& A); +} +#ifndef IGL_STATIC_LIBRARY +# include "group_sum_matrix.cpp" +#endif +#endif diff --git a/src/igl/guess_extension.cpp b/src/igl/guess_extension.cpp new file mode 100644 index 000000000..8e8e449a1 --- /dev/null +++ b/src/igl/guess_extension.cpp @@ -0,0 +1,106 @@ +#include "guess_extension.h" + +#include + +#include "is_stl.h" +#include "ply.h" + +IGL_INLINE void igl::guess_extension(FILE * fp, std::string & guess) +{ + const auto is_off = [](FILE * fp)-> bool + { + char header[1000]; + const std::string OFF("OFF"); + const std::string NOFF("NOFF"); + const std::string COFF("COFF"); + bool f = (fscanf(fp,"%s\n",header)==1 && ( + std::string(header).compare(0, OFF.length(), OFF)==0 || + std::string(header).compare(0, COFF.length(), COFF)==0 || + std::string(header).compare(0,NOFF.length(),NOFF)==0)); + rewind(fp); + return f; + }; + const auto is_ply = [](FILE * ply_file) -> bool + { + int nelems; + char ** elem_names; + igl::ply::PlyFile * in_ply = igl::ply::ply_read(ply_file,&nelems,&elem_names); + if(in_ply==NULL) + { + return false; + } + free(in_ply); + rewind(ply_file); + return true; + }; + const auto is_wrl = [](FILE * wrl_file)->bool + { + bool still_comments = true; + char line[1000]; + std::string needle("point ["); + std::string haystack; + while(still_comments) + { + if(fgets(line,1000,wrl_file) == NULL) + { + rewind(wrl_file); + return false; + } + haystack = std::string(line); + still_comments = std::string::npos == haystack.find(needle); + } + rewind(wrl_file); + return true; + }; + const auto is_mesh = [](FILE * mesh_file )->bool + { + char line[2048]; + // eat comments at beginning of file + bool still_comments= true; + while(still_comments) + { + if(fgets(line,2048,mesh_file) == NULL) + { + rewind(mesh_file); + return false; + } + still_comments = (line[0] == '#' || line[0] == '\n'); + } + char str[2048]; + sscanf(line," %s",str); + // check that first word is MeshVersionFormatted + if(0!=strcmp(str,"MeshVersionFormatted")) + { + rewind(mesh_file); + return false; + } + rewind(mesh_file); + return true; + }; + guess = "obj"; + if(is_mesh(fp)) + { + guess = "mesh"; + }else if(is_off(fp)) + { + guess = "off"; + }else if(is_ply(fp)) + { + guess = "ply"; + }else if(igl::is_stl(fp)) + { + guess = "stl"; + }else if(is_wrl(fp)) + { + guess = "wrl"; + } + // else obj + rewind(fp); +} + +IGL_INLINE std::string igl::guess_extension(FILE * fp) +{ + std::string guess; + guess_extension(fp,guess); + return guess; +} diff --git a/src/igl/guess_extension.h b/src/igl/guess_extension.h new file mode 100644 index 000000000..77df6f523 --- /dev/null +++ b/src/igl/guess_extension.h @@ -0,0 +1,25 @@ +#ifndef IGL_GUESS_EXTENSION_H +#define IGL_GUESS_EXTENSION_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Given a file pointer at the beginning of a "mesh" file, try to guess the + // extension of the file format it comes from. The file pointer is rewound on + // return. + // + // Inputs: + // fp file pointer (see output) + // Outputs: + // fp file pointer rewound + // guess extension as string. One of "mesh",{"obj"},"off","ply","stl", or + // "wrl" + // + IGL_INLINE void guess_extension(FILE * fp, std::string & guess); + IGL_INLINE std::string guess_extension(FILE * fp); +} +#ifndef IGL_STATIC_LIBRARY +# include "guess_extension.cpp" +#endif +#endif diff --git a/src/igl/harmonic.cpp b/src/igl/harmonic.cpp new file mode 100644 index 000000000..aa07245a1 --- /dev/null +++ b/src/igl/harmonic.cpp @@ -0,0 +1,175 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "harmonic.h" +#include "adjacency_matrix.h" +#include "cotmatrix.h" +#include "diag.h" +#include "invert_diag.h" +#include "isdiag.h" +#include "massmatrix.h" +#include "min_quad_with_fixed.h" +#include "speye.h" +#include "sum.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedW> +IGL_INLINE bool igl::harmonic( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W) +{ + using namespace Eigen; + typedef typename DerivedV::Scalar Scalar; + SparseMatrix L,M; + cotmatrix(V,F,L); + if(k>1) + { + massmatrix(V,F,MASSMATRIX_TYPE_DEFAULT,M); + } + return harmonic(L,M,b,bc,k,W); +} + +template < + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedW> +IGL_INLINE bool igl::harmonic( + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W) +{ + using namespace Eigen; + typedef typename Derivedbc::Scalar Scalar; + SparseMatrix A; + adjacency_matrix(F,A); + // sum each row + SparseVector Asum; + sum(A,1,Asum); + // Convert row sums into diagonal of sparse matrix + SparseMatrix Adiag; + diag(Asum,Adiag); + SparseMatrix L = A-Adiag; + SparseMatrix M; + speye(L.rows(),M); + return harmonic(L,M,b,bc,k,W); +} + +template < + typename DerivedL, + typename DerivedM, + typename Derivedb, + typename Derivedbc, + typename DerivedW> +IGL_INLINE bool igl::harmonic( + const Eigen::SparseMatrix & L, + const Eigen::SparseMatrix & M, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W) +{ + const int n = L.rows(); + assert(n == L.cols() && "L must be square"); + assert((k==1 || n == M.cols() ) && "M must be same size as L"); + assert((k==1 || n == M.rows() ) && "M must be square"); + assert((k==1 || igl::isdiag(M)) && "Mass matrix should be diagonal"); + + Eigen::SparseMatrix Q; + igl::harmonic(L,M,k,Q); + + typedef DerivedL Scalar; + min_quad_with_fixed_data data; + min_quad_with_fixed_precompute(Q,b,Eigen::SparseMatrix(),true,data); + W.resize(n,bc.cols()); + typedef Eigen::Matrix VectorXS; + const VectorXS B = VectorXS::Zero(n,1); + for(int w = 0;w +IGL_INLINE void igl::harmonic( + const Eigen::SparseMatrix & L, + const Eigen::SparseMatrix & M, + const int k, + Eigen::SparseMatrix & Q) +{ + assert(L.rows() == L.cols()&&"L should be square"); + Q = -L; + if(k == 1) return; + assert(L.rows() == M.rows()&&"L should match M's dimensions"); + assert(M.rows() == M.cols()&&"M should be square"); + Eigen::SparseMatrix Mi; + invert_diag(M,Mi); + // This is **not** robust for k>2. See KKT system in [Jacobson et al. 2010] + // of the kharmonic function in gptoolbox + for(int p = 1;p +IGL_INLINE void igl::harmonic( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const int k, + Eigen::SparseMatrix & Q) +{ + Eigen::SparseMatrix L,M; + cotmatrix(V,F,L); + if(k>1) + { + massmatrix(V,F,MASSMATRIX_TYPE_DEFAULT,M); + } + return harmonic(L,M,k,Q); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::harmonic, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::SparseMatrix&); +// generated by autoexplicit.sh +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +template bool igl::harmonic, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/harmonic.h b/src/igl/harmonic.h new file mode 100644 index 000000000..1a48ecfb5 --- /dev/null +++ b/src/igl/harmonic.h @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HARMONIC_H +#define IGL_HARMONIC_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Compute k-harmonic weight functions "coordinates". + // + // + // Inputs: + // V #V by dim vertex positions + // F #F by simplex-size list of element indices + // b #b boundary indices into V + // bc #b by #W list of boundary values + // k power of harmonic operation (1: harmonic, 2: biharmonic, etc) + // Outputs: + // W #V by #W list of weights + // + template < + typename DerivedV, + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedW> + IGL_INLINE bool harmonic( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W); + // Compute harmonic map using uniform laplacian operator + // + // Inputs: + // F #F by simplex-size list of element indices + // b #b boundary indices into V + // bc #b by #W list of boundary values + // k power of harmonic operation (1: harmonic, 2: biharmonic, etc) + // Outputs: + // W #V by #W list of weights + // + template < + typename DerivedF, + typename Derivedb, + typename Derivedbc, + typename DerivedW> + IGL_INLINE bool harmonic( + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W); + // Compute a harmonic map using a given Laplacian and mass matrix + // + // Inputs: + // L #V by #V discrete (integrated) Laplacian + // M #V by #V mass matrix + // b #b boundary indices into V + // bc #b by #W list of boundary values + // k power of harmonic operation (1: harmonic, 2: biharmonic, etc) + // Outputs: + // W #V by #V list of weights + template < + typename DerivedL, + typename DerivedM, + typename Derivedb, + typename Derivedbc, + typename DerivedW> + IGL_INLINE bool harmonic( + const Eigen::SparseMatrix & L, + const Eigen::SparseMatrix & M, + const Eigen::MatrixBase & b, + const Eigen::MatrixBase & bc, + const int k, + Eigen::PlainObjectBase & W); + // Build the discrete k-harmonic operator (computing integrated quantities). + // That is, if the k-harmonic PDE is Q x = 0, then this minimizes x' Q x + // + // Inputs: + // L #V by #V discrete (integrated) Laplacian + // M #V by #V mass matrix + // k power of harmonic operation (1: harmonic, 2: biharmonic, etc) + // Outputs: + // Q #V by #V discrete (integrated) k-Laplacian + template < + typename DerivedL, + typename DerivedM, + typename DerivedQ> + IGL_INLINE void harmonic( + const Eigen::SparseMatrix & L, + const Eigen::SparseMatrix & M, + const int k, + Eigen::SparseMatrix & Q); + // Inputs: + // V #V by dim vertex positions + // F #F by simplex-size list of element indices + // k power of harmonic operation (1: harmonic, 2: biharmonic, etc) + // Outputs: + // Q #V by #V discrete (integrated) k-Laplacian + template < + typename DerivedV, + typename DerivedF, + typename DerivedQ> + IGL_INLINE void harmonic( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const int k, + Eigen::SparseMatrix & Q); +}; + +#ifndef IGL_STATIC_LIBRARY +#include "harmonic.cpp" +#endif +#endif diff --git a/src/igl/harwell_boeing.cpp b/src/igl/harwell_boeing.cpp new file mode 100644 index 000000000..895416510 --- /dev/null +++ b/src/igl/harwell_boeing.cpp @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "harwell_boeing.h" + +template +IGL_INLINE void igl::harwell_boeing( + const Eigen::SparseMatrix & A, + int & num_rows, + std::vector & V, + std::vector & R, + std::vector & C) +{ + num_rows = A.rows(); + int num_cols = A.cols(); + int nnz = A.nonZeros(); + V.resize(nnz); + R.resize(nnz); + C.resize(num_cols+1); + + // Assumes outersize is columns + assert(A.cols() == A.outerSize()); + int column_pointer = 0; + int i = 0; + int k = 0; + // Iterate over outside + for(; k::InnerIterator it (A,k); it; ++it) + { + V[i] = it.value(); + R[i] = it.row(); + i++; + // Also increment column pointer + column_pointer++; + } + } + // by convention C[num_cols] = nnz + C[k] = column_pointer; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::harwell_boeing(Eigen::SparseMatrix const&, int&, std::vector >&, std::vector >&, std::vector >&); +#endif diff --git a/src/igl/harwell_boeing.h b/src/igl/harwell_boeing.h new file mode 100644 index 000000000..df166ab82 --- /dev/null +++ b/src/igl/harwell_boeing.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HARWELL_BOEING_H +#define IGL_HARWELL_BOEING_H +#include "igl_inline.h" + +#include +#include + + +namespace igl +{ + // Convert the matrix to Compressed sparse column (CSC or CCS) format, + // also known as Harwell Boeing format. As described: + // http://netlib.org/linalg/html_templates/node92.html + // or + // http://en.wikipedia.org/wiki/Sparse_matrix + // #Compressed_sparse_column_.28CSC_or_CCS.29 + // Templates: + // Scalar type of sparse matrix like double + // Inputs: + // A sparse m by n matrix + // Outputs: + // num_rows number of rows + // V non-zero values, row indices running fastest, size(V) = nnz + // R row indices corresponding to vals, size(R) = nnz + // C index in vals of first entry in each column, size(C) = num_cols+1 + // + // All indices and pointers are 0-based + template + IGL_INLINE void harwell_boeing( + const Eigen::SparseMatrix & A, + int & num_rows, + std::vector & V, + std::vector & R, + std::vector & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "harwell_boeing.cpp" +#endif + +#endif diff --git a/src/igl/hausdorff.cpp b/src/igl/hausdorff.cpp new file mode 100644 index 000000000..9b1ad4743 --- /dev/null +++ b/src/igl/hausdorff.cpp @@ -0,0 +1,89 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "hausdorff.h" +#include "point_mesh_squared_distance.h" + +template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename Scalar> +IGL_INLINE void igl::hausdorff( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Scalar & d) +{ + using namespace Eigen; + assert(VA.cols() == 3 && "VA should contain 3d points"); + assert(FA.cols() == 3 && "FA should contain triangles"); + assert(VB.cols() == 3 && "VB should contain 3d points"); + assert(FB.cols() == 3 && "FB should contain triangles"); + Matrix sqr_DBA,sqr_DAB; + Matrix I; + Matrix C; + point_mesh_squared_distance(VB,VA,FA,sqr_DBA,I,C); + point_mesh_squared_distance(VA,VB,FB,sqr_DAB,I,C); + const Scalar dba = sqr_DBA.maxCoeff(); + const Scalar dab = sqr_DAB.maxCoeff(); + d = sqrt(std::max(dba,dab)); +} + +template < + typename DerivedV, + typename Scalar> +IGL_INLINE void igl::hausdorff( + const Eigen::MatrixBase& V, + const std::function & dist_to_B, + Scalar & l, + Scalar & u) +{ + // e 3-long vector of opposite edge lengths + Eigen::Matrix e; + // Maximum edge length + Scalar e_max = 0; + for(int i=0;i<3;i++) + { + e(i) = (V.row((i+1)%3)-V.row((i+2)%3)).norm(); + e_max = std::max(e_max,e(i)); + } + // Semiperimeter + const Scalar s = (e(0)+e(1)+e(2))*0.5; + // Area + const Scalar A = sqrt(s*(s-e(0))*(s-e(1))*(s-e(2))); + // Circumradius + const Scalar R = e(0)*e(1)*e(2)/(4.*A); + // inradius + const Scalar r = A/s; + // Initialize lower bound to ∞ + l = std::numeric_limits::infinity(); + // d 3-long vector of distance from each corner to B + Eigen::Matrix d; + Scalar u1 = std::numeric_limits::infinity(); + Scalar u2 = 0; + for(int i=0;i<3;i++) + { + d(i) = dist_to_B(V(i,0),V(i,1),V(i,2)); + // Lower bound is simply the max over vertex distances + l = std::max(d(i),l); + // u1 is the minimum of corner distances + maximum adjacent edge + u1 = std::min(u1,d(i) + std::max(e((i+1)%3),e((i+2)%3))); + // u2 first takes the maximum over corner distances + u2 = std::max(u2,d(i)); + } + // u2 is the distance from the circumcenter/midpoint of obtuse edge plus the + // largest corner distance + u2 += (s-r>2.*R ? R : 0.5*e_max); + u = std::min(u1,u2); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::hausdorff, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double&); +#endif diff --git a/src/igl/hausdorff.h b/src/igl/hausdorff.h new file mode 100644 index 000000000..92e22b333 --- /dev/null +++ b/src/igl/hausdorff.h @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HAUSDORFF_H +#define IGL_HAUSDORFF_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // HAUSDORFF compute the Hausdorff distance between mesh (VA,FA) and mesh + // (VB,FB). This is the + // + // d(A,B) = max ( max min d(a,b) , max min d(b,a) ) + // a∈A b∈B b∈B a∈A + // + // Known issue: This is only computing max(min(va,B),min(vb,A)). This is + // better than max(min(va,Vb),min(vb,Va)). This (at least) is missing + // "edge-edge" cases like the distance between the two different + // triangulations of a non-planar quad in 3D. Even simpler, consider the + // Hausdorff distance between the non-convex, block letter V polygon (with 7 + // vertices) in 2D and its convex hull. The Hausdorff distance is defined by + // the midpoint in the middle of the segment across the concavity and some + // non-vertex point _on the edge_ of the V. + // + // Inputs: + // VA #VA by 3 list of vertex positions + // FA #FA by 3 list of face indices into VA + // VB #VB by 3 list of vertex positions + // FB #FB by 3 list of face indices into VB + // Outputs: + // d hausdorff distance + // //pair 2 by 3 list of "determiner points" so that pair(1,:) is from A + // // and pair(2,:) is from B + // + template < + typename DerivedVA, + typename DerivedFA, + typename DerivedVB, + typename DerivedFB, + typename Scalar> + IGL_INLINE void hausdorff( + const Eigen::PlainObjectBase & VA, + const Eigen::PlainObjectBase & FA, + const Eigen::PlainObjectBase & VB, + const Eigen::PlainObjectBase & FB, + Scalar & d); + // Compute lower and upper bounds (l,u) on the Hausdorff distance between a triangle + // (V) and a pointset (e.g., mesh, triangle soup) given by a distance function + // handle (dist_to_B). + // + // Inputs: + // V 3 by 3 list of corner positions so that V.row(i) is the position of the + // ith corner + // dist_to_B function taking the x,y,z coordinate of a query position and + // outputting the closest-point distance to some point-set B + // Outputs: + // l lower bound on Hausdorff distance + // u upper bound on Hausdorff distance + // + template < + typename DerivedV, + typename Scalar> + IGL_INLINE void hausdorff( + const Eigen::MatrixBase& V, + const std::function< + Scalar(const Scalar &,const Scalar &, const Scalar &)> & dist_to_B, + Scalar & l, + Scalar & u); +} + +#ifndef IGL_STATIC_LIBRARY +# include "hausdorff.cpp" +#endif + +#endif + diff --git a/src/igl/hessian.cpp b/src/igl/hessian.cpp new file mode 100644 index 000000000..ebf81cd7d --- /dev/null +++ b/src/igl/hessian.cpp @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// and Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "hessian.h" +#include + +#include "grad.h" +#include "igl/doublearea.h" +#include "igl/repdiag.h" + + + +template +IGL_INLINE void igl::hessian( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& H) +{ + typedef typename DerivedV::Scalar denseScalar; + typedef typename Eigen::Matrix VecXd; + typedef typename Eigen::SparseMatrix SparseMat; + typedef typename Eigen::DiagonalMatrix + DiagMat; + + int dim = V.cols(); + assert((dim==2 || dim==3) && + "The dimension of the vertices should be 2 or 3"); + + //Construct the combined gradient matric + SparseMat G; + igl::grad(DerivedV(V), + DerivedF(F), + G, false); + SparseMat GG(F.rows(), dim*V.rows()); + GG.reserve(G.nonZeros()); + for(int i=0; i, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/hessian.h b/src/igl/hessian.h new file mode 100644 index 000000000..360750d2c --- /dev/null +++ b/src/igl/hessian.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// and Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_FEM_HESSIAN_H +#define IGL_FEM_HESSIAN_H +#include "igl_inline.h" + +#include +#include + + +namespace igl +{ + // Constructs the finite element Hessian matrix + // as described in https://arxiv.org/abs/1707.04348, + // Natural Boundary Conditions for Smoothing in Geometry Processing + // (Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) + // The interior vertices are NOT set to zero yet. + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // H #V by #V Hessian energy matrix, each column i + // corresponding to V(i,:) + // + // + // + template + IGL_INLINE void hessian( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& H); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "hessian.cpp" +#endif + +#endif diff --git a/src/igl/hessian_energy.cpp b/src/igl/hessian_energy.cpp new file mode 100644 index 000000000..fa0b10a7d --- /dev/null +++ b/src/igl/hessian_energy.cpp @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// and Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "hessian_energy.h" +#include + +#include "hessian.h" +#include "massmatrix.h" +#include "boundary_loop.h" + + +template +IGL_INLINE void igl::hessian_energy( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& Q) +{ + typedef typename DerivedV::Scalar denseScalar; + typedef typename Eigen::Matrix VecXd; + typedef typename Eigen::SparseMatrix SparseMat; + typedef typename Eigen::DiagonalMatrix + DiagMat; + + int dim = V.cols(); + assert((dim==2 || dim==3) && + "The dimension of the vertices should be 2 or 3"); + + SparseMat M; + igl::massmatrix(V,F,igl::MASSMATRIX_TYPE_VORONOI,M); + + //Kill non-interior DOFs + VecXd Mint = M.diagonal(); + std::vector > bdryLoop; + igl::boundary_loop(DerivedF(F),bdryLoop); + for(const std::vector& loop : bdryLoop) + for(const int& bdryVert : loop) + Mint(bdryVert) = 0.; + + //Invert Mint + for(int i=0; i 0) + Mint(i) = 1./Mint(i); + + //Repeat Mint to form diaginal matrix + DiagMat stackedMinv = Mint.replicate(dim*dim,1).asDiagonal(); + + //Compute squared Hessian + SparseMat H; + igl::hessian(V,F,H); + Q = H.transpose()*stackedMinv*H; + +} + + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::hessian_energy, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/hessian_energy.h b/src/igl/hessian_energy.h new file mode 100644 index 000000000..e53373580 --- /dev/null +++ b/src/igl/hessian_energy.h @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// and Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HESSIAN_ENERGY_H +#define IGL_HESSIAN_ENERGY_H +#include "igl_inline.h" + +#include +#include + + +namespace igl +{ + // Constructs the Hessian energy matrix using mixed FEM + // as described in https://arxiv.org/abs/1707.04348 + // Natural Boundary Conditions for Smoothing in Geometry Processing + // (Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // Q #V by #V Hessian energy matrix, each row/column i + // corresponding to V(i,:) + // + // + // + template + IGL_INLINE void hessian_energy( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + Eigen::SparseMatrix& Q); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "hessian_energy.cpp" +#endif + +#endif diff --git a/src/igl/histc.cpp b/src/igl/histc.cpp new file mode 100644 index 000000000..3f967b3be --- /dev/null +++ b/src/igl/histc.cpp @@ -0,0 +1,113 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "histc.h" +#include +#include + +template +IGL_INLINE void igl::histc( + const Eigen::MatrixBase & X, + const Eigen::MatrixBase & E, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & B) +{ + histc(X,E,B); + const int n = E.size(); + const int m = X.size(); + assert(m == B.size()); + N.resize(n,1); + N.setConstant(0); +#pragma omp parallel for + for(int j = 0;j= 0) + { +#pragma omp atomic + N(B(j))++; + } + } +} + +template +IGL_INLINE void igl::histc( + const Eigen::MatrixBase & X, + const Eigen::MatrixBase & E, + Eigen::PlainObjectBase & B) +{ + const int m = X.size(); + using namespace std; + assert( + (E.bottomRightCorner(E.size()-1,1) - + E.topLeftCorner(E.size()-1,1)).maxCoeff() >= 0 && + "E should be monotonically increasing"); + B.resize(m,1); +#pragma omp parallel for + for(int j = 0;j E(E.size()-1)) + { + B(j) = -1; + continue; + } + // Find x in E + int l = 0; + int h = E.size()-1; + int k = l; + while((h-l)>1) + { + assert(x >= E(l)); + assert(x <= E(h)); + k = (h+l)/2; + if(x < E(k)) + { + h = k; + }else + { + l = k; + } + } + if(x == E(h)) + { + k = h; + }else + { + k = l; + } + B(j) = k; + } +} + +template +IGL_INLINE void igl::histc( + const typename DerivedE::Scalar & x, + const Eigen::MatrixBase & E, + typename DerivedE::Index & b) +{ + Eigen::Matrix X; + X(0) = x; + Eigen::Matrix B; + hist(X,E,B); + b = B(0); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::histc, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::histc, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::histc, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::histc, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::histc, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#if EIGEN_VERSION_AT_LEAST(3,3,0) +#else +template void igl::histc, Eigen::Matrix >, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase, Eigen::Matrix > > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif + +#endif diff --git a/src/igl/histc.h b/src/igl/histc.h new file mode 100644 index 000000000..2f0ed9135 --- /dev/null +++ b/src/igl/histc.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HISTC_H +#define IGL_HISTC_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // Like matlab's histc. Count occurrences of values in X between consecutive + // entries in E + // + // Inputs: + // X m-long Vector of values + // E n-long Monotonically increasing vector of edges + // Outputs: + // N n-long vector where N(k) reveals how many values in X fall between + // E(k) <= X < E(k+1) + // B m-long vector of bin ids so that B(j) = k if E(k) <= X(j) < E(k+1). + // B(j) = -1 if X(j) is outside of E. + // + // O(n+m*log(n)) + template + IGL_INLINE void histc( + const Eigen::MatrixBase & X, + const Eigen::MatrixBase & E, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & B); + // Truly O(m*log(n)) + template + IGL_INLINE void histc( + const Eigen::MatrixBase & X, + const Eigen::MatrixBase & E, + Eigen::PlainObjectBase & B); + // Scalar search wrapper + template + IGL_INLINE void histc( + const typename DerivedE::Scalar & x, + const Eigen::MatrixBase & E, + typename DerivedE::Index & b); +} + +#ifndef IGL_STATIC_LIBRARY +# include "histc.cpp" +#endif + +#endif + + + diff --git a/src/igl/hsv_to_rgb.cpp b/src/igl/hsv_to_rgb.cpp new file mode 100644 index 000000000..a1727ff50 --- /dev/null +++ b/src/igl/hsv_to_rgb.cpp @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "hsv_to_rgb.h" +#include + + +template +IGL_INLINE void igl::hsv_to_rgb(const T * hsv, T * rgb) +{ + igl::hsv_to_rgb( + hsv[0],hsv[1],hsv[2], + rgb[0],rgb[1],rgb[2]); +} + +template +IGL_INLINE void igl::hsv_to_rgb( + const T & h, const T & s, const T & v, + T & r, T & g, T & b) +{ + // From medit + double f,p,q,t,hh; + int i; + hh = ((int)h % 360) / 60.; + i = (int)std::floor(hh); /* largest int <= h */ + f = hh - i; /* fractional part of h */ + p = v * (1.0 - s); + q = v * (1.0 - (s * f)); + t = v * (1.0 - (s * (1.0 - f))); + + switch(i) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } +} + +template +void igl::hsv_to_rgb( + const Eigen::PlainObjectBase & H, + Eigen::PlainObjectBase & R) +{ + assert(H.cols() == 3); + R.resizeLike(H); + for(typename DerivedH::Index r = 0;r, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::hsv_to_rgb, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::hsv_to_rgb, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::hsv_to_rgb, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::hsv_to_rgb(double const*, double*); +#endif diff --git a/src/igl/hsv_to_rgb.h b/src/igl/hsv_to_rgb.h new file mode 100644 index 000000000..aeb64d9d3 --- /dev/null +++ b/src/igl/hsv_to_rgb.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_HSV_TO_RGB_H +#define IGL_HSV_TO_RGB_H +#include "igl_inline.h" +#include +namespace igl +{ + // Convert RGB to HSV + // + // Inputs: + // h hue value (degrees: [0,360]) + // s saturation value ([0,1]) + // v value value ([0,1]) + // Outputs: + // r red value ([0,1]) + // g green value ([0,1]) + // b blue value ([0,1]) + template + IGL_INLINE void hsv_to_rgb(const T * hsv, T * rgb); + template + IGL_INLINE void hsv_to_rgb( + const T & h, const T & s, const T & v, + T & r, T & g, T & b); + template + void hsv_to_rgb( + const Eigen::PlainObjectBase & H, + Eigen::PlainObjectBase & R); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "hsv_to_rgb.cpp" +#endif + +#endif + diff --git a/src/igl/igl_inline.h b/src/igl/igl_inline.h new file mode 100644 index 000000000..20c9630e4 --- /dev/null +++ b/src/igl/igl_inline.h @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// This should *NOT* be contained in a IGL_*_H ifdef, since it may be defined +// differently based on when it is included +#ifdef IGL_INLINE +#undef IGL_INLINE +#endif + +#ifndef IGL_STATIC_LIBRARY +# define IGL_INLINE inline +#else +# define IGL_INLINE +#endif diff --git a/src/igl/in_element.cpp b/src/igl/in_element.cpp new file mode 100644 index 000000000..dcdfd8a89 --- /dev/null +++ b/src/igl/in_element.cpp @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "in_element.h" + +template +IGL_INLINE void igl::in_element( + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + const Eigen::PlainObjectBase & Q, + const AABB & aabb, + Eigen::VectorXi & I) +{ + using namespace std; + using namespace Eigen; + const int Qr = Q.rows(); + I.setConstant(Qr,1,-1); +#pragma omp parallel for if (Qr>10000) + for(int e = 0;e +IGL_INLINE void igl::in_element( + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + const Eigen::PlainObjectBase & Q, + const AABB & aabb, + Eigen::SparseMatrix & I) +{ + using namespace std; + using namespace Eigen; + const int Qr = Q.rows(); + std::vector > IJV; + IJV.reserve(Qr); +#pragma omp parallel for if (Qr>10000) + for(int e = 0;e(e,r,1)); + } + } + I.resize(Qr,Ele.rows()); + I.setFromTriplets(IJV.begin(),IJV.end()); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::in_element, Eigen::Matrix, 2>(Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase > const&, igl::AABB, 2> const&, Eigen::Matrix&); +template void igl::in_element, Eigen::Matrix, 3>(Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase > const&, igl::AABB, 3> const&, Eigen::Matrix&); +#endif diff --git a/src/igl/in_element.h b/src/igl/in_element.h new file mode 100644 index 000000000..dcbc03aa6 --- /dev/null +++ b/src/igl/in_element.h @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IN_ELEMENT_H +#define IGL_IN_ELEMENT_H + +#include "igl_inline.h" +#include "AABB.h" +#include +#include + +namespace igl +{ + // Determine whether each point in a list of points is in the elements of a + // mesh. + // + // templates: + // DIM dimension of vertices in V (# of columns) + // Inputs: + // V #V by dim list of mesh vertex positions. + // Ele #Ele by dim+1 list of mesh indices into #V. + // Q #Q by dim list of query point positions + // aabb axis-aligned bounding box tree object (see AABB.h) + // Outputs: + // I #Q list of indices into Ele of first containing element (-1 means no + // containing element) + template + IGL_INLINE void in_element( + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + const Eigen::PlainObjectBase & Q, + const AABB & aabb, + Eigen::VectorXi & I); + // Outputs: + // I #Q by #Ele sparse matrix revealing whether each element contains each + // point: I(q,e) means point q is in element e + template + IGL_INLINE void in_element( + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + const Eigen::PlainObjectBase & Q, + const AABB & aabb, + Eigen::SparseMatrix & I); +}; + +#ifndef IGL_STATIC_LIBRARY +#include "in_element.cpp" +#endif + +#endif diff --git a/src/igl/infinite_cost_stopping_condition.cpp b/src/igl/infinite_cost_stopping_condition.cpp new file mode 100644 index 000000000..033f2b678 --- /dev/null +++ b/src/igl/infinite_cost_stopping_condition.cpp @@ -0,0 +1,108 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "infinite_cost_stopping_condition.h" + +IGL_INLINE void igl::infinite_cost_stopping_condition( + const std::function & cost_and_placement, + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition) +{ + stopping_condition = + [&cost_and_placement] + ( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + const std::set > & Q, + const std::vector >::iterator > & Qit, + const Eigen::MatrixXd & C, + const int e, + const int /*e1*/, + const int /*e2*/, + const int /*f1*/, + const int /*f2*/)->bool + { + Eigen::RowVectorXd p; + double cost; + cost_and_placement(e,V,F,E,EMAP,EF,EI,cost,p); + return std::isinf(cost); + }; +} + +IGL_INLINE + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> + igl::infinite_cost_stopping_condition( + const std::function & cost_and_placement) +{ + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> stopping_condition; + infinite_cost_stopping_condition(cost_and_placement,stopping_condition); + return stopping_condition; +} + diff --git a/src/igl/infinite_cost_stopping_condition.h b/src/igl/infinite_cost_stopping_condition.h new file mode 100644 index 000000000..d37e98025 --- /dev/null +++ b/src/igl/infinite_cost_stopping_condition.h @@ -0,0 +1,85 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INFINITE_COST_STOPPING_CONDITION_H +#define IGL_INFINITE_COST_STOPPING_CONDITION_H +#include "igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + // Stopping condition function compatible with igl::decimate. The output + // function handle will return true if cost of next edge is infinite. + // + // Inputs: + // cost_and_placement handle being used by igl::collapse_edge + // Outputs: + // stopping_condition + // + IGL_INLINE void infinite_cost_stopping_condition( + const std::function & cost_and_placement, + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition); + IGL_INLINE + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> + infinite_cost_stopping_condition( + const std::function & cost_and_placement); +} + +#ifndef IGL_STATIC_LIBRARY +# include "infinite_cost_stopping_condition.cpp" +#endif +#endif + + diff --git a/src/igl/inradius.cpp b/src/igl/inradius.cpp new file mode 100644 index 000000000..d5a06ab50 --- /dev/null +++ b/src/igl/inradius.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "inradius.h" +#include "edge_lengths.h" +#include "doublearea.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedR> +IGL_INLINE void igl::inradius( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & r) +{ + Eigen::Matrix l; + Eigen::Matrix R; + igl::edge_lengths(V,F,l); + // If R is the circumradius, + // R*r = (abc)/(2*(a+b+c)) + // R = abc/(4*area) + // r(abc/(4*area)) = (abc)/(2*(a+b+c)) + // r/(4*area) = 1/(2*(a+b+c)) + // r = (2*area)/(a+b+c) + DerivedR A; + igl::doublearea(l,0.,A); + r = A.array() /l.array().rowwise().sum(); +} diff --git a/src/igl/inradius.h b/src/igl/inradius.h new file mode 100644 index 000000000..07166b4b9 --- /dev/null +++ b/src/igl/inradius.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INRADIUS_H +#define IGL_INRADIUS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the inradius of each triangle in a mesh (V,F) + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of triangle indices into V + // Outputs: + // R #F list of inradii + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedR> + IGL_INLINE void inradius( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & R); +} +#ifndef IGL_STATIC_LIBRARY +# include "inradius.cpp" +#endif +#endif + diff --git a/src/igl/internal_angles.cpp b/src/igl/internal_angles.cpp new file mode 100644 index 000000000..5c6e8dfbe --- /dev/null +++ b/src/igl/internal_angles.cpp @@ -0,0 +1,130 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// Copyright (C) 2015 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "internal_angles.h" +#include "squared_edge_lengths.h" +#include "parallel_for.h" +#include "get_seconds.h" + +template +IGL_INLINE void igl::internal_angles( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & K) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedV::Scalar Scalar; + if(F.cols() == 3) + { + // Edge lengths + Matrix< + Scalar, + DerivedF::RowsAtCompileTime, + DerivedF::ColsAtCompileTime> L_sq; + igl::squared_edge_lengths(V,F,L_sq); + + assert(F.cols() == 3 && "F should contain triangles"); + igl::internal_angles_using_squared_edge_lengths(L_sq,K); + }else + { + assert(V.cols() == 3 && "If F contains non-triangle facets, V must be 3D"); + K.resizeLike(F); + auto corner = []( + const typename DerivedV::ConstRowXpr & x, + const typename DerivedV::ConstRowXpr & y, + const typename DerivedV::ConstRowXpr & z) + { + typedef Eigen::Matrix RowVector3S; + RowVector3S v1 = (x-y).normalized(); + RowVector3S v2 = (z-y).normalized(); + // http://stackoverflow.com/questions/10133957/signed-angle-between-two-vectors-without-a-reference-plane + Scalar s = v1.cross(v2).norm(); + Scalar c = v1.dot(v2); + return atan2(s, c); + }; + for(unsigned i=0; i +IGL_INLINE void igl::internal_angles_using_squared_edge_lengths( + const Eigen::MatrixBase& L_sq, + Eigen::PlainObjectBase & K) +{ + typedef typename DerivedL::Index Index; + assert(L_sq.cols() == 3 && "Edge-lengths should come from triangles"); + const Index m = L_sq.rows(); + K.resize(m,3); + parallel_for( + m, + [&L_sq,&K](const Index f) + { + for(size_t d = 0;d<3;d++) + { + const auto & s1 = L_sq(f,d); + const auto & s2 = L_sq(f,(d+1)%3); + const auto & s3 = L_sq(f,(d+2)%3); + K(f,d) = acos((s3 + s2 - s1)/(2.*sqrt(s3*s2))); + } + }, + 1000l); +} + +template +IGL_INLINE void igl::internal_angles_using_edge_lengths( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase & K) +{ + // Note: + // Usage of internal_angles_using_squared_edge_lengths() is preferred to internal_angles_using_squared_edge_lengths() + // This function is deprecated and probably will be removed in future versions + typedef typename DerivedL::Index Index; + assert(L.cols() == 3 && "Edge-lengths should come from triangles"); + const Index m = L.rows(); + K.resize(m,3); + parallel_for( + m, + [&L,&K](const Index f) + { + for(size_t d = 0;d<3;d++) + { + const auto & s1 = L(f,d); + const auto & s2 = L(f,(d+1)%3); + const auto & s3 = L(f,(d+2)%3); + K(f,d) = acos((s3*s3 + s2*s2 - s1*s1)/(2.*s3*s2)); + } + }, + 1000l); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::internal_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/internal_angles.h b/src/igl/internal_angles.h new file mode 100644 index 000000000..1dd990ed4 --- /dev/null +++ b/src/igl/internal_angles.h @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INTERNAL_ANGLES_H +#define IGL_INTERNAL_ANGLES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute internal angles for a triangle mesh + // + // Inputs: + // V #V by dim eigen Matrix of mesh vertex nD positions + // F #F by poly-size eigen Matrix of face (triangle) indices + // Output: + // K #F by poly-size eigen Matrix of internal angles + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // + // Known Issues: + // if poly-size ≠ 3 then dim must equal 3. + template + IGL_INLINE void internal_angles( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & K); + // Inputs: + // L_sq #F by 3 list of squared edge lengths + // Output: + // K #F by poly-size eigen Matrix of internal angles + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // + // Note: + // Usage of internal_angles_using_squared_edge_lengths is preferred to internal_angles_using_squared_edge_lengths + template + IGL_INLINE void internal_angles_using_squared_edge_lengths( + const Eigen::MatrixBase& L_sq, + Eigen::PlainObjectBase & K); + // Inputs: + // L #F by 3 list of edge lengths + // Output: + // K #F by poly-size eigen Matrix of internal angles + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // + // Note: + // Usage of internal_angles_using_squared_edge_lengths is preferred to internal_angles_using_squared_edge_lengths + // This function is deprecated and probably will be removed in future versions + template + IGL_INLINE void internal_angles_using_edge_lengths( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase & K);} + +#ifndef IGL_STATIC_LIBRARY +# include "internal_angles.cpp" +#endif + +#endif + + diff --git a/src/igl/intersect.cpp b/src/igl/intersect.cpp new file mode 100644 index 000000000..f930fc0be --- /dev/null +++ b/src/igl/intersect.cpp @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "intersect.h" +template +IGL_INLINE void igl::intersect(const M & A, const M & B, M & C) +{ + // Stupid O(size(A) * size(B)) to do it + // Alec: This should be implemented by using unique and sort like `setdiff` + M dyn_C(A.size() > B.size() ? A.size() : B.size(),1); + // count of intersects + int c = 0; + // Loop over A + for(int i = 0;i +IGL_INLINE M igl::intersect(const M & A, const M & B) +{ + M C; + intersect(A,B,C); + return C; +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template Eigen::Matrix igl::intersect >(Eigen::Matrix const&, Eigen::Matrix const&); +#endif diff --git a/src/igl/intersect.h b/src/igl/intersect.h new file mode 100644 index 000000000..be4da91e5 --- /dev/null +++ b/src/igl/intersect.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INTERSECT_H +#define IGL_INTERSECT_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine the intersect between two sets of coefficients using == + // Templates: + // M matrix type that implements indexing by global index M(i) + // Inputs: + // A matrix of coefficients + // B matrix of coefficients + // Output: + // C matrix of elements appearing in both A and B, C is always resized to + // have a single column + template + IGL_INLINE void intersect(const M & A, const M & B, M & C); + // Last argument as return + template + IGL_INLINE M intersect(const M & A, const M & B); +} +#ifndef IGL_STATIC_LIBRARY +#include "intersect.cpp" +#endif +#endif diff --git a/src/igl/invert_diag.cpp b/src/igl/invert_diag.cpp new file mode 100644 index 000000000..b87a8d5b4 --- /dev/null +++ b/src/igl/invert_diag.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "invert_diag.h" + +template +IGL_INLINE void igl::invert_diag( + const Eigen::SparseMatrix& X, + Eigen::SparseMatrix& Y) +{ +#ifndef NDEBUG + typename Eigen::SparseVector dX = X.diagonal().sparseView(); + // Check that there are no zeros along the diagonal + assert(dX.nonZeros() == dX.size()); +#endif + // http://www.alecjacobson.com/weblog/?p=2552 + if(&Y != &X) + { + Y = X; + } + // Iterate over outside + for(int k=0; k::InnerIterator it (Y,k); it; ++it) + { + if(it.col() == it.row()) + { + T v = it.value(); + assert(v != 0); + v = ((T)1.0)/v; + Y.coeffRef(it.row(),it.col()) = v; + } + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::invert_diag(Eigen::SparseMatrix const&, Eigen::SparseMatrix&); +template void igl::invert_diag(Eigen::SparseMatrix const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/invert_diag.h b/src/igl/invert_diag.h new file mode 100644 index 000000000..2194b3f14 --- /dev/null +++ b/src/igl/invert_diag.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_INVERT_DIAG_H +#define IGL_INVERT_DIAG_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include + +namespace igl +{ + // Invert the diagonal entries of a matrix (if the matrix is a diagonal + // matrix then this amounts to inverting the matrix) + + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // X an m by n sparse matrix + // Outputs: + // Y an m by n sparse matrix + template + IGL_INLINE void invert_diag( + const Eigen::SparseMatrix& X, + Eigen::SparseMatrix& Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "invert_diag.cpp" +#endif + +#endif + diff --git a/src/igl/is_border_vertex.cpp b/src/igl/is_border_vertex.cpp new file mode 100644 index 000000000..a337609ae --- /dev/null +++ b/src/igl/is_border_vertex.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_border_vertex.h" +#include + +#include "triangle_triangle_adjacency.h" + +template +IGL_INLINE std::vector igl::is_border_vertex( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F) +{ + DerivedF FF; + igl::triangle_triangle_adjacency(F,FF); + std::vector ret(V.rows()); + for(unsigned i=0; i > igl::is_border_vertex, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template std::vector > igl::is_border_vertex, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template std::vector > igl::is_border_vertex, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/is_border_vertex.h b/src/igl/is_border_vertex.h new file mode 100644 index 000000000..2e611ac11 --- /dev/null +++ b/src/igl/is_border_vertex.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_BORDER_VERTEX_H +#define IGL_IS_BORDER_VERTEX_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Determine vertices on open boundary of a (manifold) mesh with triangle + // faces F + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3 list of triangle indices + // Returns #V vector of bools revealing whether vertices are on boundary + // + // Known Bugs: - does not depend on V + // - assumes mesh is edge manifold + // + template + IGL_INLINE std::vector is_border_vertex( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_border_vertex.cpp" +#endif + +#endif diff --git a/src/igl/is_boundary_edge.cpp b/src/igl/is_boundary_edge.cpp new file mode 100644 index 000000000..679214bbc --- /dev/null +++ b/src/igl/is_boundary_edge.cpp @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_boundary_edge.h" +#include "unique_rows.h" +#include "sort.h" + +template < + typename DerivedF, + typename DerivedE, + typename DerivedB> +void igl::is_boundary_edge( + const Eigen::PlainObjectBase & E, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B) +{ + using namespace Eigen; + using namespace std; + // Should be triangles + assert(F.cols() == 3); + // Should be edges + assert(E.cols() == 2); + // number of faces + const int m = F.rows(); + // Collect all directed edges after E + MatrixXi EallE(E.rows()+3*m,2); + EallE.block(0,0,E.rows(),E.cols()) = E; + for(int e = 0;e<3;e++) + { + for(int f = 0;f +void igl::is_boundary_edge( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) +{ + using namespace Eigen; + using namespace std; + // Should be triangles + assert(F.cols() == 3); + // number of faces + const int m = F.rows(); + // Collect all directed edges after E + MatrixXi allE(3*m,2); + for(int e = 0;e<3;e++) + { + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::is_boundary_edge, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::is_boundary_edge, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::is_boundary_edge, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::is_boundary_edge, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::is_boundary_edge, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/is_boundary_edge.h b/src/igl/is_boundary_edge.h new file mode 100644 index 000000000..f68ebb7e4 --- /dev/null +++ b/src/igl/is_boundary_edge.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IS_BOUNDARY_EDGE_H +#define IS_BOUNDARY_EDGE_H +#include + +namespace igl +{ + // IS_BOUNDARY_EDGE Determine for each edge E if it is a "boundary edge" in F. + // Boundary edges are undirected edges which occur only once. + // + // Inputs: + // E #E by 2 list of edges + // F #F by 3 list of triangles + // Outputs: + // B #E list bools. true iff unoriented edge occurs exactly once in F + // (non-manifold and non-existant edges will be false) + // + template < + typename DerivedF, + typename DerivedE, + typename DerivedB> + void is_boundary_edge( + const Eigen::PlainObjectBase & E, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B); + // Wrapper where Edges should also be computed from F + // E #E by 2 list of edges + // EMAP #F*3 list of indices mapping allE to E + template < + typename DerivedF, + typename DerivedE, + typename DerivedB, + typename DerivedEMAP> + void is_boundary_edge( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_boundary_edge.cpp" +#endif + +#endif diff --git a/src/igl/is_dir.cpp b/src/igl/is_dir.cpp new file mode 100644 index 000000000..e7346c1dd --- /dev/null +++ b/src/igl/is_dir.cpp @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_dir.h" + +#include + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +IGL_INLINE bool igl::is_dir(const char * filename) +{ + struct stat status; + if(stat(filename,&status)!=0) + { + // path does not exist + return false; + } + // Tests whether existing path is a directory + return S_ISDIR(status.st_mode); +} diff --git a/src/igl/is_dir.h b/src/igl/is_dir.h new file mode 100644 index 000000000..68e30a564 --- /dev/null +++ b/src/igl/is_dir.h @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_DIR_H +#define IGL_IS_DIR_H +#include "igl_inline.h" +namespace igl +{ + // Act like php's is_dir function + // http://php.net/manual/en/function.is-dir.php + // Tells whether the given filename is a directory. + // Input: + // filename Path to the file. If filename is a relative filename, it will + // be checked relative to the current working directory. + // Returns TRUE if the filename exists and is a directory, FALSE + // otherwise. + IGL_INLINE bool is_dir(const char * filename); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_dir.cpp" +#endif + +#endif diff --git a/src/igl/is_edge_manifold.cpp b/src/igl/is_edge_manifold.cpp new file mode 100644 index 000000000..1b106056e --- /dev/null +++ b/src/igl/is_edge_manifold.cpp @@ -0,0 +1,104 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_edge_manifold.h" +#include "oriented_facets.h" +#include "unique_simplices.h" + +#include +#include + +template < + typename DerivedF, + typename DerivedBF, + typename DerivedE, + typename DerivedEMAP, + typename DerivedBE> +IGL_INLINE bool igl::is_edge_manifold( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& BF, + Eigen::PlainObjectBase& E, + Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& BE) +{ + using namespace Eigen; + typedef typename DerivedF::Index Index; + typedef Matrix VectorXF; + typedef Matrix MatrixXF2; + MatrixXF2 allE; + oriented_facets(F,allE); + // Find unique undirected edges and mapping + VectorXF _; + unique_simplices(allE,E,_,EMAP); + std::vector count(E.rows(),0); + for(Index e = 0;e +IGL_INLINE bool igl::is_edge_manifold( + const Eigen::MatrixBase& F) +{ + // TODO: It's bothersome that this is not calling/reusing the code from the + // overload above. This could result in disagreement. + + // List of edges (i,j,f,c) where edge i > TTT; + for(int f=0;f v2) std::swap(v1,v2); + std::vector r(4); + r[0] = v1; r[1] = v2; + r[2] = f; r[3] = i; + TTT.push_back(r); + } + // Sort lexicographically + std::sort(TTT.begin(),TTT.end()); + + for(int i=2;i<(int)TTT.size();++i) + { + // Check any edges occur 3 times + std::vector& r1 = TTT[i-2]; + std::vector& r2 = TTT[i-1]; + std::vector& r3 = TTT[i]; + if ( (r1[0] == r2[0] && r2[0] == r3[0]) + && + (r1[1] == r2[1] && r2[1] == r3[1]) ) + { + return false; + } + } + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::is_edge_manifold >(Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +//template bool igl::is_edge_manifold(Eigen::Matrix const&, Eigen::Matrix const&); +template bool igl::is_edge_manifold >(Eigen::MatrixBase > const&); +template bool igl::is_edge_manifold >(Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/is_edge_manifold.h b/src/igl/is_edge_manifold.h new file mode 100644 index 000000000..5832806ac --- /dev/null +++ b/src/igl/is_edge_manifold.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_EDGE_MANIFOLD_H +#define IGL_IS_EDGE_MANIFOLD_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // check if the mesh is edge-manifold + // + // Inputs: + // V #V by dim list of mesh vertex positions **unneeded** + // F #F by 3 list of triangle indices + // Returns whether mesh is edge manifold. + // + // Known Bugs: + // Does not check for non-manifold vertices + // + // See also: is_vertex_manifold + template < + typename DerivedF, + typename DerivedBF, + typename DerivedE, + typename DerivedEMAP, + typename DerivedBE> + IGL_INLINE bool is_edge_manifold( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& BF, + Eigen::PlainObjectBase& E, + Eigen::PlainObjectBase& EMAP, + Eigen::PlainObjectBase& BE); + template + IGL_INLINE bool is_edge_manifold( + const Eigen::MatrixBase& F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_edge_manifold.cpp" +#endif + +#endif diff --git a/src/igl/is_file.cpp b/src/igl/is_file.cpp new file mode 100644 index 000000000..6c040b86c --- /dev/null +++ b/src/igl/is_file.cpp @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_file.h" + +#include +#ifdef _WIN32 +# ifndef S_ISREG +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +# endif +#endif +IGL_INLINE bool igl::is_file(const char * filename) +{ + struct stat status; + if(stat(filename,&status)!=0) + { + // path does not exist + return false; + } + // Tests whether existing path is a regular file + return S_ISREG(status.st_mode); +} diff --git a/src/igl/is_file.h b/src/igl/is_file.h new file mode 100644 index 000000000..5610c7ff3 --- /dev/null +++ b/src/igl/is_file.h @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_FILE_H +#define IGL_IS_FILE_H +#include "igl_inline.h" +namespace igl +{ + // Act like php's is_file function + // http://php.net/manual/en/function.is-file.php + // Tells whether the given filename is a regular file. + // Input: + // filename Path to the file. If filename is a relative filename, it will + // be checked relative to the current working directory. + // Returns TRUE if the filename exists and is a regular file, FALSE + // otherwise. + IGL_INLINE bool is_file(const char * filename); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_file.cpp" +#endif + +#endif diff --git a/src/igl/is_irregular_vertex.cpp b/src/igl/is_irregular_vertex.cpp new file mode 100644 index 000000000..34665d66f --- /dev/null +++ b/src/igl/is_irregular_vertex.cpp @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_irregular_vertex.h" +#include + +#include "is_border_vertex.h" + +template +IGL_INLINE std::vector igl::is_irregular_vertex(const Eigen::PlainObjectBase &V, const Eigen::PlainObjectBase &F) +{ + Eigen::VectorXi count = Eigen::VectorXi::Zero(F.maxCoeff()); + + for(unsigned i=0; i border = is_border_vertex(V,F); + + std::vector res(count.size()); + + for (unsigned i=0; i > igl::is_irregular_vertex, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template std::vector > igl::is_irregular_vertex, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/is_irregular_vertex.h b/src/igl/is_irregular_vertex.h new file mode 100644 index 000000000..e07e5ab70 --- /dev/null +++ b/src/igl/is_irregular_vertex.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_IRREGULAR_VERTEX_H +#define IGL_IS_IRREGULAR_VERTEX_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Determine if a vertex is irregular, i.e. it has more than 6 (triangles) + // or 4 (quads) incident edges. Vertices on the boundary are ignored. + // + // Inputs: + // V #V by dim list of vertex positions + // F #F by 3[4] list of triangle[quads] indices + // Returns #V vector of bools revealing whether vertices are singular + // + template + IGL_INLINE std::vector is_irregular_vertex(const Eigen::PlainObjectBase &V, const Eigen::PlainObjectBase &F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_irregular_vertex.cpp" +#endif + +#endif diff --git a/src/igl/is_planar.cpp b/src/igl/is_planar.cpp new file mode 100644 index 000000000..6058a03d4 --- /dev/null +++ b/src/igl/is_planar.cpp @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_planar.h" +IGL_INLINE bool igl::is_planar(const Eigen::MatrixXd & V) +{ + if(V.size() == 0) return false; + if(V.cols() == 2) return true; + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_PLANAR_H +#define IGL_IS_PLANAR_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Determine if a set of points lies on the XY plane + // + // Inputs: + // V #V by dim list of vertex positions + // Return true if a mesh has constant value of 0 in z coordinate + // + // Known bugs: Doesn't determine if vertex is flat if it doesn't lie on the + // XY plane. + IGL_INLINE bool is_planar(const Eigen::MatrixXd & V); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_planar.cpp" +#endif +#endif diff --git a/src/igl/is_readable.cpp b/src/igl/is_readable.cpp new file mode 100644 index 000000000..eb5e95347 --- /dev/null +++ b/src/igl/is_readable.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_readable.h" + +#ifdef _WIN32 +# include +IGL_INLINE bool igl::is_readable(const char* filename) +{ + FILE * f = fopen(filename,"r"); + if(f == NULL) + { + return false; + } + fclose(f); + return true; +} +#else +# include +# include +# include +IGL_INLINE bool igl::is_readable(const char* filename) +{ + // Check if file already exists + struct stat status; + if(stat(filename,&status)!=0) + { + return false; + } + + // Get current users uid and gid + uid_t this_uid = getuid(); + gid_t this_gid = getgid(); + + // Dealing with owner + if( this_uid == status.st_uid ) + { + return S_IRUSR & status.st_mode; + } + + // Dealing with group member + if( this_gid == status.st_gid ) + { + return S_IRGRP & status.st_mode; + } + + // Dealing with other + return S_IROTH & status.st_mode; + +} +#endif diff --git a/src/igl/is_readable.h b/src/igl/is_readable.h new file mode 100644 index 000000000..689701285 --- /dev/null +++ b/src/igl/is_readable.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_READABLE_H +#define IGL_IS_READABLE_H +#include "igl_inline.h" +namespace igl +{ + // Check if a file is reabable like PHP's is_readable function: + // http://www.php.net/manual/en/function.is-readable.php + // Input: + // filename path to file + // Returns true if file exists and is readable and false if file doesn't + // exist or *is not readable* + // + // Note: Windows version will not check user or group ids + IGL_INLINE bool is_readable(const char * filename); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_readable.cpp" +#endif + +#endif diff --git a/src/igl/is_sparse.cpp b/src/igl/is_sparse.cpp new file mode 100644 index 000000000..507393d85 --- /dev/null +++ b/src/igl/is_sparse.cpp @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_sparse.h" +template +IGL_INLINE bool igl::is_sparse( + const Eigen::SparseMatrix & A) +{ + return true; +} +template +IGL_INLINE bool igl::is_sparse( + const Eigen::PlainObjectBase& A) +{ + return false; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::is_sparse(Eigen::SparseMatrix const&); +// generated by autoexplicit.sh +template bool igl::is_sparse >(Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/is_sparse.h b/src/igl/is_sparse.h new file mode 100644 index 000000000..9547bb8e8 --- /dev/null +++ b/src/igl/is_sparse.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_SPARSE_H +#define IGL_IS_SPARSE_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Determine if a matrix A is sparse + // + // Template: + // T,DerivedA defines scalar type + // Inputs: + // A matrix in question + // Returns true if A is represented with a sparse matrix + template + IGL_INLINE bool is_sparse( + const Eigen::SparseMatrix & A); + template + IGL_INLINE bool is_sparse( + const Eigen::PlainObjectBase& A); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_sparse.cpp" +#endif + +#endif + diff --git a/src/igl/is_stl.cpp b/src/igl/is_stl.cpp new file mode 100644 index 000000000..240703616 --- /dev/null +++ b/src/igl/is_stl.cpp @@ -0,0 +1,64 @@ +#include "is_stl.h" +#include +IGL_INLINE bool igl::is_stl(FILE * stl_file, bool & is_ascii) +{ + + // solid? + // YES NO + // / if .stl, definitely binary + // / + // perfect size? + // YES NO + // + const auto perfect_size = [](FILE * stl_file)->bool + { + //stl_file = freopen(NULL,"rb",stl_file); + // Read 80 header + char header[80]; + if(fread(header,sizeof(char),80,stl_file)!=80) + { + return false; + } + // Read number of triangles + unsigned int num_tri; + if(fread(&num_tri,sizeof(unsigned int),1,stl_file)!=1) + { + return false; + } + fseek(stl_file,0,SEEK_END); + int file_size = ftell(stl_file); + fseek(stl_file,0,SEEK_SET); + //stl_file = freopen(NULL,"r",stl_file); + return (file_size == 80 + 4 + (4*12 + 2) * num_tri); + }; + // Specifically 80 character header + char header[80]; + char solid[80]; + is_ascii = true; + bool f = true; + if(fread(header,1,80,stl_file) != 80) + { + f = false; + goto finish; + } + + sscanf(header,"%s",solid); + if(std::string("solid") == solid) + { + f = true; + is_ascii = !perfect_size(stl_file); + }else + { + is_ascii = false; + f = perfect_size(stl_file); + } +finish: + rewind(stl_file); + return f; +} + +IGL_INLINE bool igl::is_stl(FILE * stl_file) +{ + bool is_ascii; + return is_stl(stl_file,is_ascii); +} diff --git a/src/igl/is_stl.h b/src/igl/is_stl.h new file mode 100644 index 000000000..e8f78df1a --- /dev/null +++ b/src/igl/is_stl.h @@ -0,0 +1,21 @@ +#ifndef IGL_IS_STL_H +#define IGL_IS_STL_H +#include "igl_inline.h" +#include +namespace igl +{ + // Given a file pointer, determine if it contains an .stl file and then + // rewind it. + // + // Inputs: + // stl_file pointer to file + // Outputs: + // is_ascii flag whether stl is ascii + // Returns whether stl_file is an .stl file + IGL_INLINE bool is_stl(FILE * stl_file, bool & is_ascii); + IGL_INLINE bool is_stl(FILE * stl_file); +}; +#ifndef IGL_STATIC_LIBRARY +# include "is_stl.cpp" +#endif +#endif diff --git a/src/igl/is_symmetric.cpp b/src/igl/is_symmetric.cpp new file mode 100644 index 000000000..f40473b66 --- /dev/null +++ b/src/igl/is_symmetric.cpp @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_symmetric.h" +#include "find.h" + +template +IGL_INLINE bool igl::is_symmetric(const Eigen::SparseMatrix& A) +{ + if(A.rows() != A.cols()) + { + return false; + } + assert(A.size() != 0); + Eigen::SparseMatrix AT = A.transpose(); + Eigen::SparseMatrix AmAT = A-AT; + //// Eigen screws up something with LLT if you try to do + //SparseMatrix AmAT = A-A.transpose(); + //// Eigen crashes at runtime if you try to do + // return (A-A.transpose()).nonZeros() == 0; + return AmAT.nonZeros() == 0; +} + +template +IGL_INLINE bool igl::is_symmetric( + const Eigen::PlainObjectBase& A) +{ + if(A.rows() != A.cols()) + { + return false; + } + assert(A.size() != 0); + return (A-A.transpose()).eval().nonZeros() == 0; +} + +template +IGL_INLINE bool igl::is_symmetric( + const Eigen::SparseMatrix& A, + const epsilonT epsilon) +{ + using namespace Eigen; + using namespace std; + if(A.rows() != A.cols()) + { + return false; + } + assert(A.size() != 0); + SparseMatrix AT = A.transpose(); + SparseMatrix AmAT = A-AT; + VectorXi AmATI,AmATJ; + Matrix AmATV; + find(AmAT,AmATI,AmATJ,AmATV); + if(AmATI.size() == 0) + { + return true; + } + + return AmATV.maxCoeff() < epsilon && AmATV.minCoeff() > -epsilon; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::is_symmetric >(Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::is_symmetric(Eigen::SparseMatrix const&); +template bool igl::is_symmetric(Eigen::SparseMatrix const&, double); +template bool igl::is_symmetric(Eigen::SparseMatrix const&, int); +#endif diff --git a/src/igl/is_symmetric.h b/src/igl/is_symmetric.h new file mode 100644 index 000000000..31725298e --- /dev/null +++ b/src/igl/is_symmetric.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_SYMMETRIC_H +#define IGL_IS_SYMMETRIC_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +namespace igl +{ + // Returns true if the given matrix is symmetric + // Inputs: + // A m by m matrix + // Returns true if the matrix is square and symmetric + template + IGL_INLINE bool is_symmetric(const Eigen::SparseMatrix& A); + // Inputs: + // epsilon threshold on L1 difference between A and A' + template + IGL_INLINE bool is_symmetric(const Eigen::SparseMatrix& A, const epsilonT epsilon); + template + IGL_INLINE bool is_symmetric( + const Eigen::PlainObjectBase& A); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_symmetric.cpp" +#endif + +#endif diff --git a/src/igl/is_vertex_manifold.cpp b/src/igl/is_vertex_manifold.cpp new file mode 100644 index 000000000..d266ebab7 --- /dev/null +++ b/src/igl/is_vertex_manifold.cpp @@ -0,0 +1,101 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_vertex_manifold.h" +#include "triangle_triangle_adjacency.h" +#include "vertex_triangle_adjacency.h" +#include "unique.h" +#include +#include +#include +#include +#include + +template +IGL_INLINE bool igl::is_vertex_manifold( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& B) +{ + using namespace std; + using namespace Eigen; + assert(F.cols() == 3 && "F must contain triangles"); + typedef typename DerivedF::Scalar Index; + typedef typename DerivedF::Index FIndex; + const FIndex m = F.rows(); + const Index n = F.maxCoeff()+1; + vector > > TT; + vector > > TTi; + triangle_triangle_adjacency(F,TT,TTi); + + vector > V2F,_1; + vertex_triangle_adjacency(n,F,V2F,_1); + + const auto & check_vertex = [&](const Index v)->bool + { + vector uV2Fv; + { + vector _1,_2; + unique(V2F[v],uV2Fv,_1,_2); + } + const FIndex one_ring_size = uV2Fv.size(); + if(one_ring_size == 0) + { + return false; + } + const FIndex g = uV2Fv[0]; + queue Q; + Q.push(g); + map seen; + while(!Q.empty()) + { + const FIndex f = Q.front(); + Q.pop(); + if(seen.count(f)==1) + { + continue; + } + seen[f] = true; + // Face f's neighbor lists opposite opposite each corner + for(const auto & c : TT[f]) + { + // Each neighbor + for(const auto & n : c) + { + bool contains_v = false; + for(Index nc = 0;nc, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template bool igl::is_vertex_manifold, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/is_vertex_manifold.h b/src/igl/is_vertex_manifold.h new file mode 100644 index 000000000..bf9caded6 --- /dev/null +++ b/src/igl/is_vertex_manifold.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_VERTEX_MANIFOLD_H +#define IGL_IS_VERTEX_MANIFOLD_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Check if a mesh is vertex-manifold. This only checks whether the faces + // incident on each vertex form exactly one connected component. Vertices + // incident on non-manifold edges are not consider non-manifold by this + // function (see is_edge_manifold.h). Unreferenced verties are considered + // non-manifold (zero components). + // + // Inputs: + // F #F by 3 list of triangle indices + // Outputs: + // B #V list indicate whether each vertex is locally manifold. + // Returns whether mesh is vertex manifold. + // + // See also: is_edge_manifold + template + IGL_INLINE bool is_vertex_manifold( + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_vertex_manifold.cpp" +#endif + +#endif diff --git a/src/igl/is_writable.cpp b/src/igl/is_writable.cpp new file mode 100644 index 000000000..f266b0078 --- /dev/null +++ b/src/igl/is_writable.cpp @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "is_writable.h" + +#ifdef _WIN32 +#include +#ifndef S_IWUSR +# define S_IWUSR S_IWRITE +#endif +IGL_INLINE bool is_writable(const char* filename) +{ + // Check if file already exists + struct stat status; + if(stat(filename,&status)!=0) + { + return false; + } + + return S_IWUSR & status.st_mode; +} +#else +#include +#include + +IGL_INLINE bool igl::is_writable(const char* filename) +{ + // Check if file already exists + struct stat status; + if(stat(filename,&status)!=0) + { + return false; + } + + // Get current users uid and gid + uid_t this_uid = getuid(); + gid_t this_gid = getgid(); + + // Dealing with owner + if( this_uid == status.st_uid ) + { + return S_IWUSR & status.st_mode; + } + + // Dealing with group member + if( this_gid == status.st_gid ) + { + return S_IWGRP & status.st_mode; + } + + // Dealing with other + return S_IWOTH & status.st_mode; +} +#endif diff --git a/src/igl/is_writable.h b/src/igl/is_writable.h new file mode 100644 index 000000000..8834e8afb --- /dev/null +++ b/src/igl/is_writable.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_IS_WRITABLE_H +#define IGL_IS_WRITABLE_H +#include "igl_inline.h" +namespace igl +{ + // Check if a file exists *and* is writable like PHP's is_writable function: + // http://www.php.net/manual/en/function.is-writable.php + // Input: + // filename path to file + // Returns true if file exists and is writable and false if file doesn't + // exist or *is not writable* + // + // Note: Windows version will not test group and user id + IGL_INLINE bool is_writable(const char * filename); +} + +#ifndef IGL_STATIC_LIBRARY +# include "is_writable.cpp" +#endif + +#endif diff --git a/src/igl/isdiag.cpp b/src/igl/isdiag.cpp new file mode 100644 index 000000000..452341bf8 --- /dev/null +++ b/src/igl/isdiag.cpp @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "isdiag.h" + +template +IGL_INLINE bool igl::isdiag(const Eigen::SparseMatrix & A) +{ + // Iterate over outside of A + for(int k=0; k::InnerIterator it (A,k); it; ++it) + { + if(it.row() != it.col() && it.value()!=0) + { + return false; + } + } + } + return true; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::isdiag(class Eigen::SparseMatrix const &); +#endif diff --git a/src/igl/isdiag.h b/src/igl/isdiag.h new file mode 100644 index 000000000..2bd555fa3 --- /dev/null +++ b/src/igl/isdiag.h @@ -0,0 +1,26 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ISDIAG_H +#define IGL_ISDIAG_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine if a given matrix is diagonal: all non-zeros lie on the + // main diagonal. + // + // Inputs: + // A m by n sparse matrix + // Returns true iff and only if the matrix is diagonal. + template + IGL_INLINE bool isdiag(const Eigen::SparseMatrix & A); +}; +#ifndef IGL_STATIC_LIBRARY +# include "isdiag.cpp" +#endif +#endif diff --git a/src/igl/ismember.cpp b/src/igl/ismember.cpp new file mode 100644 index 000000000..e3c3fd492 --- /dev/null +++ b/src/igl/ismember.cpp @@ -0,0 +1,185 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ismember.h" +#include "colon.h" +#include "list_to_matrix.h" +#include "sort.h" +#include "sortrows.h" +#include "unique.h" +#include "unique_rows.h" +#include + +template < + typename DerivedA, + typename DerivedB, + typename DerivedIA, + typename DerivedLOCB> +IGL_INLINE void igl::ismember( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & LOCB) +{ + using namespace Eigen; + using namespace std; + IA.resizeLike(A); + IA.setConstant(false); + LOCB.resizeLike(A); + LOCB.setConstant(-1); + // boring base cases + if(A.size() == 0) + { + return; + } + if(B.size() == 0) + { + return; + } + + // Get rid of any duplicates + typedef Matrix VectorA; + typedef Matrix VectorB; + const VectorA vA(Eigen::Map(DerivedA(A).data(), A.cols()*A.rows(),1)); + const VectorB vB(Eigen::Map(DerivedB(B).data(), B.cols()*B.rows(),1)); + VectorA uA; + VectorB uB; + Eigen::Matrix uIA,uIuA,uIB,uIuB; + unique(vA,uA,uIA,uIuA); + unique(vB,uB,uIB,uIuB); + // Sort both + VectorA sA; + VectorB sB; + Eigen::Matrix sIA,sIB; + sort(uA,1,true,sA,sIA); + sort(uB,1,true,sB,sIB); + + Eigen::Matrix uF = + Eigen::Matrix::Zero(sA.size(),1); + Eigen::Matrix uLOCB = + Eigen::Matrix:: + Constant(sA.size(),1,-1); + { + int bi = 0; + // loop over sA + bool past = false; + for(int a = 0;asB(bi)) + { + bi++; + past = bi>=sB.size(); + } + if(!past && sA(a)==sB(bi)) + { + uF(sIA(a)) = true; + uLOCB(sIA(a)) = uIB(sIB(bi)); + } + } + } + + Map< Matrix > + vIA(IA.data(),IA.cols()*IA.rows(),1); + Map< Matrix > + vLOCB(LOCB.data(),LOCB.cols()*LOCB.rows(),1); + for(int a = 0;a +IGL_INLINE void igl::ismember_rows( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & LOCB) +{ + using namespace Eigen; + using namespace std; + assert(A.cols() == B.cols() && "number of columns must match"); + IA.resize(A.rows(),1); + IA.setConstant(false); + LOCB.resize(A.rows(),1); + LOCB.setConstant(-1); + // boring base cases + if(A.size() == 0) + { + return; + } + if(B.size() == 0) + { + return; + } + + // Get rid of any duplicates + DerivedA uA; + DerivedB uB; + Eigen::Matrix uIA,uIuA,uIB,uIuB; + unique_rows(A,uA,uIA,uIuA); + unique_rows(B,uB,uIB,uIuB); + // Sort both + DerivedA sA; + DerivedB sB; + Eigen::Matrix sIA,sIB; + sortrows(uA,true,sA,sIA); + sortrows(uB,true,sB,sIB); + + Eigen::Matrix uF = + Eigen::Matrix::Zero(sA.size(),1); + Eigen::Matrix uLOCB = + Eigen::Matrix:: + Constant(sA.size(),1,-1); + const auto & row_greater_than = [&sA,&sB](const int a, const int b) + { + for(int c = 0;c sB(b,c)) return true; + if(sA(a,c) < sB(b,c)) return false; + } + return false; + }; + { + int bi = 0; + // loop over sA + bool past = false; + for(int a = 0;a=sB.rows(); + } + if(!past && (sA.row(a).array()==sB.row(bi).array()).all() ) + { + uF(sIA(a)) = true; + uLOCB(sIA(a)) = uIB(sIB(bi)); + } + } + } + + for(int a = 0;a, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::ismember_rows, Eigen::Matrix, Eigen::Array, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::ismember_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::ismember_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::ismember_rows, Eigen::Matrix, Eigen::Array, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ismember.h b/src/igl/ismember.h new file mode 100644 index 000000000..72be6006d --- /dev/null +++ b/src/igl/ismember.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ISMEMBER_H +#define IGL_ISMEMBER_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine if elements of A exist in elements of B + // + // Inputs: + // A ma by na matrix + // B mb by nb matrix + // Outputs: + // IA ma by na matrix of flags whether corresponding element of A exists in + // B + // LOCB ma by na matrix of indices in B locating matching element (-1 if + // not found), indices assume column major ordering + // + template < + typename DerivedA, + typename DerivedB, + typename DerivedIA, + typename DerivedLOCB> + IGL_INLINE void ismember( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & LOCB); + template < + typename DerivedA, + typename DerivedB, + typename DerivedIA, + typename DerivedLOCB> + IGL_INLINE void ismember_rows( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & LOCB); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "ismember.cpp" +#endif +#endif + diff --git a/src/igl/isolines.cpp b/src/igl/isolines.cpp new file mode 100644 index 000000000..6d4f87e32 --- /dev/null +++ b/src/igl/isolines.cpp @@ -0,0 +1,116 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + + +#include "isolines.h" + +#include +#include +#include + +#include "remove_duplicate_vertices.h" + + +template +IGL_INLINE void igl::isolines( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& z, + const int n, + Eigen::PlainObjectBase& isoV, + Eigen::PlainObjectBase& isoE) +{ + //Constants + const int dim = V.cols(); + assert(dim==2 || dim==3); + const int nVerts = V.rows(); + assert(z.rows() == nVerts && + "There must be as many function entries as vertices"); + const int nFaces = F.rows(); + const int np1 = n+1; + const double min = z.minCoeff(), max = z.maxCoeff(); + + + //Following http://www.alecjacobson.com/weblog/?p=2529 + typedef typename DerivedZ::Scalar Scalar; + typedef Eigen::Matrix Vec; + Vec iso(np1); + for(int i=0; i Matrix; + std::array t{{Matrix(nFaces, np1), + Matrix(nFaces, np1), Matrix(nFaces, np1)}}; + for(int i=0; i1) + t[k](i,j) = std::numeric_limits::quiet_NaN(); + } + } + } + + std::array,3> Fij, Iij; + for(int i=0; i LIVec; + typedef Eigen::Matrix LMat; + typedef Eigen::Matrix LIMat; + LIVec dummy1, dummy2; + igl::remove_duplicate_vertices(LMat(isoV), LIMat(isoE), + 2.2204e-15, isoV, dummy1, dummy2, isoE); + +} + + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::isolines, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int const, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &); +#endif + diff --git a/src/igl/isolines.h b/src/igl/isolines.h new file mode 100644 index 000000000..3b5199b6a --- /dev/null +++ b/src/igl/isolines.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + + +#ifndef IGL_ISOLINES_H +#define IGL_ISOLINES_H +#include "igl_inline.h" + +#include +#include + + +namespace igl +{ + // Constructs isolines for a function z given on a mesh (V,F) + // + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh faces (must be triangles) + // z #V by 1 list of function values evaluated at vertices + // n the number of desired isolines + // Outputs: + // isoV #isoV by dim list of isoline vertex positions + // isoE #isoE by 2 list of isoline edge positions + // + // + + template + IGL_INLINE void isolines( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& z, + const int n, + Eigen::PlainObjectBase& isoV, + Eigen::PlainObjectBase& isoE); +} + +#ifndef IGL_STATIC_LIBRARY +# include "isolines.cpp" +#endif + +#endif diff --git a/src/igl/jet.cpp b/src/igl/jet.cpp new file mode 100644 index 000000000..373f23bcb --- /dev/null +++ b/src/igl/jet.cpp @@ -0,0 +1,93 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "jet.h" +#include "colormap.h" + +template +IGL_INLINE void igl::jet(const T x, T * rgb) +{ + igl::colormap(igl::COLOR_MAP_TYPE_JET, x, rgb); +} + +template +IGL_INLINE void igl::jet(const T x_in, T & r, T & g, T & b) +{ + // Only important if the number of colors is small. In which case the rest is + // still wrong anyway + // x = linspace(0,1,jj)' * (1-1/jj) + 1/jj; + // + const double rone = 0.8; + const double gone = 1.0; + const double bone = 1.0; + T x = x_in; + x = (x_in<0 ? 0 : (x>1 ? 1 : x)); + + if (x<1. / 8.) + { + r = 0; + g = 0; + b = bone*(0.5 + (x) / (1. / 8.)*0.5); + } else if (x<3. / 8.) + { + r = 0; + g = gone*(x - 1. / 8.) / (3. / 8. - 1. / 8.); + b = bone; + } else if (x<5. / 8.) + { + r = rone*(x - 3. / 8.) / (5. / 8. - 3. / 8.); + g = gone; + b = (bone - (x - 3. / 8.) / (5. / 8. - 3. / 8.)); + } else if (x<7. / 8.) + { + r = rone; + g = (gone - (x - 5. / 8.) / (7. / 8. - 5. / 8.)); + b = 0; + } else + { + r = (rone - (x - 7. / 8.) / (1. - 7. / 8.)*0.5); + g = 0; + b = 0; + } +} + +template +IGL_INLINE void igl::jet( + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C) +{ + igl::colormap(igl::COLOR_MAP_TYPE_JET,Z, normalize, C); +} + +template +IGL_INLINE void igl::jet( + const Eigen::MatrixBase & Z, + const double min_z, + const double max_z, + Eigen::PlainObjectBase & C) +{ + igl::colormap(igl::COLOR_MAP_TYPE_JET, Z, min_z, max_z, C); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::jet(double, double*); +template void igl::jet(double, double&, double&, double&); +template void igl::jet(float, float*); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::jet(float, float&, float&, float&); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); + +template void igl::jet, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/jet.h b/src/igl/jet.h new file mode 100644 index 000000000..411b53af3 --- /dev/null +++ b/src/igl/jet.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_JET_H +#define IGL_JET_H +#include "igl_inline.h" +//#ifndef IGL_NO_EIGEN +# include +//#endif +namespace igl +{ + // JET like MATLAB's jet + // + // Inputs: + // m number of colors + // Outputs: + // J m by list of RGB colors between 0 and 1 + // +//#ifndef IGL_NO_EIGEN +// void jet(const int m, Eigen::MatrixXd & J); +//#endif + // Wrapper for directly computing [r,g,b] values for a given factor f between + // 0 and 1 + // + // Inputs: + // f factor determining color value as if 0 was min and 1 was max + // Outputs: + // r red value + // g green value + // b blue value + template + IGL_INLINE void jet(const T f, T * rgb); + template + IGL_INLINE void jet(const T f, T & r, T & g, T & b); + // Inputs: + // Z #Z list of factors + // normalize whether to normalize Z to be tightly between [0,1] + // Outputs: + // C #C by 3 list of rgb colors + template + IGL_INLINE void jet( + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C); + // Inputs: + // min_z value at blue + // max_z value at red + template + IGL_INLINE void jet( + const Eigen::MatrixBase & Z, + const double min_Z, + const double max_Z, + Eigen::PlainObjectBase & C); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "jet.cpp" +#endif + +#endif diff --git a/src/igl/knn.cpp b/src/igl/knn.cpp new file mode 100644 index 000000000..b2e355df1 --- /dev/null +++ b/src/igl/knn.cpp @@ -0,0 +1,105 @@ +#include "knn.h" +#include "parallel_for.h" + +#include +#include + +namespace igl { + template + IGL_INLINE void knn(const Eigen::MatrixBase& P, + const KType & k, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CN, + const Eigen::MatrixBase& W, + Eigen::PlainObjectBase & I) + { + typedef typename DerivedCN::Scalar CentersType; + typedef typename DerivedW::Scalar WidthsType; + + typedef Eigen::Matrix RowVector3PType; + + int n = P.rows(); + const KType real_k = std::min(n,k); + + auto distance_to_width_one_cube = [](RowVector3PType point){ + return std::sqrt(std::pow(std::max(std::abs(point(0))-1,0.0),2) + + std::pow(std::max(std::abs(point(1))-1,0.0),2) + + std::pow(std::max(std::abs(point(2))-1,0.0),2)); + }; + + auto distance_to_cube = [&distance_to_width_one_cube] + (RowVector3PType point, + Eigen::Matrix cube_center, + WidthsType cube_width){ + RowVector3PType transformed_point = (point-cube_center)/cube_width; + return cube_width*distance_to_width_one_cube(transformed_point); + }; + + I.resize(n,real_k); + + igl::parallel_for(n,[&](int i) + { + int points_found = 0; + RowVector3PType point_of_interest = P.row(i); + + //To make my priority queue take both points and octree cells, + //I use the indices 0 to n-1 for the n points, + // and the indices n to n+m-1 for the m octree cells + + // Using lambda to compare elements. + auto cmp = [&point_of_interest, &P, &CN, &W, + &n, &distance_to_cube](int left, int right) { + double leftdistance, rightdistance; + if(left < n){ //left is a point index + leftdistance = (P.row(left) - point_of_interest).norm(); + } else { //left is an octree cell + leftdistance = distance_to_cube(point_of_interest, + CN.row(left-n), + W(left-n)); + } + + if(right < n){ //left is a point index + rightdistance = (P.row(right) - point_of_interest).norm(); + } else { //left is an octree cell + rightdistance = distance_to_cube(point_of_interest, + CN.row(right-n), + W(right-n)); + } + return leftdistance >= rightdistance; + }; + + std::priority_queue, + decltype(cmp)> queue(cmp); + + queue.push(n); //This is the 0th octree cell (ie the root) + while(points_found < real_k){ + IndexType curr_cell_or_point = queue.top(); + queue.pop(); + if(curr_cell_or_point < n){ //current index is for is a point + I(i,points_found) = curr_cell_or_point; + points_found++; + } else { + IndexType curr_cell = curr_cell_or_point - n; + if(CH(curr_cell,0) == -1){ //In the case of a leaf + if(point_indices.at(curr_cell).size() > 0){ + //Assumption: Leaves either have one point, or none + queue.push(point_indices.at(curr_cell).at(0)); + } + } else { //Not a leaf + for(int j = 0; j < 8; j++){ + //+n to adjust for the octree cells + queue.push(CH(curr_cell,j)+n); + } + } + } + } + },1000); + } +} + + + + diff --git a/src/igl/knn.h b/src/igl/knn.h new file mode 100644 index 000000000..0411fc3ee --- /dev/null +++ b/src/igl/knn.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Gavin Barill +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/ + +#ifndef IGL_KNN +#define IGL_KNN +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Given a 3D set of points P, an whole number k, and an octree + // find the indicies of the k nearest neighbors for each point in P. + // Note that each point is its own neighbor. + // + // The octree data structures used in this function are intended to be the + // same ones output from igl::octree + // + // Inputs: + // P #P by 3 list of point locations + // k number of neighbors to find + // point_indices a vector of vectors, where the ith entry is a vector of + // the indices into P that are the ith octree cell's points + // CH #OctreeCells by 8, where the ith row is the indices of + // the ith octree cell's children + // CN #OctreeCells by 3, where the ith row is a 3d row vector + // representing the position of the ith cell's center + // W #OctreeCells, a vector where the ith entry is the width + // of the ith octree cell + // Outputs: + // I #P by k list of k-nearest-neighbor indices into P + template + IGL_INLINE void knn(const Eigen::MatrixBase& P, + const KType & k, + const std::vector > & point_indices, + const Eigen::MatrixBase& CH, + const Eigen::MatrixBase& CN, + const Eigen::MatrixBase& W, + Eigen::PlainObjectBase & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "knn.cpp" +#endif +#endif + diff --git a/src/igl/launch_medit.cpp b/src/igl/launch_medit.cpp new file mode 100644 index 000000000..151af8e5b --- /dev/null +++ b/src/igl/launch_medit.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "launch_medit.h" +#include "writeMESH.h" +#include +#include +#include +#include + +#define MEDIT_PATH "/opt/local/bin/medit" +#define TEMP_MESH_FILE "/var/tmp/temp.mesh" +#define TEMP_MEDIT_FILE "/var/tmp/temp.medit" + +template +IGL_INLINE int igl::launch_medit( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & F, + const bool wait) +{ + using namespace std; + // Build medit command, end with & so command returns without waiting + stringstream command; + command<, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +#endif + diff --git a/src/igl/launch_medit.h b/src/igl/launch_medit.h new file mode 100644 index 000000000..8a324bb22 --- /dev/null +++ b/src/igl/launch_medit.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LAUNCH_MEDIT_H +#define IGL_LAUNCH_MEDIT_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Writes the tetmesh in (V,T,F) to a temporary file, opens it with medit + // (forking with a system call) and returns + // + // + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedT integer-value: i.e. from MatrixXi + // DerivedF integer-value: i.e. from MatrixXi + // Inputs: + // V double matrix of vertex positions #V by 3 + // T #T list of tet indices into vertex positions + // F #F list of face indices into vertex positions + // wait whether to wait for medit process to finish before returning + // Returns returned value of system call (probably not useful if wait=false + // because of the fork) + template + IGL_INLINE int launch_medit( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & F, + const bool wait); +} + +#ifndef IGL_STATIC_LIBRARY +# include "launch_medit.cpp" +#endif + +#endif + diff --git a/src/igl/lbs_matrix.cpp b/src/igl/lbs_matrix.cpp new file mode 100644 index 000000000..a80f74be0 --- /dev/null +++ b/src/igl/lbs_matrix.cpp @@ -0,0 +1,184 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "lbs_matrix.h" + +IGL_INLINE void igl::lbs_matrix( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + Eigen::MatrixXd & M) +{ + using namespace Eigen; + // Number of dimensions + const int dim = V.cols(); + // Number of model points + const int n = V.rows(); + // Number of skinning transformations/weights + const int m = W.cols(); + + // Assumes that first n rows of weights correspond to V + assert(W.rows() >= n); + + M.resize(n,(dim+1)*m); + for(int j = 0;j& M) +{ + // number of mesh vertices + int n = V.rows(); + assert(n == W.rows()); + // dimension of mesh + int dim = V.cols(); + // number of handles + int m = W.cols(); + + M.resize(n*dim,m*dim*(dim+1)); + + // loop over coordinates of mesh vertices + for(int x = 0; x < dim; x++) + { + // loop over mesh vertices + for(int j = 0; j < n; j++) + { + // loop over handles + for(int i = 0; i < m; i++) + { + // loop over cols of affine transformations + for(int c = 0; c < (dim+1); c++) + { + double value = W(j,i); + if(c& M) +{ + // number of mesh vertices + int n = V.rows(); + assert(n == W.rows()); + assert(n == WI.rows()); + // dimension of mesh + int dim = V.cols(); + // number of handles + int m = WI.maxCoeff()+1; + // max number of influencing handles + int k = W.cols(); + assert(k == WI.cols()); + + M.resize(n*dim,m*dim*(dim+1)); + + // loop over coordinates of mesh vertices + for(int x = 0; x < dim; x++) + { + // loop over mesh vertices + for(int j = 0; j < n; j++) + { + // loop over handles + for(int i = 0; i < k; i++) + { + // loop over cols of affine transformations + for(int c = 0; c < (dim+1); c++) + { + double value = W(j,i); + if(c sM; + lbs_matrix_column(V,W,WI,sM); + M = MatrixXd(sM); +} diff --git a/src/igl/lbs_matrix.h b/src/igl/lbs_matrix.h new file mode 100644 index 000000000..307273d20 --- /dev/null +++ b/src/igl/lbs_matrix.h @@ -0,0 +1,94 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LBS_MATRIX_H +#define IGL_LBS_MATRIX_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // LBS_MATRIX Linear blend skinning can be expressed by V' = M * T where V' is + // a #V by dim matrix of deformed vertex positions (one vertex per row), M is a + // #V by (dim+1)*#T (composed of weights and rest positions) and T is a + // #T*(dim+1) by dim matrix of #T stacked transposed transformation matrices. + // See equations (1) and (2) in "Fast Automatic Skinning Transformations" + // [Jacobson et al 2012] + // + // Inputs: + // V #V by dim list of rest positions + // W #V+ by #T list of weights + // Outputs: + // M #V by #T*(dim+1) + // + // In MATLAB: + // kron(ones(1,size(W,2)),[V ones(size(V,1),1)]).*kron(W,ones(1,size(V,2)+1)) + IGL_INLINE void lbs_matrix( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + Eigen::MatrixXd & M); + // LBS_MATRIX construct a matrix that when multiplied against a column of + // affine transformation entries computes new coordinates of the vertices + // + // I'm not sure it makes since that the result is stored as a sparse matrix. + // The number of non-zeros per row *is* dependent on the number of mesh + // vertices and handles. + // + // Inputs: + // V #V by dim list of vertex rest positions + // W #V by #handles list of correspondence weights + // Output: + // M #V * dim by #handles * dim * (dim+1) matrix such that + // new_V(:) = LBS(V,W,A) = reshape(M * A,size(V)), where A is a column + // vectors formed by the entries in each handle's dim by dim+1 + // transformation matrix. Specifcally, A = + // reshape(permute(Astack,[3 1 2]),n*dim*(dim+1),1) + // or A = [Lxx;Lyx;Lxy;Lyy;tx;ty], and likewise for other dim + // if Astack(:,:,i) is the dim by (dim+1) transformation at handle i + IGL_INLINE void lbs_matrix_column( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + Eigen::SparseMatrix& M); + IGL_INLINE void lbs_matrix_column( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + Eigen::MatrixXd & M); + // Same as LBS_MATRIX above but instead of giving W as a full matrix of weights + // (each vertex has #handles weights), a constant number of weights are given + // for each vertex. + // + // Inputs: + // V #V by dim list of vertex rest positions + // W #V by k list of k correspondence weights per vertex + // WI #V by k list of k correspondence weight indices per vertex. Such that + // W(j,WI(i)) gives the ith most significant correspondence weight on vertex j + // Output: + // M #V * dim by #handles * dim * (dim+1) matrix such that + // new_V(:) = LBS(V,W,A) = reshape(M * A,size(V)), where A is a column + // vectors formed by the entries in each handle's dim by dim+1 + // transformation matrix. Specifcally, A = + // reshape(permute(Astack,[3 1 2]),n*dim*(dim+1),1) + // or A = [Lxx;Lyx;Lxy;Lyy;tx;ty], and likewise for other dim + // if Astack(:,:,i) is the dim by (dim+1) transformation at handle i + // + IGL_INLINE void lbs_matrix_column( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + const Eigen::MatrixXi & WI, + Eigen::SparseMatrix& M); + IGL_INLINE void lbs_matrix_column( + const Eigen::MatrixXd & V, + const Eigen::MatrixXd & W, + const Eigen::MatrixXi & WI, + Eigen::MatrixXd & M); +} +#ifndef IGL_STATIC_LIBRARY +#include "lbs_matrix.cpp" +#endif +#endif diff --git a/src/igl/lexicographic_triangulation.cpp b/src/igl/lexicographic_triangulation.cpp new file mode 100644 index 000000000..ce9e796ae --- /dev/null +++ b/src/igl/lexicographic_triangulation.cpp @@ -0,0 +1,132 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "lexicographic_triangulation.h" +#include "sortrows.h" + +#include +#include + +template< + typename DerivedP, + typename Orient2D, + typename DerivedF + > +IGL_INLINE void igl::lexicographic_triangulation( + const Eigen::PlainObjectBase& P, + Orient2D orient2D, + Eigen::PlainObjectBase& F) +{ + typedef typename DerivedP::Scalar Scalar; + const size_t num_pts = P.rows(); + if (num_pts < 3) { + throw "At least 3 points are required for triangulation!"; + } + + // Sort points in lexicographic order. + DerivedP ordered_P; + Eigen::VectorXi order; + igl::sortrows(P, true, ordered_P, order); + + std::vector faces; + std::list boundary; + const Scalar p0[] = {ordered_P(0, 0), ordered_P(0, 1)}; + const Scalar p1[] = {ordered_P(1, 0), ordered_P(1, 1)}; + for (size_t i=2; i 0) { + for (size_t j=0; j<=i-2; j++) { + faces.push_back({order[j], order[j+1], order[i]}); + } + } else if (orientation < 0) { + for (size_t j=0; j<=i-2; j++) { + faces.push_back({order[j+1], order[j], order[i]}); + } + } + // Initialize current boundary. + boundary.insert(boundary.end(), order.data(), order.data()+i+1); + if (orientation < 0) { + boundary.reverse(); + } + } + } else { + const size_t bd_size = boundary.size(); + assert(bd_size >= 3); + std::vector orientations; + for (auto itr=boundary.begin(); itr!=boundary.end(); itr++) { + auto next_itr = std::next(itr, 1); + if (next_itr == boundary.end()) { + next_itr = boundary.begin(); + } + const Scalar bd_p0[] = {P(*itr, 0), P(*itr, 1)}; + const Scalar bd_p1[] = {P(*next_itr, 0), P(*next_itr, 1)}; + auto orientation = orient2D(bd_p0, bd_p1, curr_p); + if (orientation < 0) { + faces.push_back({*next_itr, *itr, order[i]}); + } + orientations.push_back(orientation); + } + + auto left_itr = boundary.begin(); + auto right_itr = boundary.begin(); + auto curr_itr = boundary.begin(); + for (size_t j=0; j= 0 && orientations[prev] < 0) { + right_itr = curr_itr; + } else if (orientations[j] < 0 && orientations[prev] >= 0) { + left_itr = curr_itr; + } + } + assert(left_itr != right_itr); + + for (auto itr=left_itr; itr!=right_itr; itr++) { + if (itr == boundary.end()) itr = boundary.begin(); + if (itr == right_itr) break; + if (itr == left_itr || itr == right_itr) continue; + itr = boundary.erase(itr); + if (itr == boundary.begin()) { + itr = boundary.end(); + } else { + itr--; + } + } + + if (right_itr == boundary.begin()) { + assert(std::next(left_itr, 1) == boundary.end()); + boundary.insert(boundary.end(), order[i]); + } else { + assert(std::next(left_itr, 1) == right_itr); + boundary.insert(right_itr, order[i]); + } + } + } + + const size_t num_faces = faces.size(); + if (num_faces == 0) { + // All input points are collinear. + // Do nothing here. + } else { + F.resize(num_faces, 3); + for (size_t i=0; i, short (*)(double const*, double const*, double const*), Eigen::Matrix >(Eigen::PlainObjectBase > const&, short (*)(double const*, double const*, double const*), Eigen::PlainObjectBase >&); +#endif \ No newline at end of file diff --git a/src/igl/lexicographic_triangulation.h b/src/igl/lexicographic_triangulation.h new file mode 100644 index 000000000..4e0b81eb5 --- /dev/null +++ b/src/igl/lexicographic_triangulation.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_LEXICOGRAPHIC_TRIANGULATION_H +#define IGL_LEXICOGRAPHIC_TRIANGULATION_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Given a set of points in 2D, return a lexicographic triangulation of these + // points. + // + // Inputs: + // P #P by 2 list of vertex positions + // orient2D A functor such that orient2D(pa, pb, pc) returns + // 1 if pa,pb,pc forms a conterclockwise triangle. + // -1 if pa,pb,pc forms a clockwise triangle. + // 0 if pa,pb,pc are collinear. + // where the argument pa,pb,pc are of type Scalar[2]. + // + // Outputs: + // F #F by 3 of faces in lexicographic triangulation. + template< + typename DerivedP, + typename Orient2D, + typename DerivedF + > + IGL_INLINE void lexicographic_triangulation( + const Eigen::PlainObjectBase& P, + Orient2D orient2D, + Eigen::PlainObjectBase& F); +} + + + + +#ifndef IGL_STATIC_LIBRARY +# include "lexicographic_triangulation.cpp" +#endif +#endif diff --git a/src/igl/lim/lim.cpp b/src/igl/lim/lim.cpp new file mode 100644 index 000000000..1f4659062 --- /dev/null +++ b/src/igl/lim/lim.cpp @@ -0,0 +1,137 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "lim.h" +#include + + +IGL_INLINE igl::lim::State igl::lim::lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima) +{ + return (State)ComputeLIM( + vertices, + initialVertices, + elements, + constraintMatrix, + constraintTargets, + (EnergyType)energyType, + tolerance, + maxIteration, + findLocalMinima + ); +} + +IGL_INLINE igl::lim::State igl::lim::lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima, + bool enableOuput, + bool enableBarriers, + bool enableAlphaUpdate, + double beta, + double eps) +{ + return (State)ComputeLIM( + vertices, + initialVertices, + elements, + constraintMatrix, + constraintTargets, + (EnergyType)energyType, + tolerance, + maxIteration, + findLocalMinima, + enableOuput, + enableBarriers, + enableAlphaUpdate, + beta, + eps + ); +} + +IGL_INLINE igl::lim::State igl::lim::lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const std::vector& borderVertices, + const Eigen::Matrix& gradients, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima) +{ + return (State)ComputeLIM( + vertices, + initialVertices, + elements, + borderVertices, + gradients, + constraintMatrix, + constraintTargets, + (EnergyType)energyType, + tolerance, + maxIteration, + findLocalMinima + ); +} + +IGL_INLINE igl::lim::State igl::lim::lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const std::vector& borderVertices, + const Eigen::Matrix& gradients, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima, + bool enableOuput, + bool enableBarriers, + bool enableAlphaUpdate, + double beta, + double eps) +{ + return (State)ComputeLIM( + vertices, + initialVertices, + elements, + borderVertices, + gradients, + constraintMatrix, + constraintTargets, + (EnergyType)energyType, + tolerance, + maxIteration, + findLocalMinima, + enableOuput, + enableBarriers, + enableAlphaUpdate, + beta, + eps); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/lim/lim.h b/src/igl/lim/lim.h new file mode 100644 index 000000000..c4d3f4ad4 --- /dev/null +++ b/src/igl/lim/lim.h @@ -0,0 +1,133 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LIM_LIM_H +#define IGL_LIM_LIM_H +#include +#include +#include + +namespace igl +{ + namespace lim + { + // Computes a locally injective mapping of a triangle or tet-mesh based on + // a deformation energy subject to some provided linear positional + // constraints Cv-d. + // + // Inputs: + // vertices vx3 matrix containing vertex position of the mesh + // initialVertices vx3 matrix containing vertex position of initial + // rest pose mesh + // elements exd matrix containing vertex indices of all elements + // borderVertices (only needed for 2D LSCM) vector containing indices + // of border vertices + // gradients (only needed for 2D Poisson) vector containing + // partial derivatives of target element gradients + // (structure is: [xx_0, xy_0, xx_1, xy_1, ..., xx_v, + // xy_v, yx_0, yy_0, yx_1, yy_1, ..., yx_v, yy_v]') + // constraintMatrix C: (c)x(v*(d-1)) sparse linear positional constraint + // matrix. X an Y-coordinates are alternatingly stacked + // per row (structure for triangles: [x_1, y_1, x_2, + // y_2, ..., x_v,y_v]) + // constraintTargets d: c vector target positions + // energyType type of used energy: + // Dirichlet, Laplacian, Green, ARAP, LSCM, Poisson (only 2D), UniformLaplacian, Identity + // tolerance max squared positional constraints error + // maxIteration max number of iterations + // findLocalMinima iterating until a local minima is found. If not + // enabled only tolerance must be fulfilled. + // enableOutput (optional) enables the output (#iteration / hessian correction / step size / positional constraints / barrier constraints / deformation energy) (default : true) + // enableBarriers (optional) enables the non-flip constraints (default = true) + // enableAlphaUpdate (optional) enables dynamic alpha weight adjustment (default = true) + // beta (optional) steepness factor of barrier slopes (default: ARAP/LSCM = 0.01, Green = 1) + // eps (optional) smallest valid triangle area (default: 1e-5 * smallest triangle) + // + // where: + // v : # vertices + // c : # linear constraints + // e : # elements of mesh + // d : # vertices per element (triangle = 3, tet = 4) + //-------------------------------------------------------------------------- + // Output: + // vertices vx3 matrix containing resulting vertex position of the + // mesh + //-------------------------------------------------------------------------- + // Return values: + // Succeeded : Successful optimization with fulfilled tolerance + // LocalMinima : Convergenged to a local minima / tolerance not fulfilled + // IterationLimit : Max iteration reached before tolerance was fulfilled + // Infeasible : not feasible -> has inverted elements (decrease eps?) + + enum Energy { Dirichlet = 0, Laplacian=1, Green=2, ARAP=3, LSCM=4, Poisson=5, UniformLaplacian=6, Identity=7 }; + enum State { Uninitialized = -4, Infeasible = -3, IterationLimit = -2, LocalMinima = -1, Running = 0, Succeeded = 1 }; + + State lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima); + + State lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima, + bool enableOuput, + bool enableBarriers, + bool enableAlphaUpdate, + double beta, + double eps); + + State lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const std::vector& borderVertices, + const Eigen::Matrix& gradients, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima); + + State lim( + Eigen::Matrix& vertices, + const Eigen::Matrix& initialVertices, + const Eigen::Matrix& elements, + const std::vector& borderVertices, + const Eigen::Matrix& gradients, + const Eigen::SparseMatrix& constraintMatrix, + const Eigen::Matrix& constraintTargets, + Energy energyType, + double tolerance, + int maxIteration, + bool findLocalMinima, + bool enableOuput, + bool enableBarriers, + bool enableAlphaUpdate, + double beta, + double eps); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "lim.cpp" +#endif + +#endif diff --git a/src/igl/limit_faces.cpp b/src/igl/limit_faces.cpp new file mode 100644 index 000000000..e54c40b45 --- /dev/null +++ b/src/igl/limit_faces.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "limit_faces.h" + +#include +#include + +template +IGL_INLINE void igl::limit_faces( + const MatF & F, + const VecL & L, + const bool exclusive, + MatF & LF) +{ + using namespace std; + using namespace Eigen; + vector in(F.rows(),false); + int num_in = 0; + // loop over faces + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LIMIT_FACES_H +#define IGL_LIMIT_FACES_H +#include "igl_inline.h" +namespace igl +{ + // LIMIT_FACES limit given faces F to those which contain (only) indices found + // in L. + // + // [LF] = limit_faces(F,L,exclusive); + // [LF,in] = limit_faces(F,L,exclusive); + // + // Templates: + // MatF matrix type of faces, matrixXi + // VecL matrix type of vertex indices, VectorXi + // Inputs: + // F #F by 3 list of face indices + // L #L by 1 list of allowed indices + // exclusive flag specifying whether a face is included only if all its + // indices are in L, default is false + // Outputs: + // LF #LF by 3 list of remaining faces after limiting + // in #F list of whether given face was included + // + template + IGL_INLINE void limit_faces( + const MatF & F, + const VecL & L, + const bool exclusive, + MatF & LF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "limit_faces.cpp" +#endif + +#endif diff --git a/src/igl/line_field_missmatch.cpp b/src/igl/line_field_missmatch.cpp new file mode 100644 index 000000000..21c45b710 --- /dev/null +++ b/src/igl/line_field_missmatch.cpp @@ -0,0 +1,144 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Nico Pietroni +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "line_field_missmatch.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace igl { +template +class MissMatchCalculatorLine +{ +public: + + const Eigen::PlainObjectBase &V; + const Eigen::PlainObjectBase &F; + const Eigen::PlainObjectBase &PD1; + const Eigen::PlainObjectBase &PD2; + DerivedV N; + +private: + // internal + std::vector V_border; // bool + std::vector > VF; + std::vector > VFi; + DerivedF TT; + DerivedF TTi; + + +private: + + //compute the mismatch between 2 faces + inline int MissMatchByLine(const int f0, + const int f1) + { + Eigen::Matrix dir0 = PD1.row(f0); + Eigen::Matrix dir1 = PD1.row(f1); + Eigen::Matrix n0 = N.row(f0); + Eigen::Matrix n1 = N.row(f1); + + Eigen::Matrix dir1Rot = igl::rotation_matrix_from_directions(n1,n0)*dir1; + dir1Rot.normalize(); + + // TODO: this should be equivalent to the other code below, to check! + // Compute the angle between the two vectors + // double a0 = atan2(dir0.dot(B2.row(f0)),dir0.dot(B1.row(f0))); + // double a1 = atan2(dir1Rot.dot(B2.row(f0)),dir1Rot.dot(B1.row(f0))); + // + // double angle_diff = a1-a0; //VectToAngle(f0,dir1Rot); + + double angle_diff = atan2(dir1Rot.dot(PD2.row(f0)),dir1Rot.dot(PD1.row(f0))); + + double step=igl::PI; + int i=(int)std::floor((angle_diff/step)+0.5); + assert((i>=-2)&&(i<=2)); + int k=0; + if (i>=0) + k=i%2; + else + k=(2+i)%2; + + assert((k==0)||(k==1)); + return (k*2); + } + +public: + + inline MissMatchCalculatorLine(const Eigen::PlainObjectBase &_V, + const Eigen::PlainObjectBase &_F, + const Eigen::PlainObjectBase &_PD1, + const Eigen::PlainObjectBase &_PD2 + ): + V(_V), + F(_F), + PD1(_PD1), + PD2(_PD2) + { + igl::per_face_normals(V,F,N); + V_border = igl::is_border_vertex(V,F); + igl::vertex_triangle_adjacency(V,F,VF,VFi); + igl::triangle_triangle_adjacency(F,TT,TTi); + } + + inline void calculateMissmatchLine(Eigen::PlainObjectBase &Handle_MMatch) + { + Handle_MMatch.setConstant(F.rows(),3,-1); + for (unsigned int i=0;i +IGL_INLINE void igl::line_field_missmatch(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const bool isCombed, + Eigen::PlainObjectBase &missmatch) +{ + DerivedV PD1_combed; + DerivedV PD2_combed; + + if (!isCombed) + igl::comb_line_field(V,F,PD1,PD1_combed); + else + { + PD1_combed = PD1; + } + Eigen::MatrixXd B1,B2,B3; + igl::local_basis(V,F,B1,B2,B3); + PD2_combed = igl::rotate_vectors(PD1_combed, Eigen::VectorXd::Constant(1,igl::PI/2), B1, B2); + igl::MissMatchCalculatorLine sf(V, F, PD1_combed, PD2_combed); + sf.calculateMissmatchLine(missmatch); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/line_field_missmatch.h b/src/igl/line_field_missmatch.h new file mode 100644 index 000000000..a939685cb --- /dev/null +++ b/src/igl/line_field_missmatch.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Nico Pietroni +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_LINE_FIELD_MISSMATCH_H +#define IGL_LINE_FIELD_MISSMATCH_H +#include "igl_inline.h" +#include +namespace igl +{ + // Calculates the missmatch (integer), at each face edge, of a cross field defined on the mesh faces. + // The integer missmatch is a multiple of pi/2 that transforms the cross on one side of the edge to + // the cross on the other side. It represents the deviation from a Lie connection across the edge. + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (quad) indices + // PD1 #F by 3 eigen Matrix of the first per face cross field vector + // PD2 #F by 3 eigen Matrix of the second per face cross field vector + // isCombed boolean, specifying whether the field is combed (i.e. matching has been precomputed. + // If not, the field is combed first. + // Output: + // Handle_MMatch #F by 3 eigen Matrix containing the integer missmatch of the cross field + // across all face edges + // + + template + IGL_INLINE void line_field_missmatch(const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + const Eigen::PlainObjectBase &PD1, + const bool isCombed, + Eigen::PlainObjectBase &missmatch); +} +#ifndef IGL_STATIC_LIBRARY +#include "line_field_missmatch.cpp" +#endif + +#endif diff --git a/src/igl/line_search.cpp b/src/igl/line_search.cpp new file mode 100644 index 000000000..c94eeaa52 --- /dev/null +++ b/src/igl/line_search.cpp @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "line_search.h" + +IGL_INLINE double igl::line_search( + Eigen::MatrixXd& x, + const Eigen::MatrixXd& d, + double step_size, + std::function energy, + double cur_energy) +{ + double old_energy; + if (cur_energy > 0) + { + old_energy = cur_energy; + } + else + { + old_energy = energy(x); // no energy was given -> need to compute the current energy + } + double new_energy = old_energy; + int cur_iter = 0; int MAX_STEP_SIZE_ITER = 12; + + while (new_energy >= old_energy && cur_iter < MAX_STEP_SIZE_ITER) + { + Eigen::MatrixXd new_x = x + step_size * d; + + double cur_e = energy(new_x); + if ( cur_e >= old_energy) + { + step_size /= 2; + } + else + { + x = new_x; + new_energy = cur_e; + } + cur_iter++; + } + return new_energy; +} + + +#ifdef IGL_STATIC_LIBRARY +#endif diff --git a/src/igl/line_search.h b/src/igl/line_search.h new file mode 100644 index 000000000..057c6a43f --- /dev/null +++ b/src/igl/line_search.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LINE_SEARCH_H +#define IGL_LINE_SEARCH_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Implement a bisection linesearch to minimize a mesh-based energy on vertices given at 'x' at a search direction 'd', + // with initial step size. Stops when a point with lower energy is found, or after maximal iterations have been reached. + // + // Inputs: + // x #X by dim list of variables + // d #X by dim list of a given search direction + // i_step_size initial step size + // energy A function to compute the mesh-based energy (return an energy that is bigger than 0) + // cur_energy(OPTIONAL) The energy at the given point. Helps save redundant computations. + // This is optional. If not specified, the function will compute it. + // Outputs: + // x #X by dim list of variables at the new location + // Returns the energy at the new point 'x' + IGL_INLINE double line_search( + Eigen::MatrixXd& x, + const Eigen::MatrixXd& d, + double i_step_size, + std::function energy, + double cur_energy = -1); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "line_search.cpp" +#endif + +#endif diff --git a/src/igl/line_segment_in_rectangle.cpp b/src/igl/line_segment_in_rectangle.cpp new file mode 100644 index 000000000..6f47c7d4c --- /dev/null +++ b/src/igl/line_segment_in_rectangle.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "line_segment_in_rectangle.h" + +IGL_INLINE bool igl::line_segment_in_rectangle( + const Eigen::Vector2d & s, + const Eigen::Vector2d & d, + const Eigen::Vector2d & A, + const Eigen::Vector2d & B) +{ + using namespace std; + using namespace Eigen; + // http://stackoverflow.com/a/100165/148668 + const auto SegmentIntersectRectangle = [](double a_rectangleMinX, + double a_rectangleMinY, + double a_rectangleMaxX, + double a_rectangleMaxY, + double a_p1x, + double a_p1y, + double a_p2x, + double a_p2y)->bool + { + // Find min and max X for the segment + + double minX = a_p1x; + double maxX = a_p2x; + + if(a_p1x > a_p2x) + { + minX = a_p2x; + maxX = a_p1x; + } + + // Find the intersection of the segment's and rectangle's x-projections + + if(maxX > a_rectangleMaxX) + { + maxX = a_rectangleMaxX; + } + + if(minX < a_rectangleMinX) + { + minX = a_rectangleMinX; + } + + if(minX > maxX) // If their projections do not intersect return false + { + return false; + } + + // Find corresponding min and max Y for min and max X we found before + + double minY = a_p1y; + double maxY = a_p2y; + + double dx = a_p2x - a_p1x; + + if(fabs(dx) > 0.0000001) + { + double a = (a_p2y - a_p1y) / dx; + double b = a_p1y - a * a_p1x; + minY = a * minX + b; + maxY = a * maxX + b; + } + + if(minY > maxY) + { + double tmp = maxY; + maxY = minY; + minY = tmp; + } + + // Find the intersection of the segment's and rectangle's y-projections + + if(maxY > a_rectangleMaxY) + { + maxY = a_rectangleMaxY; + } + + if(minY < a_rectangleMinY) + { + minY = a_rectangleMinY; + } + + if(minY > maxY) // If Y-projections do not intersect return false + { + return false; + } + + return true; + }; + const double minX = std::min(A(0),B(0)); + const double minY = std::min(A(1),B(1)); + const double maxX = std::max(A(0),B(0)); + const double maxY = std::max(A(1),B(1)); + bool ret = SegmentIntersectRectangle(minX,minY,maxX,maxY,s(0),s(1),d(0),d(1)); + return ret; +} diff --git a/src/igl/line_segment_in_rectangle.h b/src/igl/line_segment_in_rectangle.h new file mode 100644 index 000000000..6d7eaf404 --- /dev/null +++ b/src/igl/line_segment_in_rectangle.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LINE_SEGMENT_IN_RECTANGLE_H +#define IGL_LINE_SEGMENT_IN_RECTANGLE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine whether a line segment overlaps with a rectangle. + // + // Inputs: + // s source point of line segment + // d dest point of line segment + // A first corner of rectangle + // B opposite corner of rectangle + // Returns true if line segment is at all inside rectangle + IGL_INLINE bool line_segment_in_rectangle( + const Eigen::Vector2d & s, + const Eigen::Vector2d & d, + const Eigen::Vector2d & A, + const Eigen::Vector2d & B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "line_segment_in_rectangle.cpp" +#endif + +#endif diff --git a/src/igl/linprog.cpp b/src/igl/linprog.cpp new file mode 100644 index 000000000..b73ffc513 --- /dev/null +++ b/src/igl/linprog.cpp @@ -0,0 +1,302 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "linprog.h" +#include "slice.h" +#include "slice_into.h" +#include "find.h" +#include "colon.h" +#include + +//#define IGL_LINPROG_VERBOSE +IGL_INLINE bool igl::linprog( + const Eigen::VectorXd & c, + const Eigen::MatrixXd & _A, + const Eigen::VectorXd & b, + const int k, + Eigen::VectorXd & x) +{ + // This is a very literal translation of + // http://www.mathworks.com/matlabcentral/fileexchange/2166-introduction-to-linear-algebra/content/strang/linprog.m + using namespace Eigen; + using namespace std; + bool success = true; + // number of constraints + const int m = _A.rows(); + // number of original variables + const int n = _A.cols(); + // number of iterations + int it = 0; + // maximum number of iterations + //const int MAXIT = 10*m; + const int MAXIT = 100*m; + // residual tolerance + const double tol = 1e-10; + const auto & sign = [](const Eigen::VectorXd & B) -> Eigen::VectorXd + { + Eigen::VectorXd Bsign(B.size()); + for(int i = 0;i0?1:(B(i)<0?-1:0); + } + return Bsign; + }; + // initial (inverse) basis matrix + VectorXd Dv = sign(sign(b).array()+0.5); + Dv.head(k).setConstant(1.); + MatrixXd D = Dv.asDiagonal(); + // Incorporate slack variables + MatrixXd A(_A.rows(),_A.cols()+D.cols()); + A<<_A,D; + // Initial basis + VectorXi B = igl::colon(n,n+m-1); + // non-basis, may turn out that vector<> would be better here + VectorXi N = igl::colon(0,n-1); + int j; + double bmin = b.minCoeff(&j); + int phase; + VectorXd xb; + VectorXd s; + VectorXi J; + if(k>0 && bmin<0) + { + phase = 1; + xb = VectorXd::Ones(m); + // super cost + s.resize(n+m+1); + s<(0,n-1),B(j); + J.resize(B.size()-1); + // [0 1 2 3 4] + // ^ + // [0 1] + // [3 4] + J.head(j) = B.head(j); + J.tail(B.size()-j-1) = B.tail(B.size()-j-1); + B(j) = n+m; + MatrixXd AJ; + igl::slice(A,J,2,AJ); + const VectorXd a = b - AJ.rowwise().sum(); + { + MatrixXd old_A = A; + A.resize(A.rows(),A.cols()+a.cols()); + A<=0 + { + phase = 1; + xb = b.array().abs(); + s.resize(n+m); + // super cost + s<::max(); + // Lagrange mutipliers fro Ax=b + VectorXd yb = D.transpose() * igl::slice(s,B); + while(true) + { + if(MAXIT>0 && it>=MAXIT) + { +#ifdef IGL_LINPROG_VERBOSE + cerr<<"linprog: warning! maximum iterations without convergence."<=-tol*(sN.array().abs().maxCoeff()+1)) + { + break; + } + // increment iteration count + it++; + // apply Bland's rule to avoid cycling + if(df>=0) + { + if(MAXIT == -1) + { +#ifdef IGL_LINPROG_VERBOSE + cerr<<"linprog: warning! degenerate vertex"<().maxCoeff(&q); + } + VectorXd d = D*A.col(N(q)); + VectorXi I; + igl::find((d.array()>tol).eval(),I); + if(I.size() == 0) + { +#ifdef IGL_LINPROG_VERBOSE + cerr<<"linprog: warning! solution is unbounded"<=0) + { + igl::find((xbd.array()==r).eval(),J); + double Bp = igl::slice(B,igl::slice(I,J)).minCoeff(); + // idiotic way of finding index in B of Bp + // code down the line seems to assume p is a scalar though the matlab + // code could find a vector of matches) + (B.array()==Bp).cast().maxCoeff(&p); + } + // update x + xb -= r*d; + xb(p) = r; + // change in f + df = r*rmin; + } + // row vector + RowVectorXd v = D.row(p)/d(p); + yb += v.transpose() * (s(N(q)) - d.transpose()*igl::slice(s,B)); + d(p)-=1; + // update inverse basis matrix + D = D - d*v; + t = B(p); + B(p) = N(q); + if(t>(n+k-1)) + { + // remove qth entry from N + VectorXi old_N = N; + N.resize(N.size()-1); + N.head(q) = old_N.head(q); + N.head(q) = old_N.head(q); + N.tail(old_N.size()-q-1) = old_N.tail(old_N.size()-q-1); + }else + { + N(q) = t; + } + } + // iterative refinement + xb = (xb+D*(b-igl::slice(A,B,2)*xb)).eval(); + // must be due to rounding + VectorXi I; + igl::find((xb.array()<0).eval(),I); + if(I.size()>0) + { + // so correct + VectorXd Z = VectorXd::Zero(I.size(),1); + igl::slice_into(Z,I,xb); + } + // B, xb,n,m,res=A(:,B)*xb-b + if(phase == 2 || it<0) + { + break; + } + if(xb.transpose()*igl::slice(s,B) > tol) + { + it = -it; +#ifdef IGL_LINPROG_VERBOSE + cerr<<"linprog: warning, no feasible solution"<double + { + return (x<0?-1:(x>0?1:0)); + }; + AS.row(i) *= sign(b(i)); + } + MatrixXd In = MatrixXd::Identity(n,n); + MatrixXd P(n+m,2*n+m); + P<< In, -In, MatrixXd::Zero(n,m), + MatrixXd::Zero(m,2*n), Im; + MatrixXd ASP = AS*P; + MatrixXd BSP(0,2*n+m); + if(p>0) + { + MatrixXd BS(p,2*n); + BS< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LINPROG_H +#define IGL_LINPROG_H +#include "igl_inline.h" +#include +namespace igl +{ + // Solve a linear program given in "standard form" + // + // min f'x + // s.t. A( 1:k,:) x <= b(1:k) + // A(k+1:end,:) x = b(k+1:end) + // ** x >= 0 ** + // + // In contrast to other APIs the entries in b may be negative. + // + // Inputs: + // c #x list of linear coefficients + // A #A by #x matrix of linear constraint coefficients + // b #A list of linear constraint right-hand sides + // k number of inequality constraints as first rows of A,b + // Outputs: + // x #x solution vector + // + IGL_INLINE bool linprog( + const Eigen::VectorXd & c, + const Eigen::MatrixXd & A, + const Eigen::VectorXd & b, + const int k, + Eigen::VectorXd & f); + + // Wrapper in friendlier general form (no implicit bounds on x) + // + // min f'x + // s.t. A x <= b + // B x = c + // + // Inputs: + // f #x list of linear coefficients + // A #A by #x matrix of linear inequality constraint coefficients + // b #A list of linear constraint right-hand sides + // B #B by #x matrix of linear equality constraint coefficients + // c #B list of linear constraint right-hand sides + // Outputs: + // x #x solution vector + // + IGL_INLINE bool linprog( + const Eigen::VectorXd & f, + const Eigen::MatrixXd & A, + const Eigen::VectorXd & b, + const Eigen::MatrixXd & B, + const Eigen::VectorXd & c, + Eigen::VectorXd & x); +} + +#ifndef IGL_STATIC_LIBRARY +# include "linprog.cpp" +#endif +#endif diff --git a/src/igl/list_to_matrix.cpp b/src/igl/list_to_matrix.cpp new file mode 100644 index 000000000..a1ca1926b --- /dev/null +++ b/src/igl/list_to_matrix.cpp @@ -0,0 +1,175 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "list_to_matrix.h" + +#include +#include + +#include + +#include "max_size.h" +#include "min_size.h" + +template +IGL_INLINE bool igl::list_to_matrix(const std::vector > & V,Eigen::PlainObjectBase& M) +{ + // number of rows + int m = V.size(); + if(m == 0) + { + M.resize(0,0); + return true; + } + // number of columns + int n = igl::min_size(V); + if(n != igl::max_size(V)) + { + return false; + } + assert(n != -1); + // Resize output + M.resize(m,n); + + // Loop over rows + for(int i = 0;i +IGL_INLINE bool igl::list_to_matrix( + const std::vector > & V, + const int n, + const T & padding, + Eigen::PlainObjectBase& M) +{ + const int m = V.size(); + M.resize(m,n); + for(int i = 0;in) + { + return false; + } + int j = 0; + for(;j +IGL_INLINE bool igl::list_to_matrix(const std::vector & V,Eigen::PlainObjectBase& M) +{ + // number of rows + int m = V.size(); + if(m == 0) + { + //fprintf(stderr,"Error: list_to_matrix() list is empty()\n"); + //return false; + if(Derived::ColsAtCompileTime == 1) + { + M.resize(0,1); + }else if(Derived::RowsAtCompileTime == 1) + { + M.resize(1,0); + }else + { + M.resize(0,0); + } + return true; + } + // Resize output + if(Derived::RowsAtCompileTime == 1) + { + M.resize(1,m); + }else + { + M.resize(m,1); + } + + // Loop over rows + for(int i = 0;i >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); + +#ifdef WIN32 +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +template bool igl::list_to_matrix >(class std::vector > const &, class Eigen::PlainObjectBase > &); +template bool igl::list_to_matrix >(class std::vector > const &,class Eigen::PlainObjectBase > &); +template bool igl::list_to_matrix >(class std::vector > const &,class Eigen::PlainObjectBase > &); +template bool igl::list_to_matrix >(class std::vector > const &,class Eigen::PlainObjectBase > &); +template bool igl::list_to_matrix >(std::vector > const&, Eigen::PlainObjectBase >&); +#endif +#endif diff --git a/src/igl/list_to_matrix.h b/src/igl/list_to_matrix.h new file mode 100644 index 000000000..1a5881dc1 --- /dev/null +++ b/src/igl/list_to_matrix.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LIST_TO_MATRIX_H +#define IGL_LIST_TO_MATRIX_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Convert a list (std::vector) of row vectors of the same length to a matrix + // Template: + // T type that can be safely cast to type in Mat via '=' + // Mat Matrix type, must implement: + // .resize(m,n) + // .row(i) = Row + // Inputs: + // V a m-long list of vectors of size n + // Outputs: + // M an m by n matrix + // Returns true on success, false on errors + template + IGL_INLINE bool list_to_matrix( + const std::vector > & V, + Eigen::PlainObjectBase& M); + // Convert a list of row vectors of `n` or less to a matrix and pad on + // the right with `padding`: + // + // Inputs: + // V a m-long list of vectors of size <=n + // n number of columns + // padding value to fill in from right for short rows + // Outputs: + // M an m by n matrix + template + IGL_INLINE bool list_to_matrix( + const std::vector > & V, + const int n, + const T & padding, + Eigen::PlainObjectBase& M); + // Vector wrapper + template + IGL_INLINE bool list_to_matrix(const std::vector & V,Eigen::PlainObjectBase& M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "list_to_matrix.cpp" +#endif + +#endif diff --git a/src/igl/local_basis.cpp b/src/igl/local_basis.cpp new file mode 100644 index 000000000..599eca739 --- /dev/null +++ b/src/igl/local_basis.cpp @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "local_basis.h" + +#include +#include +#include + +#include +#include + + +template +IGL_INLINE void igl::local_basis( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& B1, + Eigen::PlainObjectBase& B2, + Eigen::PlainObjectBase& B3 + ) +{ + using namespace Eigen; + using namespace std; + B1.resize(F.rows(),3); + B2.resize(F.rows(),3); + B3.resize(F.rows(),3); + + for (unsigned i=0;i v1 = (V.row(F(i,1)) - V.row(F(i,0))).normalized(); + Eigen::Matrix t = V.row(F(i,2)) - V.row(F(i,0)); + Eigen::Matrix v3 = v1.cross(t).normalized(); + Eigen::Matrix v2 = v1.cross(v3).normalized(); + + B1.row(i) = v1; + B2.row(i) = -v2; + B3.row(i) = v3; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::local_basis, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::local_basis, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/local_basis.h b/src/igl/local_basis.h new file mode 100644 index 000000000..392796c34 --- /dev/null +++ b/src/igl/local_basis.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LOCALBASIS_H +#define IGL_LOCALBASIS_H + +#include "igl_inline.h" +#include +#include +#include + +namespace igl +{ + // Compute a local orthogonal reference system for each triangle in the given mesh + // Templates: + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // DerivedF derived from face indices matrix type: i.e. MatrixXi + // Inputs: + // V eigen matrix #V by 3 + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // B1 eigen matrix #F by 3, each vector is tangent to the triangle + // B2 eigen matrix #F by 3, each vector is tangent to the triangle and perpendicular to B1 + // B3 eigen matrix #F by 3, normal of the triangle + // + // See also: adjacency_matrix + template + IGL_INLINE void local_basis( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& B1, + Eigen::PlainObjectBase& B2, + Eigen::PlainObjectBase& B3 + ); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "local_basis.cpp" +#endif + +#endif diff --git a/src/igl/look_at.cpp b/src/igl/look_at.cpp new file mode 100644 index 000000000..c55c9b2ec --- /dev/null +++ b/src/igl/look_at.cpp @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "look_at.h" + +template < + typename Derivedeye, + typename Derivedcenter, + typename Derivedup, + typename DerivedR> +IGL_INLINE void igl::look_at( + const Eigen::PlainObjectBase & eye, + const Eigen::PlainObjectBase & center, + const Eigen::PlainObjectBase & up, + Eigen::PlainObjectBase & R) +{ + typedef Eigen::Matrix Vector3S; + Vector3S f = (center - eye).normalized(); + Vector3S s = f.cross(up).normalized(); + Vector3S u = s.cross(f); + R = Eigen::Matrix::Identity(); + R(0,0) = s(0); + R(0,1) = s(1); + R(0,2) = s(2); + R(1,0) = u(0); + R(1,1) = u(1); + R(1,2) = u(2); + R(2,0) =-f(0); + R(2,1) =-f(1); + R(2,2) =-f(2); + R(0,3) =-s.transpose() * eye; + R(1,3) =-u.transpose() * eye; + R(2,3) = f.transpose() * eye; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::look_at, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/look_at.h b/src/igl/look_at.h new file mode 100644 index 000000000..883cbf94e --- /dev/null +++ b/src/igl/look_at.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LOOK_AT_H +#define IGL_LOOK_AT_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Implementation of the deprecated gluLookAt function. + // + // Inputs: + // eye 3-vector of eye position + // center 3-vector of center reference point + // up 3-vector of up vector + // Outputs: + // R 4x4 rotation matrix + // + template < + typename Derivedeye, + typename Derivedcenter, + typename Derivedup, + typename DerivedR > + IGL_INLINE void look_at( + const Eigen::PlainObjectBase & eye, + const Eigen::PlainObjectBase & center, + const Eigen::PlainObjectBase & up, + Eigen::PlainObjectBase & R); +} + +#ifndef IGL_STATIC_LIBRARY +# include "look_at.cpp" +#endif + +#endif + diff --git a/src/igl/loop.cpp b/src/igl/loop.cpp new file mode 100644 index 000000000..b4233ca67 --- /dev/null +++ b/src/igl/loop.cpp @@ -0,0 +1,173 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "loop.h" + +#include +#include +#include + +#include + +template < + typename DerivedF, + typename SType, + typename DerivedNF> +IGL_INLINE void igl::loop( + const int n_verts, + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix& S, + Eigen::PlainObjectBase & NF) +{ + typedef Eigen::SparseMatrix SparseMat; + typedef Eigen::Triplet Triplet_t; + + //Ref. https://graphics.stanford.edu/~mdfisher/subdivision.html + //Heavily borrowing from igl::upsample + + DerivedF FF, FFi; + triangle_triangle_adjacency(F, FF, FFi); + std::vector> adjacencyList; + adjacency_list(F, adjacencyList, true); + + //Compute the number and positions of the vertices to insert (on edges) + Eigen::MatrixXi NI = Eigen::MatrixXi::Constant(FF.rows(), FF.cols(), -1); + Eigen::MatrixXi NIdoubles = Eigen::MatrixXi::Zero(FF.rows(), FF.cols()); + Eigen::VectorXi vertIsOnBdry = Eigen::VectorXi::Zero(n_verts); + int counter = 0; + for(int i=0; i tripletList; + for(int i=0; i& localAdjList = adjacencyList[i]; + if(vertIsOnBdry(i)==1) + { + //Boundary vertex + tripletList.emplace_back(i, localAdjList.front(), 1./8.); + tripletList.emplace_back(i, localAdjList.back(), 1./8.); + tripletList.emplace_back(i, i, 3./4.); + } else + { + const int n = localAdjList.size(); + const SType dn = n; + SType beta; + if(n==3) + { + beta = 3./16.; + } else + { + beta = 3./8./dn; + } + for(int j=0; j +IGL_INLINE void igl::loop( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& NV, + Eigen::PlainObjectBase& NF, + const int number_of_subdivs) +{ + NV = V; + NF = F; + for(int i=0; i S; + loop(NV.rows(), tempF, S, NF); + // This .eval is super important + NV = (S*NV).eval(); + } +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::loop, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, int); +#endif diff --git a/src/igl/loop.h b/src/igl/loop.h new file mode 100644 index 000000000..bc1616891 --- /dev/null +++ b/src/igl/loop.h @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Oded Stein +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_LOOP_H +#define IGL_LOOP_H + +#include +#include +#include + +namespace igl +{ + // LOOP Given the triangle mesh [V, F], where n_verts = V.rows(), computes + // newV and a sparse matrix S s.t. [newV, newF] is the subdivided mesh where + // newV = S*V. + // + // Inputs: + // n_verts an integer (number of mesh vertices) + // F an m by 3 matrix of integers of triangle faces + // Outputs: + // S a sparse matrix (will become the subdivision matrix) + // newF a matrix containing the new faces + template < + typename DerivedF, + typename SType, + typename DerivedNF> + IGL_INLINE void loop( + const int n_verts, + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix& S, + Eigen::PlainObjectBase & NF); + // LOOP Given the triangle mesh [V, F], computes number_of_subdivs steps of loop subdivision and outputs the new mesh [newV, newF] + // + // Inputs: + // V an n by 3 matrix of vertices + // F an m by 3 matrix of integers of triangle faces + // number_of_subdivs an integer that specifies how many subdivision steps to do + // Outputs: + // NV a matrix containing the new vertices + // NF a matrix containing the new faces + template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF> + IGL_INLINE void loop( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& NV, + Eigen::PlainObjectBase& NF, + const int number_of_subdivs = 1); +} + +#ifndef IGL_STATIC_LIBRARY +# include "loop.cpp" +#endif + +#endif diff --git a/src/igl/lscm.cpp b/src/igl/lscm.cpp new file mode 100644 index 000000000..6932f94d4 --- /dev/null +++ b/src/igl/lscm.cpp @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "lscm.h" + +#include "vector_area_matrix.h" +#include "cotmatrix.h" +#include "repdiag.h" +#include "min_quad_with_fixed.h" +#include + +IGL_INLINE bool igl::lscm( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc, + Eigen::MatrixXd & V_uv) +{ + using namespace Eigen; + using namespace std; + + // Assemble the area matrix (note that A is #Vx2 by #Vx2) + SparseMatrix A; + igl::vector_area_matrix(F,A); + + // Assemble the cotan laplacian matrix + SparseMatrix L; + igl::cotmatrix(V,F,L); + + SparseMatrix L_flat; + repdiag(L,2,L_flat); + + VectorXi b_flat(b.size()*bc.cols(),1); + VectorXd bc_flat(bc.size(),1); + for(int c = 0;c Q = -L_flat + 2.*A; + const VectorXd B_flat = VectorXd::Zero(V.rows()*2); + igl::min_quad_with_fixed_data data; + if(!igl::min_quad_with_fixed_precompute(Q,b_flat,SparseMatrix(),true,data)) + { + return false; + } + + MatrixXd W_flat; + if(!min_quad_with_fixed_solve(data,B_flat,bc_flat,VectorXd(),W_flat)) + { + return false; + } + + + assert(W_flat.rows() == V.rows()*2); + V_uv.resize(V.rows(),2); + for (unsigned i=0;i +// 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_LSCM_H +#define IGL_LSCM_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Compute a Least-squares conformal map parametrization (equivalently + // derived in "Intrinsic Parameterizations of Surface Meshes" [Desbrun et al. + // 2002] and "Least Squares Conformal Maps for Automatic Texture Atlas + // Generation" [Lévy et al. 2002]), though this implementation follows the + // derivation in: "Spectral Conformal Parameterization" [Mullen et al. 2008] + // (note, this does **not** implement the Eigen-decomposition based method in + // [Mullen et al. 2008], which is not equivalent). Input should be a manifold + // mesh (also no unreferenced vertices) and "boundary" (fixed vertices) `b` + // should contain at least two vertices per connected component. + // + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh faces (must be triangles) + // b #b boundary indices into V + // bc #b by 3 list of boundary values + // Outputs: + // UV #V by 2 list of 2D mesh vertex positions in UV space + // Returns true only on solver success. + // + IGL_INLINE bool lscm( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::VectorXi& b, + const Eigen::MatrixXd& bc, + Eigen::MatrixXd& V_uv); +} + +#ifndef IGL_STATIC_LIBRARY +# include "lscm.cpp" +#endif + +#endif diff --git a/src/igl/map_vertices_to_circle.cpp b/src/igl/map_vertices_to_circle.cpp new file mode 100755 index 000000000..233613936 --- /dev/null +++ b/src/igl/map_vertices_to_circle.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "map_vertices_to_circle.h" +#include "PI.h" + +IGL_INLINE void igl::map_vertices_to_circle( + const Eigen::MatrixXd& V, + const Eigen::VectorXi& bnd, + Eigen::MatrixXd& UV) +{ + // Get sorted list of boundary vertices + std::vector interior,map_ij; + map_ij.resize(V.rows()); + + std::vector isOnBnd(V.rows(),false); + for (int i = 0; i < bnd.size(); i++) + { + isOnBnd[bnd[i]] = true; + map_ij[bnd[i]] = i; + } + + for (int i = 0; i < (int)isOnBnd.size(); i++) + { + if (!isOnBnd[i]) + { + map_ij[i] = interior.size(); + interior.push_back(i); + } + } + + // Map boundary to unit circle + std::vector len(bnd.size()); + len[0] = 0.; + + for (int i = 1; i < bnd.size(); i++) + { + len[i] = len[i-1] + (V.row(bnd[i-1]) - V.row(bnd[i])).norm(); + } + double total_len = len[len.size()-1] + (V.row(bnd[0]) - V.row(bnd[bnd.size()-1])).norm(); + + UV.resize(bnd.size(),2); + for (int i = 0; i < bnd.size(); i++) + { + double frac = len[i] * 2. * igl::PI / total_len; + UV.row(map_ij[bnd[i]]) << cos(frac), sin(frac); + } + +} diff --git a/src/igl/map_vertices_to_circle.h b/src/igl/map_vertices_to_circle.h new file mode 100755 index 000000000..54743717f --- /dev/null +++ b/src/igl/map_vertices_to_circle.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAP_VERTICES_TO_CIRCLE_H +#define IGL_MAP_VERTICES_TO_CIRCLE_H +#include "igl_inline.h" +#include "PI.h" + +#include +#include + +namespace igl +{ + + // Map the vertices whose indices are in a given boundary loop (bnd) on the + // unit circle with spacing proportional to the original boundary edge + // lengths. + // + // Inputs: + // V #V by dim list of mesh vertex positions + // b #W list of vertex ids + // Outputs: + // UV #W by 2 list of 2D position on the unit circle for the vertices in b + IGL_INLINE void map_vertices_to_circle( + const Eigen::MatrixXd& V, + const Eigen::VectorXi& bnd, + Eigen::MatrixXd& UV); +} + +#ifndef IGL_STATIC_LIBRARY +# include "map_vertices_to_circle.cpp" +#endif + +#endif diff --git a/src/igl/massmatrix.cpp b/src/igl/massmatrix.cpp new file mode 100644 index 000000000..b51e11061 --- /dev/null +++ b/src/igl/massmatrix.cpp @@ -0,0 +1,166 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "massmatrix.h" +#include "normalize_row_sums.h" +#include "sparse.h" +#include "doublearea.h" +#include "repmat.h" +#include +#include + +template +IGL_INLINE void igl::massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const MassMatrixType type, + Eigen::SparseMatrix& M) +{ + using namespace Eigen; + using namespace std; + + const int n = V.rows(); + const int m = F.rows(); + const int simplex_size = F.cols(); + + MassMatrixType eff_type = type; + // Use voronoi of for triangles by default, otherwise barycentric + if(type == MASSMATRIX_TYPE_DEFAULT) + { + eff_type = (simplex_size == 3?MASSMATRIX_TYPE_VORONOI:MASSMATRIX_TYPE_BARYCENTRIC); + } + + // Not yet supported + assert(type!=MASSMATRIX_TYPE_FULL); + + Matrix MI; + Matrix MJ; + Matrix MV; + if(simplex_size == 3) + { + // Triangles + // edge lengths numbered same as opposite vertices + Matrix l(m,3); + // loop over faces + for(int i = 0;i dblA; + doublearea(l,0.,dblA); + + switch(eff_type) + { + case MASSMATRIX_TYPE_BARYCENTRIC: + // diagonal entries for each face corner + MI.resize(m*3,1); MJ.resize(m*3,1); MV.resize(m*3,1); + MI.block(0*m,0,m,1) = F.col(0); + MI.block(1*m,0,m,1) = F.col(1); + MI.block(2*m,0,m,1) = F.col(2); + MJ = MI; + repmat(dblA,3,1,MV); + MV.array() /= 6.0; + break; + case MASSMATRIX_TYPE_VORONOI: + { + // diagonal entries for each face corner + // http://www.alecjacobson.com/weblog/?p=874 + MI.resize(m*3,1); MJ.resize(m*3,1); MV.resize(m*3,1); + MI.block(0*m,0,m,1) = F.col(0); + MI.block(1*m,0,m,1) = F.col(1); + MI.block(2*m,0,m,1) = F.col(2); + MJ = MI; + + // Holy shit this needs to be cleaned up and optimized + Matrix cosines(m,3); + cosines.col(0) = + (l.col(2).array().pow(2)+l.col(1).array().pow(2)-l.col(0).array().pow(2))/(l.col(1).array()*l.col(2).array()*2.0); + cosines.col(1) = + (l.col(0).array().pow(2)+l.col(2).array().pow(2)-l.col(1).array().pow(2))/(l.col(2).array()*l.col(0).array()*2.0); + cosines.col(2) = + (l.col(1).array().pow(2)+l.col(0).array().pow(2)-l.col(2).array().pow(2))/(l.col(0).array()*l.col(1).array()*2.0); + Matrix barycentric = cosines.array() * l.array(); + normalize_row_sums(barycentric,barycentric); + Matrix partial = barycentric; + partial.col(0).array() *= dblA.array() * 0.5; + partial.col(1).array() *= dblA.array() * 0.5; + partial.col(2).array() *= dblA.array() * 0.5; + Matrix quads(partial.rows(),partial.cols()); + quads.col(0) = (partial.col(1)+partial.col(2))*0.5; + quads.col(1) = (partial.col(2)+partial.col(0))*0.5; + quads.col(2) = (partial.col(0)+partial.col(1))*0.5; + + quads.col(0) = (cosines.col(0).array()<0).select( 0.25*dblA,quads.col(0)); + quads.col(1) = (cosines.col(0).array()<0).select(0.125*dblA,quads.col(1)); + quads.col(2) = (cosines.col(0).array()<0).select(0.125*dblA,quads.col(2)); + + quads.col(0) = (cosines.col(1).array()<0).select(0.125*dblA,quads.col(0)); + quads.col(1) = (cosines.col(1).array()<0).select(0.25*dblA,quads.col(1)); + quads.col(2) = (cosines.col(1).array()<0).select(0.125*dblA,quads.col(2)); + + quads.col(0) = (cosines.col(2).array()<0).select(0.125*dblA,quads.col(0)); + quads.col(1) = (cosines.col(2).array()<0).select(0.125*dblA,quads.col(1)); + quads.col(2) = (cosines.col(2).array()<0).select( 0.25*dblA,quads.col(2)); + + MV.block(0*m,0,m,1) = quads.col(0); + MV.block(1*m,0,m,1) = quads.col(1); + MV.block(2*m,0,m,1) = quads.col(2); + + break; + } + case MASSMATRIX_TYPE_FULL: + assert(false && "Implementation incomplete"); + break; + default: + assert(false && "Unknown Mass matrix eff_type"); + } + + }else if(simplex_size == 4) + { + assert(V.cols() == 3); + assert(eff_type == MASSMATRIX_TYPE_BARYCENTRIC); + MI.resize(m*4,1); MJ.resize(m*4,1); MV.resize(m*4,1); + MI.block(0*m,0,m,1) = F.col(0); + MI.block(1*m,0,m,1) = F.col(1); + MI.block(2*m,0,m,1) = F.col(2); + MI.block(3*m,0,m,1) = F.col(3); + MJ = MI; + // loop over tets + for(int i = 0;i v0m3,v1m3,v2m3; + v0m3.head(V.cols()) = V.row(F(i,0)) - V.row(F(i,3)); + v1m3.head(V.cols()) = V.row(F(i,1)) - V.row(F(i,3)); + v2m3.head(V.cols()) = V.row(F(i,2)) - V.row(F(i,3)); + Scalar v = fabs(v0m3.dot(v1m3.cross(v2m3)))/6.0; + MV(i+0*m) = v/4.0; + MV(i+1*m) = v/4.0; + MV(i+2*m) = v/4.0; + MV(i+3*m) = v/4.0; + } + }else + { + // Unsupported simplex size + assert(false && "Unsupported simplex size"); + } + sparse(MI,MJ,MV,n,n,M); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::massmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MassMatrixType, Eigen::SparseMatrix&); +// generated by autoexplicit.sh +template void igl::massmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MassMatrixType, Eigen::SparseMatrix&); +// generated by autoexplicit.sh +template void igl::massmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MassMatrixType, Eigen::SparseMatrix&); +template void igl::massmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MassMatrixType, Eigen::SparseMatrix&); +template void igl::massmatrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::MassMatrixType, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/massmatrix.h b/src/igl/massmatrix.h new file mode 100644 index 000000000..69d8dc960 --- /dev/null +++ b/src/igl/massmatrix.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MASSMATRIX_TYPE_H +#define IGL_MASSMATRIX_TYPE_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + + enum MassMatrixType + { + MASSMATRIX_TYPE_BARYCENTRIC = 0, + MASSMATRIX_TYPE_VORONOI = 1, + MASSMATRIX_TYPE_FULL = 2, + MASSMATRIX_TYPE_DEFAULT = 3, + NUM_MASSMATRIX_TYPE = 4 + }; + + // Constructs the mass (area) matrix for a given mesh (V,F). + // + // Templates: + // DerivedV derived type of eigen matrix for V (e.g. derived from + // MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. derived from + // MatrixXi) + // Scalar scalar type for eigen sparse matrix (e.g. double) + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by simplex_size list of mesh faces (must be triangles) + // type one of the following ints: + // MASSMATRIX_TYPE_BARYCENTRIC barycentric + // MASSMATRIX_TYPE_VORONOI voronoi-hybrid {default} + // MASSMATRIX_TYPE_FULL full {not implemented} + // Outputs: + // M #V by #V mass matrix + // + // See also: adjacency_matrix + // + template + IGL_INLINE void massmatrix( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const MassMatrixType type, + Eigen::SparseMatrix& M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "massmatrix.cpp" +#endif + +#endif + diff --git a/src/igl/mat_max.cpp b/src/igl/mat_max.cpp new file mode 100644 index 000000000..c5da97569 --- /dev/null +++ b/src/igl/mat_max.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mat_max.h" + +template +IGL_INLINE void igl::mat_max( + const Eigen::DenseBase & X, + const int dim, + Eigen::PlainObjectBase & Y, + Eigen::PlainObjectBase & I) +{ + assert(dim==1||dim==2); + + // output size + int n = (dim==1?X.cols():X.rows()); + // resize output + Y.resize(n); + I.resize(n); + + // loop over dimension opposite of dim + for(int j = 0;j, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/mat_max.h b/src/igl/mat_max.h new file mode 100644 index 000000000..ad3ec6e61 --- /dev/null +++ b/src/igl/mat_max.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAT_MAX_H +#define IGL_MAT_MAX_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Ideally this becomes a super overloaded function supporting everything + // that matlab's max supports + + // Max function for matrices to act like matlab's max function. Specifically + // like [Y,I] = max(X,[],dim); + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // X m by n matrix + // dim dimension along which to take max + // Outputs: + // Y n-long vector (if dim == 1) + // or + // Y m-long vector (if dim == 2) + // I vector the same size as Y containing the indices along dim of maximum + // entries + template + IGL_INLINE void mat_max( + const Eigen::DenseBase & X, + const int dim, + Eigen::PlainObjectBase & Y, + Eigen::PlainObjectBase & I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "mat_max.cpp" +#endif + +#endif diff --git a/src/igl/mat_min.cpp b/src/igl/mat_min.cpp new file mode 100644 index 000000000..ad50c7014 --- /dev/null +++ b/src/igl/mat_min.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mat_min.h" + +template +IGL_INLINE void igl::mat_min( + const Eigen::DenseBase & X, + const int dim, + Eigen::PlainObjectBase & Y, + Eigen::PlainObjectBase & I) +{ + assert(dim==1||dim==2); + + // output size + int n = (dim==1?X.cols():X.rows()); + // resize output + Y.resize(n); + I.resize(n); + + // loop over dimension opposite of dim + for(int j = 0;j +//IGL_INLINE Eigen::Matrix igl::mat_min( +// const Eigen::Matrix & X, +// const int dim) +//{ +// Eigen::Matrix Y; +// Eigen::Matrix I; +// mat_min(X,dim,Y,I); +// return Y; +//} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::mat_min, Eigen::Array, Eigen::Matrix >(Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::mat_min, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/mat_min.h b/src/igl/mat_min.h new file mode 100644 index 000000000..3673aeded --- /dev/null +++ b/src/igl/mat_min.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAT_MIN_H +#define IGL_MAT_MIN_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Ideally this becomes a super overloaded function supporting everything + // that matlab's min supports + + // Min function for matrices to act like matlab's min function. Specifically + // like [Y,I] = min(X,[],dim); + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // X m by n matrix + // dim dimension along which to take min + // Outputs: + // Y n-long sparse vector (if dim == 1) + // or + // Y m-long sparse vector (if dim == 2) + // I vector the same size as Y containing the indices along dim of minimum + // entries + // + // See also: mat_max + template + IGL_INLINE void mat_min( + const Eigen::DenseBase & X, + const int dim, + Eigen::PlainObjectBase & Y, + Eigen::PlainObjectBase & I); + // Use Y = X.colwise().minCoeff() instead + //// In-line wrapper + //template + //IGL_INLINE Eigen::Matrix mat_min( + // const Eigen::Matrix & X, + // const int dim); +} + +#ifndef IGL_STATIC_LIBRARY +# include "mat_min.cpp" +#endif + +#endif diff --git a/src/igl/mat_to_quat.cpp b/src/igl/mat_to_quat.cpp new file mode 100644 index 000000000..5c8e04b1f --- /dev/null +++ b/src/igl/mat_to_quat.cpp @@ -0,0 +1,141 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mat_to_quat.h" +#include + +// This could be replaced by something fast +template +static inline Q_type ReciprocalSqrt( const Q_type x ) +{ + return 1.0/sqrt(x); +} + +//// Converts row major order matrix to quat +//// http://software.intel.com/sites/default/files/m/d/4/1/d/8/293748.pdf +//template +//IGL_INLINE void igl::mat4_to_quat(const Q_type * m, Q_type * q) +//{ +// Q_type t = + m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] + 1.0f; +// Q_type s = ReciprocalSqrt( t ) * 0.5f; +// q[3] = s * t; +// q[2] = ( m[0 * 4 + 1] - m[1 * 4 + 0] ) * s; +// q[1] = ( m[2 * 4 + 0] - m[0 * 4 + 2] ) * s; +// q[0] = ( m[1 * 4 + 2] - m[2 * 4 + 1] ) * s; +//} + +// https://bmgame.googlecode.com/svn/idlib/math/Simd_AltiVec.cpp +template +IGL_INLINE void igl::mat4_to_quat(const Q_type * mat, Q_type * q) +{ + Q_type trace; + Q_type s; + Q_type t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = ReciprocalSqrt( t ) * 0.5f; + + q[3] = s * t; + q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + s = ReciprocalSqrt( t ) * 0.5f; + + q[i] = s * t; + q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + //// Unused translation + //jq.t[0] = mat[0 * 4 + 3]; + //jq.t[1] = mat[1 * 4 + 3]; + //jq.t[2] = mat[2 * 4 + 3]; +} + +template +IGL_INLINE void igl::mat3_to_quat(const Q_type * mat, Q_type * q) +{ + Q_type trace; + Q_type s; + Q_type t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + trace = mat[0 * 3 + 0] + mat[1 * 3 + 1] + mat[2 * 3 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = ReciprocalSqrt( t ) * 0.5f; + + q[3] = s * t; + q[0] = ( mat[1 * 3 + 2] - mat[2 * 3 + 1] ) * s; + q[1] = ( mat[2 * 3 + 0] - mat[0 * 3 + 2] ) * s; + q[2] = ( mat[0 * 3 + 1] - mat[1 * 3 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 3 + 1] > mat[0 * 3 + 0] ) { + i = 1; + } + if ( mat[2 * 3 + 2] > mat[i * 3 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 3 + i] - ( mat[j * 3 + j] + mat[k * 3 + k] ) ) + 1.0f; + s = ReciprocalSqrt( t ) * 0.5f; + + q[i] = s * t; + q[3] = ( mat[j * 3 + k] - mat[k * 3 + j] ) * s; + q[j] = ( mat[i * 3 + j] + mat[j * 3 + i] ) * s; + q[k] = ( mat[i * 3 + k] + mat[k * 3 + i] ) * s; + } + + //// Unused translation + //jq.t[0] = mat[0 * 4 + 3]; + //jq.t[1] = mat[1 * 4 + 3]; + //jq.t[2] = mat[2 * 4 + 3]; +} + + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::mat4_to_quat(double const*, double*); +template void igl::mat4_to_quat(float const*, float*); +template void igl::mat3_to_quat(double const*, double*); +#endif diff --git a/src/igl/mat_to_quat.h b/src/igl/mat_to_quat.h new file mode 100644 index 000000000..acea683f7 --- /dev/null +++ b/src/igl/mat_to_quat.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAT_TO_QUAT_H +#define IGL_MAT_TO_QUAT_H +#include "igl_inline.h" +namespace igl +{ + // Convert a OpenGL (rotation) matrix to a quaternion + // + // Input: + // m 16-element opengl rotation matrix + // Output: + // q 4-element quaternion (not normalized) + template + IGL_INLINE void mat4_to_quat(const Q_type * m, Q_type * q); + // Input: + // m 9-element opengl rotation matrix + template + IGL_INLINE void mat3_to_quat(const Q_type * m, Q_type * q); +} + +#ifndef IGL_STATIC_LIBRARY +# include "mat_to_quat.cpp" +#endif + +#endif + diff --git a/src/igl/material_colors.h b/src/igl/material_colors.h new file mode 100644 index 000000000..1927fa42d --- /dev/null +++ b/src/igl/material_colors.h @@ -0,0 +1,57 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATERIAL_COLORS_H +#define IGL_MATERIAL_COLORS_H +#include +// Define constant material colors for use with opengl glMaterialfv +// Most of these colors come from IGL publications +namespace igl +{ + // Gold/Silver used in BBW/MONO/STBS/FAST + const float GOLD_AMBIENT[4] = { 51.0/255.0, 43.0/255.0,33.3/255.0,1.0f }; + const float GOLD_DIFFUSE[4] = { 255.0/255.0,228.0/255.0,58.0/255.0,1.0f }; + const float GOLD_SPECULAR[4] = { 255.0/255.0,235.0/255.0,80.0/255.0,1.0f }; + const float SILVER_AMBIENT[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + const float SILVER_DIFFUSE[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + const float SILVER_SPECULAR[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + // Blue/Cyan more similar to Jovan Popovic's blue than to Mario Botsch's blue + const float CYAN_AMBIENT[4] = { 59.0/255.0, 68.0/255.0,255.0/255.0,1.0f }; + const float CYAN_DIFFUSE[4] = { 94.0/255.0,185.0/255.0,238.0/255.0,1.0f }; + const float CYAN_SPECULAR[4] = { 163.0/255.0,221.0/255.0,255.0/255.0,1.0f }; + const float DENIS_PURPLE_DIFFUSE[4] = { 80.0/255.0,64.0/255.0,255.0/255.0,1.0f }; + const float LADISLAV_ORANGE_DIFFUSE[4] = {1.0f, 125.0f / 255.0f, 19.0f / 255.0f, 0.0f}; + // FAST armadillos colors + const float FAST_GREEN_DIFFUSE[4] = { 113.0f/255.0f, 239.0f/255.0f, 46.0f/255.0f, 1.0f}; + const float FAST_RED_DIFFUSE[4] = { 255.0f/255.0f, 65.0f/255.0f, 46.0f/255.0f, 1.0f}; + const float FAST_BLUE_DIFFUSE[4] = { 106.0f/255.0f, 106.0f/255.0f, 255.0f/255.0f, 1.0f}; + const float FAST_GRAY_DIFFUSE[4] = { 150.0f/255.0f, 150.0f/255.0f, 150.0f/255.0f, 1.0f}; + // Basic colors + const float WHITE[4] = { 255.0/255.0,255.0/255.0,255.0/255.0,1.0f }; + const float BLACK[4] = { 0.0/255.0,0.0/255.0,0.0/255.0,1.0f }; + const float WHITE_AMBIENT[4] = { 255.0/255.0,255.0/255.0,255.0/255.0,1.0f }; + const float WHITE_DIFFUSE[4] = { 255.0/255.0,255.0/255.0,255.0/255.0,1.0f }; + const float WHITE_SPECULAR[4] = { 255.0/255.0,255.0/255.0,255.0/255.0,1.0f }; + const float BBW_POINT_COLOR[4] = {239./255.,213./255.,46./255.,255.0/255.0}; + const float BBW_LINE_COLOR[4] = {106./255.,106./255.,255./255.,255./255.}; + const float MIDNIGHT_BLUE_DIFFUSE[4] = { 21.0f/255.0f, 27.0f/255.0f, 84.0f/255.0f, 1.0f}; + // Winding number colors + const float EASTER_RED_DIFFUSE[4] = {0.603922,0.494118f,0.603922f,1.0f}; + const float WN_OPEN_BOUNDARY_COLOR[4] = {154./255.,0./255.,0./255.,1.0f}; + const float WN_NON_MANIFOLD_EDGE_COLOR[4] = {201./255., 51./255.,255./255.,1.0f}; + const Eigen::Vector4f + MAYA_GREEN(128./255.,242./255.,0./255.,1.), + MAYA_YELLOW(255./255.,247./255.,50./255.,1.), + MAYA_RED(234./255.,63./255.,52./255.,1.), + MAYA_BLUE(0./255.,73./255.,252./255.,1.), + MAYA_PURPLE(180./255.,73./255.,200./255.,1.), + MAYA_VIOLET(31./255.,15./255.,66./255.,1.), + MAYA_GREY(0.5,0.5,0.5,1.0), + MAYA_CYAN(131./255.,219./255.,252./255.,1.), + MAYA_SEA_GREEN(70./255.,252./255.,167./255.,1.); +} +#endif diff --git a/src/igl/matlab/MatlabWorkspace.h b/src/igl/matlab/MatlabWorkspace.h new file mode 100644 index 000000000..c8dcaf9ed --- /dev/null +++ b/src/igl/matlab/MatlabWorkspace.h @@ -0,0 +1,579 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_MATLAB_WORKSPACE_H +#define IGL_MATLAB_MATLAB_WORKSPACE_H + +#include +#include + +#include + +#include +#include + +namespace igl +{ + namespace matlab + { + // It would be really great to replicate this for a simple XML-based + // workspace. + // + // Class which contains data of a matlab workspace which can be written to a + // .mat file and loaded from matlab + // + // This depends on matlab at compile time (though it shouldn't necessarily + // have to) but it does not depend on running the matlab engine at run-time. + // + // Known bugs: Treats all matrices as doubles (this may actually be desired + // for some "index" matrices since matlab's sparse command takes doubles + // rather than int class matrices). It is of course not desired when dealing + // with logicals or uint's for images. + class MatlabWorkspace + { + private: + // KNOWN BUG: Why not use a map? Any reason to allow duplicate names? + // + // List of names + std::vector names; + // List of data pointers + std::vector data; + public: + MatlabWorkspace(); + ~MatlabWorkspace(); + // Clear names and data of variables in workspace + inline void clear(); + // Save current list of variables + // + // Inputs: + // path path to .mat file + // Returns true on success, false on failure + inline bool write(const std::string & path) const; + // Load list of variables from .mat file + // + // Inputs: + // path path to .mat file + // Returns true on success, false on failure + inline bool read(const std::string & path); + // Assign data to a variable name in the workspace + // + // Template: + // DerivedM eigen matrix (e.g. MatrixXd) + // Inputs: + // M data (usually a matrix) + // name variable name to save into work space + // Returns true on success, false on failure + // + // Known Bugs: Assumes Eigen is using column major ordering + template + inline MatlabWorkspace& save( + const Eigen::PlainObjectBase& M, + const std::string & name); + // Template: + // MT sparse matrix type (e.g. double) + template + inline MatlabWorkspace& save( + const Eigen::SparseMatrix& M, + const std::string & name); + // Templates: + // ScalarM scalar type, e.g. double + template + inline MatlabWorkspace& save( + const std::vector > & vM, + const std::string & name); + // Templates: + // ScalarV scalar type, e.g. double + template + inline MatlabWorkspace& save( + const std::vector & vV, + const std::string & name); + // NOTE: Eigen stores quaternions coefficients as (i,j,k,1), but most of + // our matlab code stores them as (1,i,j,k) This takes a quaternion and + // saves it as a (1,i,j,k) row vector + // + // Templates: + // Q quaternion type + template + inline MatlabWorkspace& save( + const Eigen::Quaternion & q, + const std::string & name); + inline MatlabWorkspace& save( + const double d, + const std::string & name); + // Same as save() but adds 1 to each element, useful for saving "index" + // matrices like lists of faces or elements + template + inline MatlabWorkspace& save_index( + const Eigen::DenseBase& M, + const std::string & name); + template + inline MatlabWorkspace& save_index( + const std::vector > & vM, + const std::string & name); + template + inline MatlabWorkspace& save_index( + const std::vector & vV, + const std::string & name); + // Find a certain matrix by name. + // + // KNOWN BUG: Outputs the first found (not necessarily unique lists). + // + // Template: + // DerivedM eigen matrix (e.g. MatrixXd) + // Inputs: + // name exact name of matrix as string + // Outputs: + // M matrix + // Returns true only if found. + template + inline bool find( + const std::string & name, + Eigen::PlainObjectBase& M); + template + inline bool find( + const std::string & name, + Eigen::SparseMatrix& M); + inline bool find( + const std::string & name, + double & d); + inline bool find( + const std::string & name, + int & v); + // Subtracts 1 from all entries + template + inline bool find_index( + const std::string & name, + Eigen::PlainObjectBase& M); + }; + } +} + +// Implementation + +// Be sure that this is not compiled into libigl.a +// http://stackoverflow.com/a/3318993/148668 + +// IGL +#include "igl/list_to_matrix.h" + +// MATLAB +#include "mat.h" + +// STL +#include +#include +#include + +inline igl::matlab::MatlabWorkspace::MatlabWorkspace(): + names(), + data() +{ +} + +inline igl::matlab::MatlabWorkspace::~MatlabWorkspace() +{ + // clean up data + clear(); +} + +inline void igl::matlab::MatlabWorkspace::clear() +{ + for_each(data.begin(),data.end(),&mxDestroyArray); + data.clear(); + names.clear(); +} + +inline bool igl::matlab::MatlabWorkspace::write(const std::string & path) const +{ + using namespace std; + MATFile * mat_file = matOpen(path.c_str(), "w"); + if(mat_file == NULL) + { + fprintf(stderr,"Error opening file %s\n",path.c_str()); + return false; + } + assert(names.size() == data.size()); + // loop over names and data + for(int i = 0;i < (int)names.size(); i++) + { + // Put variable as LOCAL variable + int status = matPutVariable(mat_file,names[i].c_str(), data[i]); + if(status != 0) + { + cerr<<"^MatlabWorkspace::save Error: matPutVariable ("< +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const Eigen::PlainObjectBase& M, + const std::string & name) +{ + using namespace std; + const int m = M.rows(); + const int n = M.cols(); + mxArray * mx_data = mxCreateDoubleMatrix(m,n,mxREAL); + data.push_back(mx_data); + names.push_back(name); + // Copy data immediately + // Use Eigen's map and cast to copy + Eigen::Map< Eigen::Matrix > + map(mxGetPr(mx_data),m,n); + map = M.template cast(); + return *this; +} + +// Treat everything as a double +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const Eigen::SparseMatrix& M, + const std::string & name) +{ + using namespace std; + const int m = M.rows(); + const int n = M.cols(); + // THIS WILL NOT WORK FOR ROW-MAJOR + assert(n==M.outerSize()); + const int nzmax = M.nonZeros(); + mxArray * mx_data = mxCreateSparse(m, n, nzmax, mxREAL); + data.push_back(mx_data); + names.push_back(name); + // Copy data immediately + double * pr = mxGetPr(mx_data); + mwIndex * ir = mxGetIr(mx_data); + mwIndex * jc = mxGetJc(mx_data); + + // Iterate over outside + int k = 0; + for(int j=0; j::InnerIterator it (M,j); it; ++it) + { + pr[k] = it.value(); + ir[k] = it.row(); + k++; + } + } + jc[M.outerSize()] = k; + + return *this; +} + +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const std::vector > & vM, + const std::string & name) +{ + Eigen::MatrixXd M; + list_to_matrix(vM,M); + return this->save(M,name); +} + +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const std::vector & vV, + const std::string & name) +{ + Eigen::MatrixXd V; + list_to_matrix(vV,V); + return this->save(V,name); +} + +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const Eigen::Quaternion & q, + const std::string & name) +{ + Eigen::Matrix qm; + qm(0,0) = q.w(); + qm(0,1) = q.x(); + qm(0,2) = q.y(); + qm(0,3) = q.z(); + return save(qm,name); +} + +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( + const double d, + const std::string & name) +{ + Eigen::VectorXd v(1); + v(0) = d; + return save(v,name); +} + +template +inline igl::matlab::MatlabWorkspace& + igl::matlab::MatlabWorkspace::save_index( + const Eigen::DenseBase& M, + const std::string & name) +{ + DerivedM Mp1 = M; + Mp1.array() += 1; + return this->save(Mp1,name); +} + +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save_index( + const std::vector > & vM, + const std::string & name) +{ + Eigen::MatrixXd M; + list_to_matrix(vM,M); + return this->save_index(M,name); +} + +template +inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save_index( + const std::vector & vV, + const std::string & name) +{ + Eigen::MatrixXd V; + list_to_matrix(vV,V); + return this->save_index(V,name); +} + +template +inline bool igl::matlab::MatlabWorkspace::find( + const std::string & name, + Eigen::PlainObjectBase& M) +{ + using namespace std; + const int i = std::find(names.begin(), names.end(), name)-names.begin(); + if(i>=(int)names.size()) + { + return false; + } + assert(i<=(int)data.size()); + mxArray * mx_data = data[i]; + assert(!mxIsSparse(mx_data)); + assert(mxGetNumberOfDimensions(mx_data) == 2); + //cout< > + (mxGetPr(mx_data),M.rows(),M.cols()).cast(); + return true; +} + +template +inline bool igl::matlab::MatlabWorkspace::find( + const std::string & name, + Eigen::SparseMatrix& M) +{ + using namespace std; + using namespace Eigen; + const int i = std::find(names.begin(), names.end(), name)-names.begin(); + if(i>=(int)names.size()) + { + return false; + } + assert(i<=(int)data.size()); + mxArray * mx_data = data[i]; + // Handle boring case where matrix is actually an empty dense matrix + if(mxGetNumberOfElements(mx_data) == 0) + { + M.resize(0,0); + return true; + } + assert(mxIsSparse(mx_data)); + assert(mxGetNumberOfDimensions(mx_data) == 2); + //cout< > MIJV; + const int nnz = mxGetNzmax(mx_data); + MIJV.reserve(nnz); + // Iterate over outside + int k = 0; + for(int j=0; j(ir[k],j,pr[k])); + k++; + } + } + M.resize(m,n); + M.setFromTriplets(MIJV.begin(),MIJV.end()); + + return true; +} + +inline bool igl::matlab::MatlabWorkspace::find( + const std::string & name, + int & v) +{ + using namespace std; + const int i = std::find(names.begin(), names.end(), name)-names.begin(); + if(i>=(int)names.size()) + { + return false; + } + assert(i<=(int)data.size()); + mxArray * mx_data = data[i]; + assert(!mxIsSparse(mx_data)); + assert(mxGetNumberOfDimensions(mx_data) == 2); + //cout<=(int)names.size()) + { + return false; + } + assert(i<=(int)data.size()); + mxArray * mx_data = data[i]; + assert(!mxIsSparse(mx_data)); + assert(mxGetNumberOfDimensions(mx_data) == 2); + //cout< +inline bool igl::matlab::MatlabWorkspace::find_index( + const std::string & name, + Eigen::PlainObjectBase& M) +{ + if(!find(name,M)) + { + return false; + } + M.array() -= 1; + return true; +} + + +//template +//bool igl::matlab::MatlabWorkspace::save(const Data & M, const std::string & name) +//{ +// using namespace std; +// // If I don't know the type then I can't save it +// cerr<<"^MatlabWorkspace::save Error: Unknown data type. "<< +// name<<" not saved."< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_MEX_STREAM_H +#define IGL_MATLAB_MEX_STREAM_H +#include +namespace igl +{ + namespace matlab + { + // http://stackoverflow.com/a/249008/148668 + + // Class to implement "cout" for mex files to print to the matlab terminal + // window. + // + // Insert at the beginning of mexFunction(): + // MexStream mout; + // std::streambuf *outbuf = std::cout.rdbuf(&mout); + // ... + // ALWAYS restore original buffer to avoid memory leak problems in matlab + // std::cout.rdbuf(outbuf); + // + class MexStream : public std::streambuf + { + public: + protected: + inline virtual std::streamsize xsputn(const char *s, std::streamsize n); + inline virtual int overflow(int c = EOF); + }; + } +} + +// Implementation +#include +inline std::streamsize igl::matlab::MexStream::xsputn( + const char *s, + std::streamsize n) +{ + mexPrintf("%.*s",n,s); + mexEvalString("drawnow;"); // to dump string. + return n; +} + +inline int igl::matlab::MexStream::overflow(int c) +{ + if (c != EOF) { + mexPrintf("%.1s",&c); + mexEvalString("drawnow;"); // to dump string. + } + return 1; +} +#endif diff --git a/src/igl/matlab/matlabinterface.cpp b/src/igl/matlab/matlabinterface.cpp new file mode 100644 index 000000000..6c03485de --- /dev/null +++ b/src/igl/matlab/matlabinterface.cpp @@ -0,0 +1,330 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include + +// Implementation + +// Init the MATLAB engine +// (no need to call it directly since it is automatically invoked by any other command) +IGL_INLINE void igl::matlab::mlinit(Engine** mlengine) +{ + *mlengine = engOpen("\0"); +} + +// Closes the MATLAB engine +IGL_INLINE void igl::matlab::mlclose(Engine** mlengine) +{ + engClose(*mlengine); + *mlengine = 0; +} + +// Send a matrix to MATLAB +IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::MatrixXd& M) +{ + if (*mlengine == 0) + mlinit(mlengine); + + mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL); + double *pM = mxGetPr(A); + + int c = 0; + for(int j=0; j& M) +{ + if (*mlengine == 0) + mlinit(mlengine); + + mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL); + double *pM = mxGetPr(A); + + int c = 0; + for(int j=0; j t; + + mxArray *ary = engGetVariable(*mlengine, name.c_str()); + if (ary == NULL) + { + m = 0; + n = 0; + M = Eigen::MatrixXd(0,0); + } + else + { + m = mxGetM(ary); + n = mxGetN(ary); + M = Eigen::MatrixXd(m,n); + + double *pM = mxGetPr(ary); + + int c = 0; + for(int j=0; j t; + + mxArray *ary = engGetVariable(*mlengine, name.c_str()); + if (ary == NULL) + { + m = 0; + n = 0; + M = Eigen::MatrixXf(0,0); + } + else + { + m = mxGetM(ary); + n = mxGetN(ary); + M = Eigen::MatrixXf(m,n); + + double *pM = mxGetPr(ary); + + int c = 0; + for(int j=0; j t; + + mxArray *ary = engGetVariable(*mlengine, name.c_str()); + if (ary == NULL) + { + m = 0; + n = 0; + M = Eigen::MatrixXi(0,0); + } + else + { + m = mxGetM(ary); + n = mxGetN(ary); + M = Eigen::MatrixXi(m,n); + + double *pM = mxGetPr(ary); + + int c = 0; + for(int j=0; j& M) +{ + if (*mlengine == 0) + mlinit(mlengine); + + unsigned long m = 0; + unsigned long n = 0; + std::vector t; + + mxArray *ary = engGetVariable(*mlengine, name.c_str()); + if (ary == NULL) + { + m = 0; + n = 0; + M = Eigen::Matrix(0,0); + } + else + { + m = mxGetM(ary); + n = mxGetN(ary); + M = Eigen::Matrix(m,n); + + double *pM = mxGetPr(ary); + + int c = 0; + for(int j=0; j' && buf[1] == '>' && buf[2] == ' ') + buf += 3; + if (buf[0] == '\n') ++buf; + + return std::string(buf); +} + +// Send a sparse matrix +IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::SparseMatrix& M) +{ + int count = 0; +// // Count non-zero +// for (unsigned k=0; k::InnerIterator it(M,k); it; ++it) +// if (it.value() != 0) +// ++count; + + Eigen::MatrixXd T(M.nonZeros(),3); + for (unsigned k=0; k<(unsigned)M.outerSize(); ++k) + { + for (Eigen::SparseMatrix::InnerIterator it(M,k); it; ++it) + { + T(count,0) = it.row(); + T(count,1) = it.col(); + T(count,2) = it.value(); + ++count; + } + } + + T.col(0) = T.col(0).array()+1; + T.col(1) = T.col(1).array()+1; + + mlsetmatrix(mlengine,"temp93765",T); + + std::string temp = name + " = sparse(temp93765(:,1),temp93765(:,2),temp93765(:,3)," + + std::to_string(M.rows()) + "," + + std::to_string(M.cols()) + ");"; + + mleval(mlengine,temp); + mleval(mlengine,"clear temp93765"); +} diff --git a/src/igl/matlab/matlabinterface.h b/src/igl/matlab/matlabinterface.h new file mode 100644 index 000000000..63d72c74e --- /dev/null +++ b/src/igl/matlab/matlabinterface.h @@ -0,0 +1,90 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_MATLAB_INTERFACE_H +#define IGL_MATLAB_MATLAB_INTERFACE_H +#include "../igl_inline.h" +// WARNING: These functions require matlab installed +// Additional header folder required: +// /Applications/MATLAB_R2011a.app/extern/include +// Additional binary lib to be linked with: +// /Applications/MATLAB_R2011a.app/bin/maci64/libeng.dylib +// /Applications/MATLAB_R2011a.app/bin/maci64/libmx.dylib + +// MAC ONLY: +// Add to the environment variables: +// DYLD_LIBRARY_PATH = /Applications/MATLAB_R2011a.app/bin/maci64/ +// PATH = /opt/local/bin:/opt/local/sbin:/Applications/MATLAB_R2011a.app/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/texbin:/usr/X11/bin + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include // Matlab engine header + +namespace igl +{ + namespace matlab + { + // Init the MATLAB engine + // (no need to call it directly since it is automatically invoked by any other command) + IGL_INLINE void mlinit(Engine** engine); + + // Closes the MATLAB engine + IGL_INLINE void mlclose(Engine** engine); + + // Send a matrix to MATLAB + IGL_INLINE void mlsetmatrix(Engine** engine, std::string name, const Eigen::MatrixXd& M); + + // Send a matrix to MATLAB + IGL_INLINE void mlsetmatrix(Engine** engine, std::string name, const Eigen::MatrixXf& M); + + // Send a matrix to MATLAB + IGL_INLINE void mlsetmatrix(Engine** engine, std::string name, const Eigen::MatrixXi& M); + + // Send a matrix to MATLAB + IGL_INLINE void mlsetmatrix(Engine** mlengine, std::string name, const Eigen::Matrix& M); + + // Receive a matrix from MATLAB + IGL_INLINE void mlgetmatrix(Engine** engine, std::string name, Eigen::MatrixXd& M); + + // Receive a matrix from MATLAB + IGL_INLINE void mlgetmatrix(Engine** engine, std::string name, Eigen::MatrixXf& M); + + // Receive a matrix from MATLAB + IGL_INLINE void mlgetmatrix(Engine** engine, std::string name, Eigen::MatrixXi& M); + + // Receive a matrix from MATLAB + IGL_INLINE void mlgetmatrix(Engine** mlengine, std::string name, Eigen::Matrix& M); + + // Send a single scalar to MATLAB + IGL_INLINE void mlsetscalar(Engine** engine, std::string name, double s); + + // Receive a single scalar from MATLAB + IGL_INLINE double mlgetscalar(Engine** engine, std::string name); + + // Execute arbitrary MATLAB code and return the MATLAB output + IGL_INLINE std::string mleval(Engine** engine, std::string code); + + // Send a sparse matrix to MATLAB + IGL_INLINE void mlsetmatrix(Engine** mlengine, std::string name, const Eigen::SparseMatrix& M); + + } +} + +// Be sure that this is not compiled into libigl.a +#ifndef IGL_STATIC_LIBRARY +# include "matlabinterface.cpp" +#endif + +#endif diff --git a/src/igl/matlab/mexErrMsgTxt.cpp b/src/igl/matlab/mexErrMsgTxt.cpp new file mode 100644 index 000000000..89dda1fa3 --- /dev/null +++ b/src/igl/matlab/mexErrMsgTxt.cpp @@ -0,0 +1,17 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mexErrMsgTxt.h" + +IGL_INLINE void igl::matlab::mexErrMsgTxt(bool assertion, const char * text) +{ + if(!assertion) + { + ::mexErrMsgTxt(text); + } +} + diff --git a/src/igl/matlab/mexErrMsgTxt.h b/src/igl/matlab/mexErrMsgTxt.h new file mode 100644 index 000000000..61b838e6e --- /dev/null +++ b/src/igl/matlab/mexErrMsgTxt.h @@ -0,0 +1,25 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_MEXERRMSGTXT_H +#define IGL_MATLAB_MEXERRMSGTXT_H +#include "../igl_inline.h" +// Overload mexErrMsgTxt to check an assertion then print text only if +// assertion fails +#include "mex.h" +namespace igl +{ + namespace matlab + { + // Wrapper for mexErrMsgTxt that only calls error if test fails + IGL_INLINE void mexErrMsgTxt(bool test, const char * message); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "mexErrMsgTxt.cpp" +#endif +#endif diff --git a/src/igl/matlab/parse_rhs.cpp b/src/igl/matlab/parse_rhs.cpp new file mode 100644 index 000000000..dafc8ba48 --- /dev/null +++ b/src/igl/matlab/parse_rhs.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "parse_rhs.h" +#include + +template +IGL_INLINE void igl::matlab::parse_rhs_double( + const mxArray *prhs[], + Eigen::PlainObjectBase & V) +{ + using namespace Eigen; + // Use Eigen's map and cast to copy + V = Map< Matrix > + (mxGetPr(prhs[0]),mxGetM(prhs[0]),mxGetN(prhs[0])) + .cast(); +} + +template +IGL_INLINE void igl::matlab::parse_rhs_index( + const mxArray *prhs[], + Eigen::PlainObjectBase & V) +{ + parse_rhs_double(prhs,V); + V.array() -= 1; +} + +template +IGL_INLINE void igl::matlab::parse_rhs( + const mxArray *prhs[], + Eigen::SparseMatrix & M) +{ + using namespace Eigen; + using namespace std; + const mxArray * mx_data = prhs[0]; + // Handle boring case where matrix is actually an empty dense matrix + if(mxGetNumberOfElements(mx_data) == 0) + { + M.resize(0,0); + return; + } + assert(mxIsSparse(mx_data)); + assert(mxGetNumberOfDimensions(mx_data) == 2); + //cout< > MIJV; + MIJV.reserve(mxGetNumberOfElements(mx_data)); + // Iterate over outside + int k = 0; + for(int j=0; j(ir[k],j,pr[k])); + k++; + } + } + M.resize(m,n); + M.setFromTriplets(MIJV.begin(),MIJV.end()); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::matlab::parse_rhs_index >(mxArray_tag const**, Eigen::PlainObjectBase >&); +template void igl::matlab::parse_rhs_index >(mxArray_tag const**, Eigen::PlainObjectBase >&); +template void igl::matlab::parse_rhs_double >(mxArray_tag const**, Eigen::PlainObjectBase >&); +template void igl::matlab::parse_rhs_index >(mxArray_tag const**, Eigen::PlainObjectBase >&); +template void igl::matlab::parse_rhs_double >(mxArray_tag const**, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/matlab/parse_rhs.h b/src/igl/matlab/parse_rhs.h new file mode 100644 index 000000000..3cabec734 --- /dev/null +++ b/src/igl/matlab/parse_rhs.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_PARSE_RHS_H +#define IGL_MATLAB_PARSE_RHS_H +#include +#include +#include +#include +namespace igl +{ + namespace matlab + { + // Reads in a matrix as a double + // + // Inputs: + // prhs points to rhs argument + // Outputs: + // V M by N matrix + template + IGL_INLINE void parse_rhs_double( + const mxArray *prhs[], + Eigen::PlainObjectBase & V); + // Reads in a matrix and subtracts 1 + template + IGL_INLINE void parse_rhs_index( + const mxArray *prhs[], + Eigen::PlainObjectBase & V); + template + IGL_INLINE void parse_rhs( + const mxArray *prhs[], + Eigen::SparseMatrix & M); + } +}; +#ifndef IGL_STATIC_LIBRARY +# include "parse_rhs.cpp" +#endif +#endif diff --git a/src/igl/matlab/prepare_lhs.cpp b/src/igl/matlab/prepare_lhs.cpp new file mode 100644 index 000000000..93593685e --- /dev/null +++ b/src/igl/matlab/prepare_lhs.cpp @@ -0,0 +1,99 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "prepare_lhs.h" +#include +template +IGL_INLINE void igl::matlab::prepare_lhs_double( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]) +{ + using namespace std; + using namespace Eigen; + const int m = V.rows(); + const int n = V.cols(); + plhs[0] = mxCreateDoubleMatrix(m,n, mxREAL); + Eigen::Map< Eigen::Matrix > + map(mxGetPr(plhs[0]),m,n); + map = V.template cast(); +} + +template +IGL_INLINE void igl::matlab::prepare_lhs_logical( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]) +{ + using namespace std; + using namespace Eigen; + const int m = V.rows(); + const int n = V.cols(); + plhs[0] = mxCreateLogicalMatrix(m,n); + mxLogical * Vp = static_cast(mxGetData(plhs[0])); + Eigen::Map< Eigen::Matrix > + map(static_cast(mxGetData(plhs[0])),m,n); + map = V.template cast(); +} + +template +IGL_INLINE void igl::matlab::prepare_lhs_index( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]) +{ + // Treat indices as reals + const auto Vd = (V.template cast().array()+1).eval(); + return prepare_lhs_double(Vd,plhs); +} + +template +IGL_INLINE void igl::matlab::prepare_lhs_double( + const Eigen::SparseMatrix & M, + mxArray *plhs[]) +{ + using namespace std; + const int m = M.rows(); + const int n = M.cols(); + // THIS WILL NOT WORK FOR ROW-MAJOR + assert(n==M.outerSize()); + const int nzmax = M.nonZeros(); + plhs[0] = mxCreateSparse(m, n, nzmax, mxREAL); + mxArray * mx_data = plhs[0]; + // Copy data immediately + double * pr = mxGetPr(mx_data); + mwIndex * ir = mxGetIr(mx_data); + mwIndex * jc = mxGetJc(mx_data); + + // Iterate over outside + int k = 0; + for(int j=0; j::InnerIterator it (M,j); it; ++it) + { + // copy (cast to double) + pr[k] = it.value(); + ir[k] = it.row(); + k++; + } + } + jc[M.outerSize()] = k; + +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::matlab::prepare_lhs_index >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_index >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_double >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_index >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_logical >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_double >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_logical >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_index >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_double >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_double >(Eigen::PlainObjectBase > const&, mxArray_tag**); +template void igl::matlab::prepare_lhs_double >(Eigen::PlainObjectBase > const&, mxArray_tag**); +#endif diff --git a/src/igl/matlab/prepare_lhs.h b/src/igl/matlab/prepare_lhs.h new file mode 100644 index 000000000..0ac0ca6ce --- /dev/null +++ b/src/igl/matlab/prepare_lhs.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_PREPARE_LHS_H +#define IGL_MATLAB_PREPARE_LHS_H +#include +#include +#include +#include +namespace igl +{ + namespace matlab + { + // Writes out a matrix as a double + // + // Inputs: + // prhs points to rhs argument + // Outputs: + // V M by N matrix + template + IGL_INLINE void prepare_lhs_double( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]); + // Casts to logical + template + IGL_INLINE void prepare_lhs_logical( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]); + // Writes out a matrix and adds 1 + template + IGL_INLINE void prepare_lhs_index( + const Eigen::PlainObjectBase & V, + mxArray *plhs[]); + // SparseMatrix + template + IGL_INLINE void prepare_lhs_double( + const Eigen::SparseMatrix & V, + mxArray *plhs[]); + }; +} +#ifndef IGL_STATIC_LIBRARY +# include "prepare_lhs.cpp" +#endif +#endif + diff --git a/src/igl/matlab/requires_arg.cpp b/src/igl/matlab/requires_arg.cpp new file mode 100644 index 000000000..a03469183 --- /dev/null +++ b/src/igl/matlab/requires_arg.cpp @@ -0,0 +1,16 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "requires_arg.h" +#include "mexErrMsgTxt.h" +#include "../C_STR.h" + +IGL_INLINE void igl::matlab::requires_arg(const int i, const int nrhs, const char *name) +{ + mexErrMsgTxt((i+1) +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REQUIRES_ARG_H +#define IGL_REQUIRES_ARG_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace matlab + { + // Simply throw an error if (i+1) +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "validate_arg.h" +#include "requires_arg.h" +#include "mexErrMsgTxt.h" +#include "../C_STR.h" + +IGL_INLINE void igl::matlab::validate_arg_scalar( + const int i, const int nrhs, const mxArray * prhs[], const char * name) +{ + requires_arg(i,nrhs,name); + mexErrMsgTxt(mxGetN(prhs[i+1])==1 && mxGetM(prhs[i+1])==1, + C_STR("Parameter '"< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VALIDATE_ARG_H +#define IGL_VALIDATE_ARG_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace matlab + { + // Throw an error if arg i+1 is not a scalar + // + // Inputs: + // i index of current argument + // nrhs total number of arguments + // prhs pointer to arguments array + // name name of current argument + IGL_INLINE void validate_arg_scalar( + const int i, const int nrhs, const mxArray * prhs[], const char * name); + IGL_INLINE void validate_arg_logical( + const int i, const int nrhs, const mxArray * prhs[], const char * name); + IGL_INLINE void validate_arg_char( + const int i, const int nrhs, const mxArray * prhs[], const char * name); + IGL_INLINE void validate_arg_double( + const int i, const int nrhs, const mxArray * prhs[], const char * name); + IGL_INLINE void validate_arg_function_handle( + const int i, const int nrhs, const mxArray * prhs[], const char * name); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "validate_arg.cpp" +#endif +#endif diff --git a/src/igl/matlab_format.cpp b/src/igl/matlab_format.cpp new file mode 100644 index 000000000..ceb5d6db7 --- /dev/null +++ b/src/igl/matlab_format.cpp @@ -0,0 +1,155 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "matlab_format.h" +#include "STR.h" +#include "find.h" + +template +IGL_INLINE const Eigen::WithFormat< DerivedM > igl::matlab_format( + const Eigen::DenseBase & M, + const std::string name) +{ + using namespace std; + string prefix = ""; + if(!name.empty()) + { + prefix = name + " = "; + } + + return M.format(Eigen::IOFormat( + Eigen::FullPrecision, + 0, + " ", + "\n", + "", + "", + // This seems like a bit of a hack since I would expect the rows to align + // with out this extra spacing on the first line + prefix + "[\n ", + "\n];")); +} + +template +IGL_INLINE const std::string +igl::matlab_format( + const Eigen::SparseMatrix & S, + const std::string name) +{ + using namespace Eigen; + using namespace std; + Matrix::Scalar,Dynamic,1> I,J,V; + Matrix SIJV; + find(S,I,J,V); + I.array() += 1; + J.array() += 1; + SIJV.resize(V.rows(),3); + SIJV << I,J,V; + string prefix = ""; + string suffix = ""; + if(!name.empty()) + { + prefix = name + "IJV = "; + suffix = "\n"+name + " = sparse("+name+"IJV(:,1),"+name+"IJV(:,2),"+name+"IJV(:,3),"+std::to_string(S.rows())+","+std::to_string(S.cols())+" );"; + } + return STR(""<< + SIJV.format(Eigen::IOFormat( + Eigen::FullPrecision, + 0, + " ", + "\n", + "", + "", + // This seems like a bit of a hack since I would expect the rows to align + // with out this extra spacing on the first line + prefix + "[\n ", + "\n];"))< > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +// generated by autoexplicit.sh +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template std::basic_string, std::allocator > const igl::matlab_format(Eigen::SparseMatrix const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::string); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +template Eigen::WithFormat > const igl::matlab_format >(Eigen::DenseBase > const&, std::basic_string, std::allocator >); +#endif diff --git a/src/igl/matlab_format.h b/src/igl/matlab_format.h new file mode 100644 index 000000000..e1f3ff830 --- /dev/null +++ b/src/igl/matlab_format.h @@ -0,0 +1,89 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATLAB_FORMAT_H +#define IGL_MATLAB_FORMAT_H + +#include "igl_inline.h" + +#include +#include +#include + +namespace igl +{ + // This is a routine to print a matrix using format suitable for pasting into + // the matlab IDE + // + // Templates: + // DerivedM e.g. derived from MatrixXd + // Input: + // input some matrix to be formatted + // name name of matrix + // Returns Formatted matrix + // + // Example: + // // M := [1 2 3;4 5 6]; + // cout< + IGL_INLINE const Eigen::WithFormat< DerivedM > matlab_format( + const Eigen::DenseBase & M, + const std::string name = ""); + // Same but for sparse matrices. Print IJV format into an auxiliary variable + // and then print a call to sparse which will construct the sparse matrix + // Example: + // // S := [0 2 3;4 5 0]; + // cout< + IGL_INLINE const std::string matlab_format( + const Eigen::SparseMatrix & S, + const std::string name = ""); + IGL_INLINE const std::string matlab_format( + const double v, + const std::string name = ""); + IGL_INLINE const std::string matlab_format( + const float v, + const std::string name = ""); + // Return just IOFormat + // + // Example: + // // M := [1 2 3;4 5 6]; + // cout< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "matrix_to_list.h" + +#include + +template +IGL_INLINE void igl::matrix_to_list( + const Eigen::DenseBase & M, + std::vector > & V) +{ + using namespace std; + V.resize(M.rows(),vector(M.cols())); + // loop over rows + for(int i = 0;i +IGL_INLINE void igl::matrix_to_list( + const Eigen::DenseBase & M, + std::vector & V) +{ + using namespace std; + V.resize(M.size()); + // loop over cols then rows + for(int j = 0;j +IGL_INLINE std::vector igl::matrix_to_list( + const Eigen::DenseBase & M) +{ + std::vector V; + matrix_to_list(M,V); + return V; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::matrix_to_list, -1, 1, true> >(Eigen::DenseBase, -1, 1, true> > const&, std::vector, -1, 1, true>::Scalar, std::allocator, -1, 1, true>::Scalar> >&); +// generated by autoexplicit.sh +template void igl::matrix_to_list, -1, 1, true> >(Eigen::DenseBase, -1, 1, true> > const&, std::vector, -1, 1, true>::Scalar, std::allocator, -1, 1, true>::Scalar> >&); +// generated by autoexplicit.sh +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +// generated by autoexplicit.sh +template std::vector::Scalar, std::allocator::Scalar> > igl::matrix_to_list >(Eigen::DenseBase > const&); +// generated by autoexplicit.sh +template std::vector::Scalar, std::allocator::Scalar> > igl::matrix_to_list >(Eigen::DenseBase > const&); +//template void igl::matrix_to_list >, double>(Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > >&); +//template void igl::matrix_to_list >, int>(Eigen::PlainObjectBase > const&, std::vector >, std::allocator > > >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >, std::allocator::Scalar, std::allocator::Scalar> > > >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >, std::allocator::Scalar, std::allocator::Scalar> > > >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >, std::allocator::Scalar, std::allocator::Scalar> > > >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +template void igl::matrix_to_list >(Eigen::DenseBase > const&, std::vector::Scalar, std::allocator::Scalar> >&); +template std::vector::Scalar, std::allocator::Scalar> > igl::matrix_to_list >(Eigen::DenseBase > const&); +#endif diff --git a/src/igl/matrix_to_list.h b/src/igl/matrix_to_list.h new file mode 100644 index 000000000..a84281127 --- /dev/null +++ b/src/igl/matrix_to_list.h @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MATRIX_TO_LIST_H +#define IGL_MATRIX_TO_LIST_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Convert a matrix to a list (std::vector) of row vectors of the same size + // + // Template: + // Mat Matrix type, must implement: + // .resize(m,n) + // .row(i) = Row + // T type that can be safely cast to type in Mat via '=' + // Inputs: + // M an m by n matrix + // Outputs: + // V a m-long list of vectors of size n + // + // See also: list_to_matrix + template + IGL_INLINE void matrix_to_list( + const Eigen::DenseBase & M, + std::vector > & V); + // Convert a matrix to a list (std::vector) of elements in column-major + // ordering. + // + // Inputs: + // M an m by n matrix + // Outputs: + // V an m*n list of elements + template + IGL_INLINE void matrix_to_list( + const Eigen::DenseBase & M, + std::vector & V); + // Return wrapper + template + IGL_INLINE std::vector matrix_to_list( + const Eigen::DenseBase & M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "matrix_to_list.cpp" +#endif + +#endif + diff --git a/src/igl/max.cpp b/src/igl/max.cpp new file mode 100644 index 000000000..e451c3b15 --- /dev/null +++ b/src/igl/max.cpp @@ -0,0 +1,46 @@ +#include "max.h" +#include "for_each.h" +#include "find_zero.h" + +template +IGL_INLINE void igl::max( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & I) +{ + const int n = A.cols(); + const int m = A.rows(); + B.resize(dim==1?n:m); + B.setConstant(std::numeric_limits::lowest()); + I.resize(dim==1?n:m); + for_each(A,[&B,&I,&dim](int i, int j,const typename DerivedB::Scalar v) + { + if(dim == 2) + { + std::swap(i,j); + } + // Coded as if dim == 1, assuming swap for dim == 2 + if(v > B(j)) + { + B(j) = v; + I(j) = i; + } + }); + Eigen::VectorXi Z; + find_zero(A,dim,Z); + for(int j = 0;j B(j)) + { + B(j) = 0; + I(j) = Z(j); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::max, Eigen::Matrix >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/max.h b/src/igl/max.h new file mode 100644 index 000000000..554a19b13 --- /dev/null +++ b/src/igl/max.h @@ -0,0 +1,27 @@ +#ifndef IGL_MAX_H +#define IGL_MAX_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Inputs: + // X m by n matrix + // dim dimension along which to take max + // Outputs: + // Y n-long vector (if dim == 1) + // or + // Y m-long vector (if dim == 2) + // I vector the same size as Y containing the indices along dim of maximum + // entries + template + IGL_INLINE void max( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "max.cpp" +#endif +#endif diff --git a/src/igl/max_faces_stopping_condition.cpp b/src/igl/max_faces_stopping_condition.cpp new file mode 100644 index 000000000..adc4fc96e --- /dev/null +++ b/src/igl/max_faces_stopping_condition.cpp @@ -0,0 +1,93 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "max_faces_stopping_condition.h" + +IGL_INLINE void igl::max_faces_stopping_condition( + int & m, + const int orig_m, + const int max_m, + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition) +{ + stopping_condition = + [orig_m,max_m,&m]( + const Eigen::MatrixXd &, + const Eigen::MatrixXi &, + const Eigen::MatrixXi &, + const Eigen::VectorXi &, + const Eigen::MatrixXi &, + const Eigen::MatrixXi &, + const std::set > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int f1, + const int f2)->bool + { + // Only subtract if we're collapsing a real face + if(f1 < orig_m) m-=1; + if(f2 < orig_m) m-=1; + return m<=(int)max_m; + }; +} + +IGL_INLINE + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> + igl::max_faces_stopping_condition( + int & m, + const int orig_m, + const int max_m) +{ + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> stopping_condition; + max_faces_stopping_condition( + m,orig_m,max_m,stopping_condition); + return stopping_condition; +} diff --git a/src/igl/max_faces_stopping_condition.h b/src/igl/max_faces_stopping_condition.h new file mode 100644 index 000000000..e886a1f5c --- /dev/null +++ b/src/igl/max_faces_stopping_condition.h @@ -0,0 +1,75 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAX_FACES_STOPPING_CONDITION_H +#define IGL_MAX_FACES_STOPPING_CONDITION_H +#include "igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + // Stopping condition function compatible with igl::decimate. The outpute + // function handle will return true if number of faces is less than max_m + // + // Inputs: + // m reference to working variable initially should be set to current + // number of faces. + // orig_m number (size) of original face list _**not**_ including any + // faces added to handle phony boundary faces connecting to point at + // infinity. For closed meshes it's safe to set this to F.rows() + // max_m maximum number of faces + // Outputs: + // stopping_condition + // + IGL_INLINE void max_faces_stopping_condition( + int & m, + const int orig_m, + const int max_m, + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> & stopping_condition); + IGL_INLINE + std::function > &, + const std::vector >::iterator > &, + const Eigen::MatrixXd &, + const int, + const int, + const int, + const int, + const int)> + max_faces_stopping_condition( + int & m, + const int orign_m, + const int max_m); +} + +#ifndef IGL_STATIC_LIBRARY +# include "max_faces_stopping_condition.cpp" +#endif +#endif + diff --git a/src/igl/max_size.cpp b/src/igl/max_size.cpp new file mode 100644 index 000000000..f3b9a07f2 --- /dev/null +++ b/src/igl/max_size.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "max_size.h" + + +template +IGL_INLINE int igl::max_size(const std::vector & V) +{ + int max_size = -1; + for( + typename std::vector::const_iterator iter = V.begin(); + iter != V.end(); + iter++) + { + int size = (int)iter->size(); + max_size = (max_size > size ? max_size : size); + } + return max_size; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +template int igl::max_size > >(std::vector >, std::allocator > > > const&); +#endif diff --git a/src/igl/max_size.h b/src/igl/max_size.h new file mode 100644 index 000000000..58035d4ce --- /dev/null +++ b/src/igl/max_size.h @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MAX_SIZE_H +#define IGL_MAX_SIZE_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Determine max size of lists in a vector + // Template: + // T some list type object that implements .size() + // Inputs: + // V vector of list types T + // Returns max .size() found in V, returns -1 if V is empty + template + IGL_INLINE int max_size(const std::vector & V); +} + +#ifndef IGL_STATIC_LIBRARY +# include "max_size.cpp" +#endif + +#endif diff --git a/src/igl/median.cpp b/src/igl/median.cpp new file mode 100644 index 000000000..718b9acc9 --- /dev/null +++ b/src/igl/median.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "median.h" +#include "matrix_to_list.h" + +#include +#include + +template +IGL_INLINE bool igl::median( + const Eigen::MatrixBase & V, mType & m) +{ + using namespace std; + if(V.size() == 0) + { + return false; + } + vector vV; + matrix_to_list(V,vV); + // http://stackoverflow.com/a/1719155/148668 + size_t n = vV.size()/2; + nth_element(vV.begin(),vV.begin()+n,vV.end()); + if(vV.size()%2==0) + { + nth_element(vV.begin(),vV.begin()+n-1,vV.end()); + m = 0.5*(vV[n]+vV[n-1]); + }else + { + m = vV[n]; + } + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::median, -1, 1, true>, float>(Eigen::MatrixBase, -1, 1, true> > const&, float&); +// generated by autoexplicit.sh +template bool igl::median, -1, 1, true>, double>(Eigen::MatrixBase, -1, 1, true> > const&, double&); +#endif diff --git a/src/igl/median.h b/src/igl/median.h new file mode 100644 index 000000000..7986dfda4 --- /dev/null +++ b/src/igl/median.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MEDIAN_H +#define IGL_MEDIAN_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the median of an eigen vector + // + // Inputs: + // V #V list of unsorted values + // Outputs: + // m median of those values + // Returns true on success, false on failure + template + IGL_INLINE bool median( + const Eigen::MatrixBase & V, mType & m); +} + +#ifndef IGL_STATIC_LIBRARY +# include "median.cpp" +#endif + +#endif diff --git a/src/igl/min.cpp b/src/igl/min.cpp new file mode 100644 index 000000000..ce8ba328e --- /dev/null +++ b/src/igl/min.cpp @@ -0,0 +1,41 @@ +#include "min.h" +#include "for_each.h" +#include "find_zero.h" + +template +IGL_INLINE void igl::min( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & I) +{ + const int n = A.cols(); + const int m = A.rows(); + B.resize(dim==1?n:m); + B.setConstant(std::numeric_limits::max()); + I.resize(dim==1?n:m); + for_each(A,[&B,&I,&dim](int i, int j,const typename DerivedB::Scalar v) + { + if(dim == 2) + { + std::swap(i,j); + } + // Coded as if dim == 1, assuming swap for dim == 2 + if(v < B(j)) + { + B(j) = v; + I(j) = i; + } + }); + Eigen::VectorXi Z; + find_zero(A,dim,Z); + for(int j = 0;j +#include +namespace igl +{ + // Inputs: + // X m by n matrix + // dim dimension along which to take min + // Outputs: + // Y n-long vector (if dim == 1) + // or + // Y m-long vector (if dim == 2) + // I vector the same size as Y containing the indices along dim of minimum + // entries + template + IGL_INLINE void min( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "min.cpp" +#endif +#endif + diff --git a/src/igl/min_quad_dense.cpp b/src/igl/min_quad_dense.cpp new file mode 100644 index 000000000..f489d996c --- /dev/null +++ b/src/igl/min_quad_dense.cpp @@ -0,0 +1,97 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "min_quad_dense.h" + +#include +#include +#include "EPS.h" +#include + +template +IGL_INLINE void igl::min_quad_dense_precompute( + const Eigen::Matrix& A, + const Eigen::Matrix& Aeq, + const bool use_lu_decomposition, + Eigen::Matrix& S) +{ + typedef Eigen::Matrix Mat; + // This threshold seems to matter a lot but I'm not sure how to + // set it + const T treshold = igl::FLOAT_EPS; + //const T treshold = igl::DOUBLE_EPS; + + const int n = A.rows(); + assert(A.cols() == n); + const int m = Aeq.rows(); + assert(Aeq.cols() == n); + + // Lagrange multipliers method: + Eigen::Matrix LM(n + m, n + m); + LM.block(0, 0, n, n) = A; + LM.block(0, n, n, m) = Aeq.transpose(); + LM.block(n, 0, m, n) = Aeq; + LM.block(n, n, m, m).setZero(); + + Mat LMpinv; + if(use_lu_decomposition) + { + // if LM is close to singular, use at your own risk :) + LMpinv = LM.inverse(); + }else + { + // use SVD + typedef Eigen::Matrix Vec; + Vec singValues; + Eigen::JacobiSVD svd; + svd.compute(LM, Eigen::ComputeFullU | Eigen::ComputeFullV ); + const Mat& u = svd.matrixU(); + const Mat& v = svd.matrixV(); + const Vec& singVals = svd.singularValues(); + + Vec pi_singVals(n + m); + int zeroed = 0; + for (int i=0; i= 0); + // printf("sv: %lg ? %lg\n",(double) sv,(double)treshold); + if (sv > treshold) pi_singVals(i, 0) = T(1) / sv; + else + { + pi_singVals(i, 0) = T(0); + zeroed++; + } + } + + printf("min_quad_dense_precompute: %i singular values zeroed (threshold = %e)\n", zeroed, treshold); + Eigen::DiagonalMatrix pi_diag(pi_singVals); + + LMpinv = v * pi_diag * u.transpose(); + } + S = LMpinv.block(0, 0, n, n + m); + + //// debug: + //mlinit(&g_pEngine); + // + //mlsetmatrix(&g_pEngine, "A", A); + //mlsetmatrix(&g_pEngine, "Aeq", Aeq); + //mlsetmatrix(&g_pEngine, "LM", LM); + //mlsetmatrix(&g_pEngine, "u", u); + //mlsetmatrix(&g_pEngine, "v", v); + //MatrixXd svMat = singVals; + //mlsetmatrix(&g_pEngine, "singVals", svMat); + //mlsetmatrix(&g_pEngine, "LMpinv", LMpinv); + //mlsetmatrix(&g_pEngine, "S", S); + + //int hu = 1; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::min_quad_dense_precompute(Eigen::Matrix const&, Eigen::Matrix const&, bool, Eigen::Matrix&); +#endif diff --git a/src/igl/min_quad_dense.h b/src/igl/min_quad_dense.h new file mode 100644 index 000000000..3d115168a --- /dev/null +++ b/src/igl/min_quad_dense.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MIN_QUAD_DENSE_H +#define IGL_MIN_QUAD_DENSE_H +#include "igl_inline.h" + +#include + +//// debug +//#include +//Engine *g_pEngine; + + +namespace igl +{ + // MIN_QUAD_WITH_FIXED Minimize quadratic energy Z'*A*Z + Z'*B + C + // subject to linear constraints Aeq*Z = Beq + // + // Templates: + // T should be a eigen matrix primitive type like float or double + // Inputs: + // A n by n matrix of quadratic coefficients + // B n by 1 column of linear coefficients + // Aeq m by n list of linear equality constraint coefficients + // Beq m by 1 list of linear equality constraint constant values + // use_lu_decomposition use lu rather than SVD + // Outputs: + // S n by (n + m) "solve" matrix, such that S*[B', Beq'] is a solution + // Returns true on success, false on error + template + IGL_INLINE void min_quad_dense_precompute( + const Eigen::Matrix& A, + const Eigen::Matrix& Aeq, + const bool use_lu_decomposition, + Eigen::Matrix& S); +} + +#ifndef IGL_STATIC_LIBRARY +# include "min_quad_dense.cpp" +#endif + +#endif diff --git a/src/igl/min_quad_with_fixed.cpp b/src/igl/min_quad_with_fixed.cpp new file mode 100644 index 000000000..ffb1322c7 --- /dev/null +++ b/src/igl/min_quad_with_fixed.cpp @@ -0,0 +1,597 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "min_quad_with_fixed.h" + +#include "slice.h" +#include "is_symmetric.h" +#include "find.h" +#include "sparse.h" +#include "repmat.h" +#include "matlab_format.h" +#include "EPS.h" +#include "cat.h" + +//#include +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include +#include +#include +#include + +template +IGL_INLINE bool igl::min_quad_with_fixed_precompute( + const Eigen::SparseMatrix& A2, + const Eigen::MatrixBase & known, + const Eigen::SparseMatrix& Aeq, + const bool pd, + min_quad_with_fixed_data & data + ) +{ +//#define MIN_QUAD_WITH_FIXED_CPP_DEBUG + using namespace Eigen; + using namespace std; + const Eigen::SparseMatrix A = 0.5*A2; +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" pre"<= 0)&& "known indices should be in [0,n)"); + assert((kr == 0 || known.maxCoeff() < n) && "known indices should be in [0,n)"); + assert(neq <= n && "Number of equality constraints should be less than DOFs"); + + + // cache known + data.known = known; + // get list of unknown indices + data.unknown.resize(n-kr); + std::vector unknown_mask; + unknown_mask.resize(n,true); + for(int i = 0;i 0) + { + data.unknown_lagrange.head(data.unknown.size()) = data.unknown; + } + if(data.lagrange.size() > 0) + { + data.unknown_lagrange.tail(data.lagrange.size()) = data.lagrange; + } + + SparseMatrix Auu; + slice(A,data.unknown,data.unknown,Auu); + assert(Auu.size() != 0 && Auu.rows() > 0 && "There should be at least one unknown."); + + // Positive definiteness is *not* determined, rather it is given as a + // parameter + data.Auu_pd = pd; + if(data.Auu_pd) + { + // PD implies symmetric + data.Auu_sym = true; + // This is an annoying assertion unless EPS can be chosen in a nicer way. + //assert(is_symmetric(Auu,EPS())); + assert(is_symmetric(Auu,1.0) && + "Auu should be symmetric if positive definite"); + }else + { + // determine if A(unknown,unknown) is symmetric and/or positive definite + VectorXi AuuI,AuuJ; + MatrixXd AuuV; + find(Auu,AuuI,AuuJ,AuuV); + data.Auu_sym = is_symmetric(Auu,EPS()*AuuV.maxCoeff()); + } + + // Determine number of linearly independent constraints + int nc = 0; + if(neq>0) + { +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" qr"<(data.Aequ.transpose().eval()),"AeqT")< new_A; + SparseMatrix AeqT = Aeq.transpose(); + SparseMatrix Z(neq,neq); + // This is a bit slower. But why isn't cat fast? + new_A = cat(1, cat(2, A, AeqT ), + cat(2, Aeq, Z )); + + // precompute RHS builders + if(kr > 0) + { + SparseMatrix Aulk,Akul; + // Slow + slice(new_A,data.unknown_lagrange,data.known,Aulk); + //// This doesn't work!!! + //data.preY = Aulk + Akul.transpose(); + // Slow + if(data.Auu_sym) + { + data.preY = Aulk*2; + }else + { + slice(new_A,data.known,data.unknown_lagrange,Akul); + SparseMatrix AkulT = Akul.transpose(); + data.preY = Aulk + AkulT; + } + }else + { + data.preY.resize(data.unknown_lagrange.size(),0); + } + + // Positive definite and no equality constraints (Positive definiteness + // implies symmetric) +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" factorize"<::LLT; + }else + { +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" ldlt"< NA; + slice(new_A,data.unknown_lagrange,data.unknown_lagrange,NA); + data.NA = NA; + // Ideally we'd use LDLT but Eigen doesn't support positive semi-definite + // matrices: + // http://forum.kde.org/viewtopic.php?f=74&t=106962&p=291990#p291990 + if(data.Auu_sym && false) + { + data.ldlt.compute(NA); + switch(data.ldlt.info()) + { + case Eigen::Success: + break; + case Eigen::NumericalIssue: + cerr<<"Error: Numerical issue."<::LDLT; + }else + { +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" lu"<1/2 + data.lu.compute(NA); + //std::cout<<"NA=["<::LU; + } + } + }else + { +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" Aeq_li=false"< AeqTR,AeqTQ; + AeqTR = data.AeqTQR.matrixR(); + // This shouldn't be necessary + AeqTR.prune(0.0); +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" matrixQ"< I(neq,neq); + I.setIdentity(); + data.AeqTE = data.AeqTQR.colsPermutation() * I; + data.AeqTET = data.AeqTQR.colsPermutation().transpose() * I; + assert(AeqTR.rows() == nu && "#rows in AeqTR should match #unknowns"); + assert(AeqTR.cols() == neq && "#cols in AeqTR should match #constraints"); + assert(AeqTQ.rows() == nu && "#rows in AeqTQ should match #unknowns"); + assert(AeqTQ.cols() == nu && "#cols in AeqTQ should match #unknowns"); + //cout<<" slice"< QRAuu = data.AeqTQ2T * Auu * data.AeqTQ2; + { +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" factorize"<::QR_LLT; + } +#ifdef MIN_QUAD_WITH_FIXED_CPP_DEBUG + cout<<" smash"< Auk; + slice(A,data.unknown,data.known,Auk); + SparseMatrix Aku; + slice(A,data.known,data.unknown,Aku); + SparseMatrix AkuT = Aku.transpose(); + data.preY = Auk + AkuT; + // Needed during solve + data.Auu = Auu; + slice(Aeq,data.known,2,data.Aeqk); + assert(data.Aeqk.rows() == neq); + assert(data.Aeqk.cols() == data.known.size()); + } + return true; +} + + +template < + typename T, + typename DerivedB, + typename DerivedY, + typename DerivedBeq, + typename DerivedZ, + typename Derivedsol> +IGL_INLINE bool igl::min_quad_with_fixed_solve( + const min_quad_with_fixed_data & data, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & Y, + const Eigen::MatrixBase & Beq, + Eigen::PlainObjectBase & Z, + Eigen::PlainObjectBase & sol) +{ + using namespace std; + using namespace Eigen; + typedef Matrix VectorXT; + typedef Matrix MatrixXT; + // number of known rows + int kr = data.known.size(); + if(kr!=0) + { + assert(kr == Y.rows()); + } + // number of columns to solve + int cols = Y.cols(); + assert(B.cols() == 1 || B.cols() == cols); + assert(Beq.size() == 0 || Beq.cols() == 1 || Beq.cols() == cols); + + // resize output + Z.resize(data.n,cols); + // Set known values + for(int i = 0;i < kr;i++) + { + for(int j = 0;j < cols;j++) + { + Z(data.known(i),j) = Y(i,j); + } + } + + if(data.Aeq_li) + { + // number of lagrange multipliers aka linear equality constraints + int neq = data.lagrange.size(); + // append lagrange multiplier rhs's + MatrixXT BBeq(B.rows() + Beq.rows(),cols); + if(B.size() > 0) + { + BBeq.topLeftCorner(B.rows(),cols) = B.replicate(1,B.cols()==cols?1:cols); + } + if(Beq.size() > 0) + { + BBeq.bottomLeftCorner(Beq.rows(),cols) = -2.0*Beq.replicate(1,Beq.cols()==cols?1:cols); + } + + // Build right hand side + MatrixXT BBequlcols; + igl::slice(BBeq,data.unknown_lagrange,1,BBequlcols); + MatrixXT NB; + if(kr == 0) + { + NB = BBequlcols; + }else + { + NB = data.preY * Y + BBequlcols; + } + + //std::cout<<"NB=["<::LLT: + sol = data.llt.solve(NB); + break; + case igl::min_quad_with_fixed_data::LDLT: + sol = data.ldlt.solve(NB); + break; + case igl::min_quad_with_fixed_data::LU: + // Not a bottleneck + sol = data.lu.solve(NB); + break; + default: + cerr<<"Error: invalid solver type"<::QR_LLT); + MatrixXT eff_Beq; + // Adjust Aeq rhs to include known parts + eff_Beq = + //data.AeqTQR.colsPermutation().transpose() * (-data.Aeqk * Y + Beq); + data.AeqTET * (-data.Aeqk * Y + Beq.replicate(1,Beq.cols()==cols?1:cols)); + // Where did this -0.5 come from? Probably the same place as above. + MatrixXT Bu; + slice(B,data.unknown,1,Bu); + MatrixXT NB; + NB = -0.5*(Bu.replicate(1,B.cols()==cols?1:cols) + data.preY * Y); + // Trim eff_Beq + const int nc = data.AeqTQR.rank(); + const int neq = Beq.rows(); + eff_Beq = eff_Beq.topLeftCorner(nc,cols).eval(); + data.AeqTR1T.template triangularView().solveInPlace(eff_Beq); + // Now eff_Beq = (data.AeqTR1T \ (data.AeqTET * (-data.Aeqk * Y + Beq))) + MatrixXT lambda_0; + lambda_0 = data.AeqTQ1 * eff_Beq; + //cout<().solveInPlace(temp1); + //cout< +IGL_INLINE bool igl::min_quad_with_fixed_solve( + const min_quad_with_fixed_data & data, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & Y, + const Eigen::MatrixBase & Beq, + Eigen::PlainObjectBase & Z) +{ + Eigen::Matrix sol; + return min_quad_with_fixed_solve(data,B,Y,Beq,Z,sol); +} + +template < + typename T, + typename Derivedknown, + typename DerivedB, + typename DerivedY, + typename DerivedBeq, + typename DerivedZ> +IGL_INLINE bool igl::min_quad_with_fixed( + const Eigen::SparseMatrix& A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & known, + const Eigen::MatrixBase & Y, + const Eigen::SparseMatrix& Aeq, + const Eigen::MatrixBase & Beq, + const bool pd, + Eigen::PlainObjectBase & Z) +{ + min_quad_with_fixed_data data; + if(!min_quad_with_fixed_precompute(A,known,Aeq,pd,data)) + { + return false; + } + return min_quad_with_fixed_solve(data,B,Y,Beq,Z); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::min_quad_with_fixed, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_precompute >(Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix const&, bool, igl::min_quad_with_fixed_data&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_precompute >(Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix const&, bool, igl::min_quad_with_fixed_data&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed_solve, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::min_quad_with_fixed_data const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template bool igl::min_quad_with_fixed, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/min_quad_with_fixed.h b/src/igl/min_quad_with_fixed.h new file mode 100644 index 000000000..269623313 --- /dev/null +++ b/src/igl/min_quad_with_fixed.h @@ -0,0 +1,179 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MIN_QUAD_WITH_FIXED_H +#define IGL_MIN_QUAD_WITH_FIXED_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +#include +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include + +namespace igl +{ + template + struct min_quad_with_fixed_data; + // Known Bugs: rows of Aeq **should probably** be linearly independent. + // During precomputation, the rows of a Aeq are checked via QR. But in case + // they're not then resulting probably will no longer be sparse: it will be + // slow. + // + // MIN_QUAD_WITH_FIXED Minimize a quadratic energy of the form + // + // trace( 0.5*Z'*A*Z + Z'*B + constant ) + // + // subject to + // + // Z(known,:) = Y, and + // Aeq*Z = Beq + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // A n by n matrix of quadratic coefficients + // known list of indices to known rows in Z + // Y list of fixed values corresponding to known rows in Z + // Aeq m by n list of linear equality constraint coefficients + // pd flag specifying whether A(unknown,unknown) is positive definite + // Outputs: + // data factorization struct with all necessary information to solve + // using min_quad_with_fixed_solve + // Returns true on success, false on error + // + // Benchmark: For a harmonic solve on a mesh with 325K facets, matlab 2.2 + // secs, igl/min_quad_with_fixed.h 7.1 secs + // + template + IGL_INLINE bool min_quad_with_fixed_precompute( + const Eigen::SparseMatrix& A, + const Eigen::MatrixBase & known, + const Eigen::SparseMatrix& Aeq, + const bool pd, + min_quad_with_fixed_data & data + ); + // Solves a system previously factored using min_quad_with_fixed_precompute + // + // Template: + // T type of sparse matrix (e.g. double) + // DerivedY type of Y (e.g. derived from VectorXd or MatrixXd) + // DerivedZ type of Z (e.g. derived from VectorXd or MatrixXd) + // Inputs: + // data factorization struct with all necessary precomputation to solve + // B n by k column of linear coefficients + // Y b by k list of constant fixed values + // Beq m by k list of linear equality constraint constant values + // Outputs: + // Z n by k solution + // sol #unknowns+#lagrange by k solution to linear system + // Returns true on success, false on error + template < + typename T, + typename DerivedB, + typename DerivedY, + typename DerivedBeq, + typename DerivedZ, + typename Derivedsol> + IGL_INLINE bool min_quad_with_fixed_solve( + const min_quad_with_fixed_data & data, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & Y, + const Eigen::MatrixBase & Beq, + Eigen::PlainObjectBase & Z, + Eigen::PlainObjectBase & sol); + // Wrapper without sol + template < + typename T, + typename DerivedB, + typename DerivedY, + typename DerivedBeq, + typename DerivedZ> + IGL_INLINE bool min_quad_with_fixed_solve( + const min_quad_with_fixed_data & data, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & Y, + const Eigen::MatrixBase & Beq, + Eigen::PlainObjectBase & Z); + template < + typename T, + typename Derivedknown, + typename DerivedB, + typename DerivedY, + typename DerivedBeq, + typename DerivedZ> + IGL_INLINE bool min_quad_with_fixed( + const Eigen::SparseMatrix& A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & known, + const Eigen::MatrixBase & Y, + const Eigen::SparseMatrix& Aeq, + const Eigen::MatrixBase & Beq, + const bool pd, + Eigen::PlainObjectBase & Z); +} + +template +struct igl::min_quad_with_fixed_data +{ + // Size of original system: number of unknowns + number of knowns + int n; + // Whether A(unknown,unknown) is positive definite + bool Auu_pd; + // Whether A(unknown,unknown) is symmetric + bool Auu_sym; + // Indices of known variables + Eigen::VectorXi known; + // Indices of unknown variables + Eigen::VectorXi unknown; + // Indices of lagrange variables + Eigen::VectorXi lagrange; + // Indices of unknown variable followed by Indices of lagrange variables + Eigen::VectorXi unknown_lagrange; + // Matrix multiplied against Y when constructing right hand side + Eigen::SparseMatrix preY; + enum SolverType + { + LLT = 0, + LDLT = 1, + LU = 2, + QR_LLT = 3, + NUM_SOLVER_TYPES = 4 + } solver_type; + // Solvers + Eigen::SimplicialLLT > llt; + Eigen::SimplicialLDLT > ldlt; + Eigen::SparseLU, Eigen::COLAMDOrdering > lu; + // QR factorization + // Are rows of Aeq linearly independent? + bool Aeq_li; + // Columns of Aeq corresponding to unknowns + int neq; + Eigen::SparseQR, Eigen::COLAMDOrdering > AeqTQR; + Eigen::SparseMatrix Aeqk; + Eigen::SparseMatrix Aequ; + Eigen::SparseMatrix Auu; + Eigen::SparseMatrix AeqTQ1; + Eigen::SparseMatrix AeqTQ1T; + Eigen::SparseMatrix AeqTQ2; + Eigen::SparseMatrix AeqTQ2T; + Eigen::SparseMatrix AeqTR1; + Eigen::SparseMatrix AeqTR1T; + Eigen::SparseMatrix AeqTE; + Eigen::SparseMatrix AeqTET; + // Debug + Eigen::SparseMatrix NA; + Eigen::Matrix NB; +}; + +#ifndef IGL_STATIC_LIBRARY +# include "min_quad_with_fixed.cpp" +#endif + +#endif diff --git a/src/igl/min_size.cpp b/src/igl/min_size.cpp new file mode 100644 index 000000000..0b0cfa9e2 --- /dev/null +++ b/src/igl/min_size.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "min_size.h" + +template +IGL_INLINE int igl::min_size(const std::vector & V) +{ + int min_size = -1; + for( + typename std::vector::const_iterator iter = V.begin(); + iter != V.end(); + iter++) + { + int size = (int)iter->size(); + // have to handle base case + if(min_size == -1) + { + min_size = size; + }else{ + min_size = (min_size < size ? min_size : size); + } + } + return min_size; +} + + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +// generated by autoexplicit.sh +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +template int igl::min_size > >(std::vector >, std::allocator > > > const&); +#endif diff --git a/src/igl/min_size.h b/src/igl/min_size.h new file mode 100644 index 000000000..e44036279 --- /dev/null +++ b/src/igl/min_size.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MIN_SIZE_H +#define IGL_MIN_SIZE_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Determine min size of lists in a vector + // Template: + // T some list type object that implements .size() + // Inputs: + // V vector of list types T + // Returns min .size() found in V, returns -1 if V is empty + template + IGL_INLINE int min_size(const std::vector & V); +} + + +#ifndef IGL_STATIC_LIBRARY +# include "min_size.cpp" +#endif + +#endif diff --git a/src/igl/mod.cpp b/src/igl/mod.cpp new file mode 100644 index 000000000..2064fe6a4 --- /dev/null +++ b/src/igl/mod.cpp @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mod.h" + +template +IGL_INLINE void igl::mod( + const Eigen::PlainObjectBase & A, + const int base, + Eigen::PlainObjectBase & B) +{ + B.resizeLike(A); + for(int i = 0;i +IGL_INLINE DerivedA igl::mod( + const Eigen::PlainObjectBase & A, const int base) +{ + DerivedA B; + mod(A,base,B); + return B; +} +#ifdef IGL_STATIC_LIBRARY +#endif diff --git a/src/igl/mod.h b/src/igl/mod.h new file mode 100644 index 000000000..3ff58a231 --- /dev/null +++ b/src/igl/mod.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MOD_H +#define IGL_MOD_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute elementwise mod: B = A % base + // + // Inputs: + // A m by n matrix + // base number to mod against + // Outputs: + // B m by n matrix + template + IGL_INLINE void mod( + const Eigen::PlainObjectBase & A, + const int base, + Eigen::PlainObjectBase & B); + template + IGL_INLINE DerivedA mod( + const Eigen::PlainObjectBase & A, const int base); +} +#ifndef IGL_STATIC_LIBRARY +#include "mod.cpp" +#endif +#endif diff --git a/src/igl/mode.cpp b/src/igl/mode.cpp new file mode 100644 index 000000000..89df6be66 --- /dev/null +++ b/src/igl/mode.cpp @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mode.h" + +// Implementation +#include + +template +IGL_INLINE void igl::mode( + const Eigen::Matrix & X, + const int d, + Eigen::Matrix & M) +{ + assert(d==1 || d==2); + using namespace std; + int m = X.rows(); + int n = X.cols(); + M.resize((d==1)?n:m,1); + for(int i = 0;i<((d==2)?m:n);i++) + { + vector counts(((d==2)?n:m),0); + for(int j = 0;j<((d==2)?n:m);j++) + { + T v = (d==2)?X(i,j):X(j,i); + for(int k = 0;k<((d==2)?n:m);k++) + { + T u = (d==2)?X(i,k):X(k,i); + if(v == u) + { + counts[k]++; + } + } + } + assert(counts.size() > 0); + int max_count = -1; + int max_count_j = -1; + int j =0; + for(vector::iterator it = counts.begin();it(Eigen::Matrix const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::mode(Eigen::Matrix const&, int, Eigen::Matrix&); +#endif diff --git a/src/igl/mode.h b/src/igl/mode.h new file mode 100644 index 000000000..4d0943881 --- /dev/null +++ b/src/igl/mode.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MODE_H +#define IGL_MODE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Takes mode of coefficients in a matrix along a given dension + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // X m by n original matrix + // d dension along which to take mode, m or n + // Outputs: + // M vector containing mode along dension d, if d==1 then this will be a + // n-long vector if d==2 then this will be a m-long vector + template + IGL_INLINE void mode( + const Eigen::Matrix & X, + const int d, + Eigen::Matrix & M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "mode.cpp" +#endif + +#endif diff --git a/src/igl/mosek/bbw.cpp b/src/igl/mosek/bbw.cpp new file mode 100644 index 000000000..99d28ab15 --- /dev/null +++ b/src/igl/mosek/bbw.cpp @@ -0,0 +1,88 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "bbw.h" +#include "mosek_quadprog.h" +#include "../harmonic.h" +#include "../slice_into.h" +#include +#include +#include + + +template < + typename DerivedV, + typename DerivedEle, + typename Derivedb, + typename Derivedbc, + typename DerivedW> +IGL_INLINE bool igl::mosek::bbw( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + const Eigen::PlainObjectBase & b, + const Eigen::PlainObjectBase & bc, + igl::BBWData & data, + igl::mosek::MosekData & mosek_data, + Eigen::PlainObjectBase & W + ) +{ + using namespace std; + using namespace Eigen; + assert(!data.partition_unity && "partition_unity not implemented yet"); + // number of domain vertices + int n = V.rows(); + // number of handles + int m = bc.cols(); + // Build biharmonic operator + Eigen::SparseMatrix Q; + harmonic(V,Ele,2,Q); + W.derived().resize(n,m); + // No linear terms + VectorXd c = VectorXd::Zero(n); + // No linear constraints + SparseMatrix A(0,n); + VectorXd uc(0,1),lc(0,1); + // Upper and lower box constraints (Constant bounds) + VectorXd ux = VectorXd::Ones(n); + VectorXd lx = VectorXd::Zero(n); + // Loop over handles + for(int i = 0;i= 1) + { + cout<<"BBW: Computing weight for handle "<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::BBWData&, igl::mosek::MosekData&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/mosek/bbw.h b/src/igl/mosek/bbw.h new file mode 100644 index 000000000..5dcae0fed --- /dev/null +++ b/src/igl/mosek/bbw.h @@ -0,0 +1,60 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MOSEK_BBW_H +#define IGL_MOSEK_BBW_H +#include "../igl_inline.h" +#include "mosek_quadprog.h" +#include "../bbw.h" +#include + +namespace igl +{ + namespace mosek + { + // Compute Bounded Biharmonic Weights on a given domain (V,Ele) with a given + // set of boundary conditions + // + // Templates + // DerivedV derived type of eigen matrix for V (e.g. MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. MatrixXi) + // Derivedb derived type of eigen matrix for b (e.g. VectorXi) + // Derivedbc derived type of eigen matrix for bc (e.g. MatrixXd) + // DerivedW derived type of eigen matrix for W (e.g. MatrixXd) + // Inputs: + // V #V by dim vertex positions + // Ele #Elements by simplex-size list of element indices + // b #b boundary indices into V + // bc #b by #W list of boundary values + // data object containing options, initial guess --> solution and results + // mosek_data object containing mosek options + // Outputs: + // W #V by #W list of *unnormalized* weights to normalize use + // igl::normalize_row_sums(W,W); + // Returns true on success, false on failure + template < + typename DerivedV, + typename DerivedEle, + typename Derivedb, + typename Derivedbc, + typename DerivedW> + IGL_INLINE bool bbw( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + const Eigen::PlainObjectBase & b, + const Eigen::PlainObjectBase & bc, + igl::BBWData & data, + igl::mosek::MosekData & mosek_data, + Eigen::PlainObjectBase & W); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "bbw.cpp" +#endif + +#endif diff --git a/src/igl/mosek/mosek_guarded.cpp b/src/igl/mosek/mosek_guarded.cpp new file mode 100644 index 000000000..242d928f6 --- /dev/null +++ b/src/igl/mosek/mosek_guarded.cpp @@ -0,0 +1,24 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mosek_guarded.h" +#include + +IGL_INLINE MSKrescodee igl::mosek::mosek_guarded(const MSKrescodee r) +{ + using namespace std; + if(r != MSK_RES_OK) + { + /* In case of an error print error code and description. */ + char symname[MSK_MAX_STR_LEN]; + char desc[MSK_MAX_STR_LEN]; + MSK_getcodedesc(r,symname,desc); + cerr<<"MOSEK ERROR ("< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MOSEK_MOSEK_GUARDED_H +#define IGL_MOSEK_MOSEK_GUARDED_H +#include "../igl_inline.h" + +#include "mosek.h" +namespace igl +{ + namespace mosek + { + // Little function to wrap around mosek call to handle errors + // + // Inputs: + // r mosek error code returned from mosek call + // Returns r untouched + IGL_INLINE MSKrescodee mosek_guarded(const MSKrescodee r); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "mosek_guarded.cpp" +#endif + +#endif + diff --git a/src/igl/mosek/mosek_linprog.cpp b/src/igl/mosek/mosek_linprog.cpp new file mode 100644 index 000000000..fe09d5adb --- /dev/null +++ b/src/igl/mosek/mosek_linprog.cpp @@ -0,0 +1,164 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mosek_linprog.h" +#include "../mosek/mosek_guarded.h" +#include "../harwell_boeing.h" +#include +#include +#include + +IGL_INLINE bool igl::mosek::mosek_linprog( + const Eigen::VectorXd & c, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + Eigen::VectorXd & x) +{ + // variables for mosek task, env and result code + MSKenv_t env; + // Create the MOSEK environment + mosek_guarded(MSK_makeenv(&env,NULL)); + // initialize mosek environment +#if MSK_VERSION_MAJOR <= 7 + mosek_guarded(MSK_initenv(env)); +#endif + const bool ret = mosek_linprog(c,A,lc,uc,lx,ux,env,x); + MSK_deleteenv(&env); + return ret; +} + +IGL_INLINE bool igl::mosek::mosek_linprog( + const Eigen::VectorXd & c, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + const MSKenv_t & env, + Eigen::VectorXd & x) +{ + // following http://docs.mosek.com/7.1/capi/Linear_optimization.html + using namespace std; + // number of constraints + const int m = A.rows(); + // number of variables + const int n = A.cols(); + + + vector vAv; + vector vAri,vAcp; + int nr; + harwell_boeing(A,nr,vAv,vAri,vAcp); + + MSKtask_t task; + // Create the optimization task + mosek_guarded(MSK_maketask(env,m,n,&task)); + // no threads + mosek_guarded(MSK_putintparam(task,MSK_IPAR_NUM_THREADS,1)); + if(m>0) + { + // Append 'm' empty constraints, the constrainst will initially have no + // bounds + mosek_guarded(MSK_appendcons(task,m)); + } + mosek_guarded(MSK_appendvars(task,n)); + + + const auto & key = [](const double lxj, const double uxj) -> + MSKboundkeye + { + MSKboundkeye k = MSK_BK_FR; + if(isfinite(lxj) && isfinite(uxj)) + { + if(lxj == uxj) + { + k = MSK_BK_FX; + }else{ + k = MSK_BK_RA; + } + }else if(isfinite(lxj)) + { + k = MSK_BK_LO; + }else if(isfinite(uxj)) + { + k = MSK_BK_UP; + } + return k; + }; + + // loop over variables + for(int j = 0;j 0) + { + // Set linear term c_j in the objective + mosek_guarded(MSK_putcj(task,j,c(j))); + } + + // Set constant bounds on variable j + const double lxj = lx.size()>0?lx[j]:-numeric_limits::infinity(); + const double uxj = ux.size()>0?ux[j]: numeric_limits::infinity(); + mosek_guarded(MSK_putvarbound(task,j,key(lxj,uxj),lxj,uxj)); + + if(m>0) + { + // Input column j of A + mosek_guarded( + MSK_putacol( + task, + j, + vAcp[j+1]-vAcp[j], + &vAri[vAcp[j]], + &vAv[vAcp[j]]) + ); + } + } + // loop over constraints + for(int i = 0;i0?lc[i]:-numeric_limits::infinity(); + const double uci = uc.size()>0?uc[i]: numeric_limits::infinity(); + mosek_guarded(MSK_putconbound(task,i,key(lci,uci),lci,uci)); + } + + // Now the optimizer has been prepared + MSKrescodee trmcode; + // run the optimizer + mosek_guarded(MSK_optimizetrm(task,&trmcode)); + // Get status + MSKsolstae solsta; + MSK_getsolsta (task,MSK_SOL_ITR,&solsta); + bool success = false; + switch(solsta) + { + case MSK_SOL_STA_OPTIMAL: + case MSK_SOL_STA_NEAR_OPTIMAL: + x.resize(n); + /* Request the basic solution. */ + MSK_getxx(task,MSK_SOL_BAS,x.data()); + success = true; + break; + case MSK_SOL_STA_DUAL_INFEAS_CER: + case MSK_SOL_STA_PRIM_INFEAS_CER: + case MSK_SOL_STA_NEAR_DUAL_INFEAS_CER: + case MSK_SOL_STA_NEAR_PRIM_INFEAS_CER: + //printf("Primal or dual infeasibility certificate found.\n"); + break; + case MSK_SOL_STA_UNKNOWN: + //printf("The status of the solution could not be determined.\n"); + break; + default: + //printf("Other solution status."); + break; + } + MSK_deletetask(&task); + return success; +} diff --git a/src/igl/mosek/mosek_linprog.h b/src/igl/mosek/mosek_linprog.h new file mode 100644 index 000000000..57791cd60 --- /dev/null +++ b/src/igl/mosek/mosek_linprog.h @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MOSEK_MOSEK_LINPROG_H +#define IGL_MOSEK_MOSEK_LINPROG_H +#include "../igl_inline.h" +#include +#include +#include +namespace igl +{ + namespace mosek + { + // Solve a linear program using mosek: + // + // min c'x + // s.t. lc <= A x <= uc + // lx <= x <= ux + // + // Inputs: + // c #x list of linear objective coefficients + // A #A by #x matrix of linear inequality constraint coefficients + // lc #A list of lower constraint bounds + // uc #A list of upper constraint bounds + // lx #x list of lower variable bounds + // ux #x list of upper variable bounds + // Outputs: + // x #x list of solution values + // Returns true iff success. + IGL_INLINE bool mosek_linprog( + const Eigen::VectorXd & c, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + Eigen::VectorXd & x); + // Wrapper that keeps mosek environment alive (if licence checking is + // becoming a bottleneck) + IGL_INLINE bool mosek_linprog( + const Eigen::VectorXd & c, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + const MSKenv_t & env, + Eigen::VectorXd & x); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "mosek_linprog.cpp" +#endif +#endif diff --git a/src/igl/mosek/mosek_quadprog.cpp b/src/igl/mosek/mosek_quadprog.cpp new file mode 100644 index 000000000..36d9e873b --- /dev/null +++ b/src/igl/mosek/mosek_quadprog.cpp @@ -0,0 +1,343 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mosek_quadprog.h" +#include "mosek_guarded.h" +#include +#include "../find.h" +#include "../verbose.h" +#include "../speye.h" +#include "../matrix_to_list.h" +#include "../list_to_matrix.h" +#include "../harwell_boeing.h" +#include "../EPS.h" + + +igl::mosek::MosekData::MosekData() +{ + // These are the default settings that worked well for BBW. Your miles may + // very well be kilometers. + + // >1e0 NONSOLUTION + // 1e-1 artifacts in deformation + // 1e-3 artifacts in isolines + // 1e-4 seems safe + // 1e-8 MOSEK DEFAULT SOLUTION + douparam[MSK_DPAR_INTPNT_TOL_REL_GAP]=1e-8; +#if MSK_VERSION_MAJOR >= 8 + douparam[MSK_DPAR_INTPNT_QO_TOL_REL_GAP]=1e-12; +#endif + // Force using multiple threads, not sure if MOSEK is properly destroying + //extra threads... +#if MSK_VERSION_MAJOR >= 7 + intparam[MSK_IPAR_NUM_THREADS] = 6; +#elif MSK_VERSION_MAJOR == 6 + intparam[MSK_IPAR_INTPNT_NUM_THREADS] = 6; +#endif +#if MSK_VERSION_MAJOR == 6 + // Force turn off data check + intparam[MSK_IPAR_DATA_CHECK]=MSK_OFF; +#endif + // Turn off presolving + // intparam[MSK_IPAR_PRESOLVE_USE] = MSK_PRESOLVE_MODE_OFF; + // Force particular matrix reordering method + // MSK_ORDER_METHOD_NONE cuts time in half roughly, since half the time is + // usually spent reordering the matrix + // !! WARNING Setting this parameter to anything but MSK_ORDER_METHOD_FREE + // seems to have the effect of setting it to MSK_ORDER_METHOD_NONE + // *Or maybe Mosek is spending a bunch of time analyzing the matrix to + // choose the right ordering method when really any of them are + // instantaneous + intparam[MSK_IPAR_INTPNT_ORDER_METHOD] = MSK_ORDER_METHOD_NONE; + // 1.0 means optimizer is very lenient about declaring model infeasible + douparam[MSK_DPAR_INTPNT_TOL_INFEAS] = 1e-8; + // Hard to say if this is doing anything, probably nothing dramatic + douparam[MSK_DPAR_INTPNT_TOL_PSAFE]= 1e2; + // Turn off convexity check + intparam[MSK_IPAR_CHECK_CONVEXITY] = MSK_CHECK_CONVEXITY_NONE; +} + +template +IGL_INLINE bool igl::mosek::mosek_quadprog( + const Index n, + std::vector & Qi, + std::vector & Qj, + std::vector & Qv, + const std::vector & c, + const Scalar cf, + const Index m, + std::vector & Av, + std::vector & Ari, + const std::vector & Acp, + const std::vector & lc, + const std::vector & uc, + const std::vector & lx, + const std::vector & ux, + MosekData & mosek_data, + std::vector & x) +{ + // I J V vectors of Q should all be same length + assert(Qv.size() == Qi.size()); + assert(Qv.size() == Qj.size()); + // number of columns in linear constraint matrix must be ≤ number of + // variables + assert( (int)Acp.size() == (n+1)); + // linear bound vectors must be size of number of constraints or empty + assert( ((int)lc.size() == m) || ((int)lc.size() == 0)); + assert( ((int)uc.size() == m) || ((int)uc.size() == 0)); + // constant bound vectors must be size of number of variables or empty + assert( ((int)lx.size() == n) || ((int)lx.size() == 0)); + assert( ((int)ux.size() == n) || ((int)ux.size() == 0)); + + // allocate space for solution in x + x.resize(n); + + // variables for mosek task, env and result code + MSKenv_t env; + MSKtask_t task; + + // Create the MOSEK environment +#if MSK_VERSION_MAJOR >= 7 + mosek_guarded(MSK_makeenv(&env,NULL)); +#elif MSK_VERSION_MAJOR == 6 + mosek_guarded(MSK_makeenv(&env,NULL,NULL,NULL,NULL)); +#endif + ///* Directs the log stream to the 'printstr' function. */ + //// Little function mosek needs in order to know how to print to std out + //const auto & printstr = [](void *handle, char str[]) + //{ + // printf("%s",str); + //} + //mosek_guarded(MSK_linkfunctoenvstream(env,MSK_STREAM_LOG,NULL,printstr)); + // initialize mosek environment +#if MSK_VERSION_MAJOR <= 7 + mosek_guarded(MSK_initenv(env)); +#endif + // Create the optimization task + mosek_guarded(MSK_maketask(env,m,n,&task)); + verbose("Creating task with %ld linear constraints and %ld variables...\n",m,n); + //// Tell mosek how to print to std out + //mosek_guarded(MSK_linkfunctotaskstream(task,MSK_STREAM_LOG,NULL,printstr)); + // Give estimate of number of variables + mosek_guarded(MSK_putmaxnumvar(task,n)); + if(m>0) + { + // Give estimate of number of constraints + mosek_guarded(MSK_putmaxnumcon(task,m)); + // Give estimate of number of non zeros in A + mosek_guarded(MSK_putmaxnumanz(task,Av.size())); + } + // Give estimate of number of non zeros in Q + mosek_guarded(MSK_putmaxnumqnz(task,Qv.size())); + if(m>0) + { + // Append 'm' empty constraints, the constrainst will initially have no + // bounds +#if MSK_VERSION_MAJOR >= 7 + mosek_guarded(MSK_appendcons(task,m)); +#elif MSK_VERSION_MAJOR == 6 + mosek_guarded(MSK_append(task,MSK_ACC_CON,m)); +#endif + } + // Append 'n' variables +#if MSK_VERSION_MAJOR >= 7 + mosek_guarded(MSK_appendvars(task,n)); +#elif MSK_VERSION_MAJOR == 6 + mosek_guarded(MSK_append(task,MSK_ACC_VAR,n)); +#endif + // add a contant term to the objective + mosek_guarded(MSK_putcfix(task,cf)); + + // loop over variables + for(int j = 0;j 0) + { + // Set linear term c_j in the objective + mosek_guarded(MSK_putcj(task,j,c[j])); + } + + // Set constant bounds on variable j + if(lx[j] == ux[j]) + { + mosek_guarded(MSK_putbound(task,MSK_ACC_VAR,j,MSK_BK_FX,lx[j],ux[j])); + }else + { + mosek_guarded(MSK_putbound(task,MSK_ACC_VAR,j,MSK_BK_RA,lx[j],ux[j])); + } + + if(m>0) + { + // Input column j of A +#if MSK_VERSION_MAJOR >= 7 + mosek_guarded( + MSK_putacol( + task, + j, + Acp[j+1]-Acp[j], + &Ari[Acp[j]], + &Av[Acp[j]]) + ); +#elif MSK_VERSION_MAJOR == 6 + mosek_guarded( + MSK_putavec( + task, + MSK_ACC_VAR, + j, + Acp[j+1]-Acp[j], + &Ari[Acp[j]], + &Av[Acp[j]]) + ); +#endif + } + } + + // loop over constraints + for(int i = 0;i::iterator pit = mosek_data.intparam.begin(); + pit != mosek_data.intparam.end(); + pit++) + { + mosek_guarded(MSK_putintparam(task,pit->first,pit->second)); + } + for( + std::map::iterator pit = mosek_data.douparam.begin(); + pit != mosek_data.douparam.end(); + pit++) + { + mosek_guarded(MSK_putdouparam(task,pit->first,pit->second)); + } + + // Now the optimizer has been prepared + MSKrescodee trmcode; + // run the optimizer + mosek_guarded(MSK_optimizetrm(task,&trmcode)); + + //// Print a summary containing information about the solution for debugging + //// purposes + //MSK_solutionsummary(task,MSK_STREAM_LOG); + + // Get status of solution + MSKsolstae solsta; +#if MSK_VERSION_MAJOR >= 7 + MSK_getsolsta (task,MSK_SOL_ITR,&solsta); +#elif MSK_VERSION_MAJOR == 6 + MSK_getsolutionstatus(task,MSK_SOL_ITR,NULL,&solsta); +#endif + + bool success = false; + switch(solsta) + { + case MSK_SOL_STA_OPTIMAL: + case MSK_SOL_STA_NEAR_OPTIMAL: + MSK_getsolutionslice(task,MSK_SOL_ITR,MSK_SOL_ITEM_XX,0,n,&x[0]); + //printf("Optimal primal solution\n"); + //for(size_t j=0; j & Q, + const Eigen::VectorXd & c, + const double cf, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + MosekData & mosek_data, + Eigen::VectorXd & x) +{ + using namespace Eigen; + using namespace std; + + typedef int Index; + typedef double Scalar; + // Q should be square + assert(Q.rows() == Q.cols()); + // Q should be symmetric +#ifdef EIGEN_HAS_A_BUG_AND_FAILS_TO_LET_ME_COMPUTE_Q_MINUS_Q_TRANSPOSE + assert( (Q-Q.transpose()).sum() < FLOAT_EPS); +#endif + // Only keep lower triangular part of Q + SparseMatrix QL; + //QL = Q.template triangularView(); + QL = Q.triangularView(); + VectorXi Qi,Qj; + VectorXd Qv; + find(QL,Qi,Qj,Qv); + vector vQi = matrix_to_list(Qi); + vector vQj = matrix_to_list(Qj); + vector vQv = matrix_to_list(Qv); + + // Convert linear term + vector vc = matrix_to_list(c); + + assert(lc.size() == A.rows()); + assert(uc.size() == A.rows()); + // Convert A to harwell boeing format + vector vAv; + vector vAr,vAc; + Index nr; + harwell_boeing(A,nr,vAv,vAr,vAc); + + assert(lx.size() == Q.rows()); + assert(ux.size() == Q.rows()); + vector vlc = matrix_to_list(lc); + vector vuc = matrix_to_list(uc); + vector vlx = matrix_to_list(lx); + vector vux = matrix_to_list(ux); + + vector vx; + bool ret = mosek_quadprog( + Q.rows(),vQi,vQj,vQv, + vc, + cf, + nr, + vAv, vAr, vAc, + vlc,vuc, + vlx,vux, + mosek_data, + vx); + list_to_matrix(vx,x); + return ret; +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template declarations +#endif diff --git a/src/igl/mosek/mosek_quadprog.h b/src/igl/mosek/mosek_quadprog.h new file mode 100644 index 000000000..38343318a --- /dev/null +++ b/src/igl/mosek/mosek_quadprog.h @@ -0,0 +1,145 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MOSEK_MOSEK_QUADPROG_H +#define IGL_MOSEK_MOSEK_QUADPROG_H +#include "../igl_inline.h" +#include +#include +#include + + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + namespace mosek + { + struct MosekData + { + // Integer parameters + std::map intparam; + // Double parameters + std::map douparam; + // Default values + IGL_INLINE MosekData(); + }; + // Solve a convex quadratic optimization problem with linear and constant + // bounds, that is: + // + // Minimize: ½ * xT * Q⁰ * x + cT * x + cf + // + // Subject to: lc ≤ Ax ≤ uc + // lx ≤ x ≤ ux + // + // where we are trying to find the optimal vector of values x. + // + // Note: Q⁰ must be symmetric and the ½ is a convention of MOSEK + // + // Note: Because of how MOSEK accepts different parts of the system, Q + // should be stored in IJV (aka Coordinate) format and should only include + // entries in the lower triangle. A should be stored in Column compressed + // (aka Harwell Boeing) format. As described: + // http://netlib.org/linalg/html_templates/node92.html + // or + // http://en.wikipedia.org/wiki/Sparse_matrix + // #Compressed_sparse_column_.28CSC_or_CCS.29 + // + // + // Templates: + // Index type for index variables + // Scalar type for floating point variables (gets cast to double?) + // Input: + // n number of variables, i.e. size of x + // Qi vector of qnnz row indices of non-zeros in LOWER TRIANGLE ONLY of + // Q⁰ + // Qj vector of qnnz column indices of non-zeros in LOWER TRIANGLE ONLY + // of Q⁰ + // Qv vector of qnnz values of non-zeros in LOWER TRIANGLE ONLY of Q⁰, + // such that: + // + // Q⁰(Qi[k],Qj[k]) = Qv[k] for k ∈ [0,Qnnz-1], where Qnnz is the + // + // number of non-zeros in Q⁰ + // c (optional) vector of n values of c, transpose of coefficient row + // vector of linear terms, EMPTY means c == 0 + // cf (ignored) value of constant term in objective, 0 means cf == 0, so + // optional only in the sense that it is mandatory + // m number of constraints, therefore also number of rows in linear + // constraint coefficient matrix A, and in linear constraint bound + // vectors lc and uc + // Av vector of non-zero values of A, in column compressed order + // Ari vector of row indices corresponding to non-zero values of A, + // Acp vector of indices into Ari and Av of the first entry for each + // column of A, size(Acp) = (# columns of A) + 1 = n + 1 + // lc vector of m linear constraint lower bounds + // uc vector of m linear constraint upper bounds + // lx vector of n constant lower bounds + // ux vector of n constant upper bounds + // Output: + // x vector of size n to hold output of optimization + // Return: + // true only if optimization was successful with no errors + // + // Note: All indices are 0-based + // + template + IGL_INLINE bool mosek_quadprog( + const Index n, + /* mosek won't allow this to be const*/ std::vector & Qi, + /* mosek won't allow this to be const*/ std::vector & Qj, + /* mosek won't allow this to be const*/ std::vector & Qv, + const std::vector & c, + const Scalar cf, + const Index m, + /* mosek won't allow this to be const*/ std::vector & Av, + /* mosek won't allow this to be const*/ std::vector & Ari, + const std::vector & Acp, + const std::vector & lc, + const std::vector & uc, + const std::vector & lx, + const std::vector & ux, + MosekData & mosek_data, + std::vector & x); + // Wrapper with Eigen elements + // + // Inputs: + // Q n by n square quadratic coefficients matrix **only lower triangle + // is used**. + // c n-long vector of linear coefficients + // cf constant coefficient + // A m by n square linear coefficienst matrix of inequality constraints + // lc m-long vector of lower bounds for linear inequality constraints + // uc m-long vector of upper bounds for linear inequality constraints + // lx n-long vector of lower bounds + // ux n-long vector of upper bounds + // mosek_data parameters struct + // Outputs: + // x n-long solution vector + // Returns true only if optimization finishes without error + // + IGL_INLINE bool mosek_quadprog( + const Eigen::SparseMatrix & Q, + const Eigen::VectorXd & c, + const double cf, + const Eigen::SparseMatrix & A, + const Eigen::VectorXd & lc, + const Eigen::VectorXd & uc, + const Eigen::VectorXd & lx, + const Eigen::VectorXd & ux, + MosekData & mosek_data, + Eigen::VectorXd & x); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "mosek_quadprog.cpp" +#endif + +#endif diff --git a/src/igl/mvc.cpp b/src/igl/mvc.cpp new file mode 100644 index 000000000..a6fed49d8 --- /dev/null +++ b/src/igl/mvc.cpp @@ -0,0 +1,196 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "mvc.h" +#include +#include +#include + +// Broken Implementation +IGL_INLINE void igl::mvc(const Eigen::MatrixXd &V, const Eigen::MatrixXd &C, Eigen::MatrixXd &W) +{ + + // at least three control points + assert(C.rows()>2); + + // dimension of points + assert(C.cols() == 3 || C.cols() == 2); + assert(V.cols() == 3 || V.cols() == 2); + + // number of polygon points + int num = C.rows(); + + Eigen::MatrixXd V1,C1; + int i_prev, i_next; + + // check if either are 3D but really all z's are 0 + bool V_flat = (V.cols() == 3) && (std::sqrt( (V.col(3)).dot(V.col(3)) ) < 1e-10); + bool C_flat = (C.cols() == 3) && (std::sqrt( (C.col(3)).dot(C.col(3)) ) < 1e-10); + + // if both are essentially 2D then ignore z-coords + if((C.cols() == 2 || C_flat) && (V.cols() == 2 || V_flat)) + { + // ignore z coordinate + V1 = V.block(0,0,V.rows(),2); + C1 = C.block(0,0,C.rows(),2); + } + else + { + // give dummy z coordinate to either mesh or poly + if(V.rows() == 2) + { + V1 = Eigen::MatrixXd(V.rows(),3); + V1.block(0,0,V.rows(),2) = V; + } + else + V1 = V; + + if(C.rows() == 2) + { + C1 = Eigen::MatrixXd(C.rows(),3); + C1.block(0,0,C.rows(),2) = C; + } + else + C1 = C; + + // check that C is planar + // average normal around poly corners + + Eigen::Vector3d n = Eigen::Vector3d::Zero(); + // take centroid as point on plane + Eigen::Vector3d p = Eigen::Vector3d::Zero(); + for (int i = 0; i0)?(i-1):(num-1); + i_next = (i1e-10) + std::cerr<<"Distance from V to plane of C is large..."< solver = basis.colPivHouseholderQr(); + // Throw away coordinates in normal direction + V1 = solver.solve(V1.transpose()).transpose().block(0,0,V1.rows(),2); + C1 = solver.solve(C1.transpose()).transpose().block(0,0,C1.rows(),2); + + } + + // vectors from V to every C, where CmV(i,j,:) is the vector from domain + // vertex j to handle i + double EPS = 1e-10; + Eigen::MatrixXd WW = Eigen::MatrixXd(C1.rows(), V1.rows()); + Eigen::MatrixXd dist_C_V (C1.rows(), V1.rows()); + std::vector< std::pair > on_corner(0); + std::vector< std::pair > on_segment(0); + for (int i = 0; i0)?(i-1):(num-1); + i_next = (i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_MVC_H +#define IGL_MVC_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // MVC - MEAN VALUE COORDINATES + // + // mvc(V,C,W) + // + // Inputs: + // V #V x dim list of vertex positions (dim = 2 or dim = 3) + // C #C x dim list of polygon vertex positions in counter-clockwise order + // (dim = 2 or dim = 3) + // + // Outputs: + // W weights, #V by #C matrix of weights + // + // Known Bugs: implementation is listed as "Broken" + IGL_INLINE void mvc( + const Eigen::MatrixXd &V, + const Eigen::MatrixXd &C, + Eigen::MatrixXd &W); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "mvc.cpp" +#endif + +#endif diff --git a/src/igl/nchoosek.cpp b/src/igl/nchoosek.cpp new file mode 100644 index 000000000..88ab5449d --- /dev/null +++ b/src/igl/nchoosek.cpp @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti, Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "nchoosek.h" +#include +#include + +IGL_INLINE double igl::nchoosek(const int n, const int k) +{ + if(k>n/2) + { + return nchoosek(n,n-k); + }else if(k==1) + { + return n; + }else + { + double c = 1; + for(int i = 1;i<=k;i++) + { + c *= (((double)n-k+i)/((double)i)); + } + return std::round(c); + } +} + +template < typename DerivedV, typename DerivedU> +IGL_INLINE void igl::nchoosek( + const Eigen::MatrixBase & V, + const int k, + Eigen::PlainObjectBase & U) +{ + using namespace Eigen; + if(V.size() == 0) + { + U.resize(0,k); + return; + } + assert((V.cols() == 1 || V.rows() == 1) && "V must be a vector"); + U.resize(nchoosek(V.size(),k),k); + int running_i = 0; + int running_j = 0; + Matrix running(1,k); + int N = V.size(); + const std::function doCombs = + [&running,&N,&doCombs,&running_i,&running_j,&U,&V](int offset, int k) + { + if(k==0) + { + U.row(running_i) = running; + running_i++; + return; + } + for (int i = offset; i <= N - k; ++i) + { + running(running_j) = V(i); + running_j++; + doCombs(i+1,k-1); + running_j--; + } + }; + doCombs(0,k); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::nchoosek, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&); +#if EIGEN_VERSION_AT_LEAST(3,3,0) +#else +template void igl::nchoosek, Eigen::Matrix >, Eigen::Matrix >(Eigen::MatrixBase, Eigen::Matrix > > const&, int, Eigen::PlainObjectBase >&); +#endif + +#endif diff --git a/src/igl/nchoosek.h b/src/igl/nchoosek.h new file mode 100644 index 000000000..ec882e6c6 --- /dev/null +++ b/src/igl/nchoosek.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti, Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_NCHOOSEK +#define IGL_NCHOOSEK +#include "igl_inline.h" +#include "deprecated.h" +#include + +#include + +namespace igl +{ + // NCHOOSEK Like matlab's nchoosek. + // + // Inputs: + // n total number elements + // k size of sub-set to consider + // Returns number of k-size combinations out of the set [1,...,n] + IGL_INLINE double nchoosek(const int n, const int k); + // + // Inputs: + // V n-long vector of elements + // k size of sub-set to consider + // Outputs: + // U nchoosek by k long matrix where each row is a unique k-size + // combination + template < typename DerivedV, typename DerivedU> + IGL_INLINE void nchoosek( + const Eigen::MatrixBase & V, + const int k, + Eigen::PlainObjectBase & U); +} + + +#ifndef IGL_STATIC_LIBRARY +#include "nchoosek.cpp" +#endif + + +#endif /* defined(IGL_NCHOOSEK) */ diff --git a/src/igl/next_filename.cpp b/src/igl/next_filename.cpp new file mode 100644 index 000000000..b453a149a --- /dev/null +++ b/src/igl/next_filename.cpp @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "next_filename.h" +#include "STR.h" +#include "file_exists.h" +#include +#include + +bool igl::next_filename( + const std::string & prefix, + const int zeros, + const std::string & suffix, + std::string & next) +{ + using namespace std; + // O(n), for huge lists could at least find bounds with exponential search + // and then narrow with binary search O(log(n)) + int i = 0; + while(true) + { + next = STR(prefix << setfill('0') << setw(zeros)< 0 && i >= pow(10,zeros)) + { + return false; + } + } +} + diff --git a/src/igl/next_filename.h b/src/igl/next_filename.h new file mode 100644 index 000000000..2aa3094da --- /dev/null +++ b/src/igl/next_filename.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NEXT_FILENAME_H +#define IGL_NEXT_FILENAME_H +#include "igl_inline.h" +#include +namespace igl +{ + // Find the file with the first filename of the form + // "prefix%0[zeros]dsuffix" + // + // Inputs: + // prefix path to containing dir and filename prefix + // zeros number of leading zeros as if digit printed with printf + // suffix suffix of filename and extension (should included dot) + // Outputs: + // next path to next file + // Returns true if found, false if exceeding range in zeros + IGL_INLINE bool next_filename( + const std::string & prefix, + const int zeros, + const std::string & suffix, + std::string & next); +} + +#ifndef IGL_STATIC_LIBRARY +# include "next_filename.cpp" +#endif + +#endif + diff --git a/src/igl/normal_derivative.cpp b/src/igl/normal_derivative.cpp new file mode 100644 index 000000000..d943fe0e2 --- /dev/null +++ b/src/igl/normal_derivative.cpp @@ -0,0 +1,118 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "LinSpaced.h" +#include "normal_derivative.h" +#include "cotmatrix_entries.h" +#include "slice.h" +#include + +template < + typename DerivedV, + typename DerivedEle, + typename Scalar> +IGL_INLINE void igl::normal_derivative( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + Eigen::SparseMatrix& DD) +{ + using namespace Eigen; + using namespace std; + // Element simplex-size + const size_t ss = Ele.cols(); + assert( ((ss==3) || (ss==4)) && "Only triangles or tets"); + // cotangents + Matrix C; + cotmatrix_entries(V,Ele,C); + vector > IJV; + // Number of elements + const size_t m = Ele.rows(); + // Number of vertices + const size_t n = V.rows(); + switch(ss) + { + default: + assert(false); + return; + case 4: + { + const MatrixXi DDJ = + slice( + Ele, + (VectorXi(24)<< + 1,0,2,0,3,0,2,1,3,1,0,1,3,2,0,2,1,2,0,3,1,3,2,3).finished(), + 2); + MatrixXi DDI(m,24); + for(size_t f = 0;f<4;f++) + { + const auto & I = (igl::LinSpaced(m,0,m-1).array()+f*m).eval(); + for(size_t r = 0;r<6;r++) + { + DDI.col(f*6+r) = I; + } + } + const DiagonalMatrix S = + (Matrix(1,-1).template replicate<12,1>()).asDiagonal(); + Matrix DDV = + slice( + C, + (VectorXi(24)<< + 2,2,1,1,3,3,0,0,4,4,2,2,5,5,1,1,0,0,3,3,4,4,5,5).finished(), + 2); + DDV *= S; + + IJV.reserve(DDV.size()); + for(size_t f = 0;f<6*4;f++) + { + for(size_t e = 0;e(DDI(e,f),DDJ(e,f),DDV(e,f))); + } + } + DD.resize(m*4,n); + DD.setFromTriplets(IJV.begin(),IJV.end()); + break; + } + case 3: + { + const MatrixXi DDJ = + slice(Ele,(VectorXi(12)<<2,0,1,0,0,1,2,1,1,2,0,2).finished(),2); + MatrixXi DDI(m,12); + for(size_t f = 0;f<3;f++) + { + const auto & I = (igl::LinSpaced(m,0,m-1).array()+f*m).eval(); + for(size_t r = 0;r<4;r++) + { + DDI.col(f*4+r) = I; + } + } + const DiagonalMatrix S = + (Matrix(1,-1).template replicate<6,1>()).asDiagonal(); + Matrix DDV = + slice(C,(VectorXi(12)<<1,1,2,2,2,2,0,0,0,0,1,1).finished(),2); + DDV *= S; + + IJV.reserve(DDV.size()); + for(size_t f = 0;f<12;f++) + { + for(size_t e = 0;e(DDI(e,f),DDJ(e,f),DDV(e,f))); + } + } + DD.resize(m*3,n); + DD.setFromTriplets(IJV.begin(),IJV.end()); + break; + } + } + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::normal_derivative, Eigen::Matrix, double>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/normal_derivative.h b/src/igl/normal_derivative.h new file mode 100644 index 000000000..861b55555 --- /dev/null +++ b/src/igl/normal_derivative.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NORMAL_DERIVATIVE_H +#define IGL_NORMAL_DERIVATIVE_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + // NORMAL_DERIVATIVE Computes the directional derivative **normal** to + // **all** (half-)edges of a triangle mesh (not just boundary edges). These + // are integrated along the edge: they're the per-face constant gradient dot + // the rotated edge vector (unit rotated edge vector for direction then + // magnitude for integration). + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3|4 list of triangle|tetrahedron indices into V + // Outputs: + // DD #F*3|4 by #V sparse matrix representing operator to compute + // directional derivative with respect to each facet of each element. + // + template < + typename DerivedV, + typename DerivedEle, + typename Scalar> + IGL_INLINE void normal_derivative( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & Ele, + Eigen::SparseMatrix& DD); +} + +#ifndef IGL_STATIC_LIBRARY +# include "normal_derivative.cpp" +#endif + +#endif + diff --git a/src/igl/normalize_quat.cpp b/src/igl/normalize_quat.cpp new file mode 100644 index 000000000..f110d6b75 --- /dev/null +++ b/src/igl/normalize_quat.cpp @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "normalize_quat.h" + +#include "EPS.h" +#include + +template +IGL_INLINE bool igl::normalize_quat( + const Q_type *q, + Q_type *out) +{ + // Get length + Q_type len = sqrt( + q[0]*q[0]+ + q[1]*q[1]+ + q[2]*q[2]+ + q[3]*q[3]); + + // Noramlize each coordinate + out[0] = q[0]/len; + out[1] = q[1]/len; + out[2] = q[2]/len; + out[3] = q[3]/len; + + // Test whether length was below Epsilon + return (len > igl::EPS()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::normalize_quat(double const*, double*); +// generated by autoexplicit.sh +template bool igl::normalize_quat(float const*, float*); +#endif diff --git a/src/igl/normalize_quat.h b/src/igl/normalize_quat.h new file mode 100644 index 000000000..e97759b17 --- /dev/null +++ b/src/igl/normalize_quat.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NORMALIZE_QUAT_H +#define IGL_NORMALIZE_QUAT_H +#include "igl_inline.h" + +namespace igl +{ + // Normalize a quaternion + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // q input quaternion + // Outputs: + // out result of normalization, allowed to be same as q + // Returns true on success, false if len(q) < EPS + template + IGL_INLINE bool normalize_quat( + const Q_type *q, + Q_type *out); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "normalize_quat.cpp" +#endif + +#endif diff --git a/src/igl/normalize_row_lengths.cpp b/src/igl/normalize_row_lengths.cpp new file mode 100644 index 000000000..3d7e566e8 --- /dev/null +++ b/src/igl/normalize_row_lengths.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "normalize_row_lengths.h" + +template +IGL_INLINE void igl::normalize_row_lengths( + const Eigen::PlainObjectBase& A, + Eigen::PlainObjectBase & B) +{ + // Resize output + B.resizeLike(A); + + // loop over rows + for(int i = 0; i < A.rows();i++) + { + B.row(i) = A.row(i).normalized(); + } + //// Or just: + //B = A; + //B.rowwise().normalize(); +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::normalize_row_lengths >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::normalize_row_lengths >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::normalize_row_lengths >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/normalize_row_lengths.h b/src/igl/normalize_row_lengths.h new file mode 100644 index 000000000..c305c0f65 --- /dev/null +++ b/src/igl/normalize_row_lengths.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NORMALIZE_ROW_LENGTHS_H +#define IGL_NORMALIZE_ROW_LENGTHS_H +#include "igl_inline.h" +#include + +// History: +// March 24, 2012: Alec changed function name from normalize_rows to +// normalize_row_lengths to avoid confusion with normalize_row_sums + +namespace igl +{ + // Obsolete: just use A.rowwise().normalize() or B=A.rowwise().normalized(); + // + // Normalize the rows in A so that their lengths are each 1 and place the new + // entries in B + // Inputs: + // A #rows by k input matrix + // Outputs: + // B #rows by k input matrix, can be the same as A + template + IGL_INLINE void normalize_row_lengths( + const Eigen::PlainObjectBase& A, + Eigen::PlainObjectBase & B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "normalize_row_lengths.cpp" +#endif + +#endif diff --git a/src/igl/normalize_row_sums.cpp b/src/igl/normalize_row_sums.cpp new file mode 100644 index 000000000..1204e073d --- /dev/null +++ b/src/igl/normalize_row_sums.cpp @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "normalize_row_sums.h" + +template +IGL_INLINE void igl::normalize_row_sums( + const Eigen::MatrixBase& A, + Eigen::MatrixBase & B) +{ +#ifndef NDEBUG + // loop over rows + for(int i = 0; i < A.rows();i++) + { + typename DerivedB::Scalar sum = A.row(i).sum(); + assert(sum != 0); + } +#endif + B = (A.array().colwise() / A.rowwise().sum().array()).eval(); +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::normalize_row_sums, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase >&); +template void igl::normalize_row_sums, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase >&); +#endif diff --git a/src/igl/normalize_row_sums.h b/src/igl/normalize_row_sums.h new file mode 100644 index 000000000..a832e82a6 --- /dev/null +++ b/src/igl/normalize_row_sums.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NORMALIZE_ROW_SUMS_H +#define IGL_NORMALIZE_ROW_SUMS_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Normalize the rows in A so that their sums are each 1 and place the new + // entries in B + // Inputs: + // A #rows by k input matrix + // Outputs: + // B #rows by k input matrix, can be the same as A + // + // Note: This is just calling an Eigen one-liner. + template + IGL_INLINE void normalize_row_sums( + const Eigen::MatrixBase& A, + Eigen::MatrixBase & B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "normalize_row_sums.cpp" +#endif + +#endif diff --git a/src/igl/null.cpp b/src/igl/null.cpp new file mode 100644 index 000000000..b2291f454 --- /dev/null +++ b/src/igl/null.cpp @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "null.h" +#include "EPS.h" + +template +IGL_INLINE void igl::null( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & N) +{ + using namespace Eigen; + typedef typename DerivedA::Scalar Scalar; + JacobiSVD svd(A, ComputeFullV); + svd.setThreshold(A.cols() * svd.singularValues().maxCoeff() * EPS()); + N = svd.matrixV().rightCols(A.cols()-svd.rank()); +} diff --git a/src/igl/null.h b/src/igl/null.h new file mode 100644 index 000000000..2349217fb --- /dev/null +++ b/src/igl/null.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_NULL_H +#define IGL_NULL_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Like MATLAB's null + // + // Compute a basis for the null space for the given matrix A: the columns of + // the output N form a basis for the space orthogonal to that spanned by the + // rows of A. + // + // Inputs: + // A m by n matrix + // Outputs: + // N n by r matrix, where r is the row rank of A + template + IGL_INLINE void null( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & N); +} + +#ifndef IGL_STATIC_LIBRARY +# include "null.cpp" +#endif + +#endif diff --git a/src/igl/octree.cpp b/src/igl/octree.cpp new file mode 100644 index 000000000..6ed95d846 --- /dev/null +++ b/src/igl/octree.cpp @@ -0,0 +1,176 @@ +#include "octree.h" +#include +#include + +namespace igl { + template + IGL_INLINE void octree(const Eigen::MatrixBase& P, + std::vector > & point_indices, + Eigen::PlainObjectBase& CH, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& W) + { + + + + const int MAX_DEPTH = 30000; + + typedef typename DerivedCH::Scalar ChildrenType; + typedef typename DerivedCN::Scalar CentersType; + typedef typename DerivedW::Scalar WidthsType; + typedef Eigen::Matrix Vector8i; + typedef Eigen::Matrix RowVector3PType; + typedef Eigen::Matrix RowVector3CentersType; + + std::vector, + Eigen::aligned_allocator > > children; + std::vector, + Eigen::aligned_allocator > > centers; + std::vector widths; + + auto get_octant = [](RowVector3PType location, + RowVector3CentersType center){ + // We use a binary numbering of children. Treating the parent cell's + // center as the origin, we number the octants in the following manner: + // The first bit is 1 iff the octant's x coordinate is positive + // The second bit is 1 iff the octant's y coordinate is positive + // The third bit is 1 iff the octant's z coordinate is positive + // + // For example, the octant with negative x, positive y, positive z is: + // 110 binary = 6 decimal + IndexType index = 0; + if( location(0) >= center(0)){ + index = index + 1; + } + if( location(1) >= center(1)){ + index = index + 2; + } + if( location(2) >= center(2)){ + index = index + 4; + } + return index; + }; + + + std::function< RowVector3CentersType(const RowVector3CentersType, + const CentersType, + const ChildrenType) > + translate_center = + [](const RowVector3CentersType & parent_center, + const CentersType h, + const ChildrenType child_index){ + RowVector3CentersType change_vector; + change_vector << -h,-h,-h; + + //positive x chilren are 1,3,4,7 + if(child_index % 2){ + change_vector(0) = h; + } + //positive y children are 2,3,6,7 + if(child_index == 2 || child_index == 3 || + child_index == 6 || child_index == 7){ + change_vector(1) = h; + } + //positive z children are 4,5,6,7 + if(child_index > 3){ + change_vector(2) = h; + } + RowVector3CentersType output = parent_center + change_vector; + return output; + }; + + // How many cells do we have so far? + IndexType m = 0; + + // Useful list of number 0..7 + const Vector8i zero_to_seven = (Vector8i()<<0,1,2,3,4,5,6,7).finished(); + const Vector8i neg_ones = (Vector8i()<<-1,-1,-1,-1,-1,-1,-1,-1).finished(); + + std::function< void(const ChildrenType, const int) > helper; + helper = [&helper,&translate_center,&get_octant,&m, + &zero_to_seven,&neg_ones,&P, + &point_indices,&children,¢ers,&widths] + (const ChildrenType index, const int depth)-> void + { + if(point_indices.at(index).size() > 1 && depth < MAX_DEPTH){ + //give the parent access to the children + children.at(index) = zero_to_seven.array() + m; + //make the children's data in our arrays + + //Add the children to the lists, as default children + CentersType h = widths.at(index)/2; + RowVector3CentersType curr_center = centers.at(index); + + + for(ChildrenType i = 0; i < 8; i++){ + children.emplace_back(neg_ones); + point_indices.emplace_back(std::vector()); + centers.emplace_back(translate_center(curr_center,h,i)); + widths.emplace_back(h); + } + + + //Split up the points into the corresponding children + for(int j = 0; j < point_indices.at(index).size(); j++){ + IndexType curr_point_index = point_indices.at(index).at(j); + IndexType cell_of_curr_point = + get_octant(P.row(curr_point_index),curr_center)+m; + point_indices.at(cell_of_curr_point).emplace_back(curr_point_index); + } + + //Now increase m + m += 8; + + + // Look ma, I'm calling myself. + for(int i = 0; i < 8; i++){ + helper(children.at(index)(i),depth+1); + } + } + }; + + { + std::vector all(P.rows()); + for(IndexType i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/ + +#ifndef IGL_OCTREE +#define IGL_OCTREE +#include "igl_inline.h" +#include +#include + + + + +namespace igl +{ + // Given a set of 3D points P, generate data structures for a pointerless + // octree. Each cell stores its points, children, center location and width. + // Our octree is not dense. We use the following rule: if the current cell + // has any number of points, it will have all 8 children. A leaf cell will + // have -1's as its list of child indices. + // + // We use a binary numbering of children. Treating the parent cell's center + // as the origin, we number the octants in the following manner: + // The first bit is 1 iff the octant's x coordinate is positive + // The second bit is 1 iff the octant's y coordinate is positive + // The third bit is 1 iff the octant's z coordinate is positive + // + // For example, the octant with negative x, positive y, positive z is: + // 110 binary = 6 decimal + // + // Inputs: + // P #P by 3 list of point locations + // + // Outputs: + // point_indices a vector of vectors, where the ith entry is a vector of + // the indices into P that are the ith octree cell's points + // CH #OctreeCells by 8, where the ith row is the indices of + // the ith octree cell's children + // CN #OctreeCells by 3, where the ith row is a 3d row vector + // representing the position of the ith cell's center + // W #OctreeCells, a vector where the ith entry is the width + // of the ith octree cell + // + template + IGL_INLINE void octree(const Eigen::MatrixBase& P, + std::vector > & point_indices, + Eigen::PlainObjectBase& CH, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& W); +} + +#ifndef IGL_STATIC_LIBRARY +# include "octree.cpp" +#endif + +#endif + diff --git a/src/igl/on_boundary.cpp b/src/igl/on_boundary.cpp new file mode 100644 index 000000000..3fb70ec59 --- /dev/null +++ b/src/igl/on_boundary.cpp @@ -0,0 +1,141 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "on_boundary.h" + +// IGL includes +#include "sort.h" +#include "face_occurrences.h" + +// STL includes + +template +IGL_INLINE void igl::on_boundary( + const std::vector > & T, + std::vector & I, + std::vector > & C) +{ + using namespace std; + if(T.empty()) + { + I.clear(); + C.clear(); + return; + } + + switch(T[0].size()) + { + case 3: + { + // Get a list of all faces + vector > F(T.size()*3,vector(2)); + // Gather faces, loop over tets + for(int i = 0; i< (int)T.size();i++) + { + assert(T[i].size() == 3); + // get face in correct order + F[i*3+0][0] = T[i][1]; + F[i*3+0][1] = T[i][2]; + F[i*3+1][0] = T[i][2]; + F[i*3+1][1] = T[i][0]; + F[i*3+2][0] = T[i][0]; + F[i*3+2][1] = T[i][1]; + } + // Counts + vector FC; + face_occurrences(F,FC); + C.resize(T.size(),vector(3)); + I.resize(T.size(),false); + for(int i = 0; i< (int)T.size();i++) + { + for(int j = 0;j<3;j++) + { + assert(FC[i*3+j] == 2 || FC[i*3+j] == 1); + C[i][j] = FC[i*3+j]==1; + // if any are on boundary set to true + I[i] = I[i] || C[i][j]; + } + } + return; + } + case 4: + { + // Get a list of all faces + vector > F(T.size()*4,vector(3)); + // Gather faces, loop over tets + for(int i = 0; i< (int)T.size();i++) + { + assert(T[i].size() == 4); + // get face in correct order + F[i*4+0][0] = T[i][1]; + F[i*4+0][1] = T[i][3]; + F[i*4+0][2] = T[i][2]; + // get face in correct order + F[i*4+1][0] = T[i][0]; + F[i*4+1][1] = T[i][2]; + F[i*4+1][2] = T[i][3]; + // get face in correct order + F[i*4+2][0] = T[i][0]; + F[i*4+2][1] = T[i][3]; + F[i*4+2][2] = T[i][1]; + // get face in correct order + F[i*4+3][0] = T[i][0]; + F[i*4+3][1] = T[i][1]; + F[i*4+3][2] = T[i][2]; + } + // Counts + vector FC; + face_occurrences(F,FC); + C.resize(T.size(),vector(4)); + I.resize(T.size(),false); + for(int i = 0; i< (int)T.size();i++) + { + for(int j = 0;j<4;j++) + { + assert(FC[i*4+j] == 2 || FC[i*4+j] == 1); + C[i][j] = FC[i*4+j]==1; + // if any are on boundary set to true + I[i] = I[i] || C[i][j]; + } + } + return; + } + } + + +} + +#include "list_to_matrix.h" +#include "matrix_to_list.h" + +template +IGL_INLINE void igl::on_boundary( + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& I, + Eigen::PlainObjectBase& C) +{ + assert(T.cols() == 0 || T.cols() == 4 || T.cols() == 3); + using namespace std; + using namespace Eigen; + // Cop out: use vector of vectors version + vector > vT; + matrix_to_list(T,vT); + vector vI; + vector > vC; + on_boundary(vT,vI,vC); + list_to_matrix(vI,I); + list_to_matrix(vC,C); +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::on_boundary, Eigen::Array, Eigen::Array >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::on_boundary, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::on_boundary, Eigen::Array, Eigen::Array >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/on_boundary.h b/src/igl/on_boundary.h new file mode 100644 index 000000000..c4dab1706 --- /dev/null +++ b/src/igl/on_boundary.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ON_BOUNDARY_H +#define IGL_ON_BOUNDARY_H +#include "igl_inline.h" +#include + +#include + +namespace igl +{ + // ON_BOUNDARY Determine boundary facets of mesh elements stored in T + // + // Templates: + // IntegerT integer-value: i.e. int + // IntegerF integer-value: i.e. int + // Input: + // T triangle|tetrahedron index list, m by 3|4, where m is the number of + // elements + // Output: + // I m long list of bools whether tet is on boundary + // C m by 3|4 list of bools whether opposite facet is on boundary + // + template + IGL_INLINE void on_boundary( + const std::vector > & T, + std::vector & I, + std::vector > & C); + // Templates: + // DerivedT integer-value: i.e. from MatrixXi + // DerivedI bool-value: i.e. from MatrixXi + // DerivedC bool-value: i.e. from MatrixXi + template + IGL_INLINE void on_boundary( + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& I, + Eigen::PlainObjectBase& C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "on_boundary.cpp" +#endif + +#endif + + diff --git a/src/igl/opengl/MeshGL.cpp b/src/igl/opengl/MeshGL.cpp new file mode 100644 index 000000000..f49924324 --- /dev/null +++ b/src/igl/opengl/MeshGL.cpp @@ -0,0 +1,313 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "MeshGL.h" +#include "bind_vertex_attrib_array.h" +#include "create_shader_program.h" +#include "destroy_shader_program.h" +#include + +IGL_INLINE void igl::opengl::MeshGL::init_buffers() +{ + // Mesh: Vertex Array Object & Buffer objects + glGenVertexArrays(1, &vao_mesh); + glBindVertexArray(vao_mesh); + glGenBuffers(1, &vbo_V); + glGenBuffers(1, &vbo_V_normals); + glGenBuffers(1, &vbo_V_ambient); + glGenBuffers(1, &vbo_V_diffuse); + glGenBuffers(1, &vbo_V_specular); + glGenBuffers(1, &vbo_V_uv); + glGenBuffers(1, &vbo_F); + glGenTextures(1, &vbo_tex); + + // Line overlay + glGenVertexArrays(1, &vao_overlay_lines); + glBindVertexArray(vao_overlay_lines); + glGenBuffers(1, &vbo_lines_F); + glGenBuffers(1, &vbo_lines_V); + glGenBuffers(1, &vbo_lines_V_colors); + + // Point overlay + glGenVertexArrays(1, &vao_overlay_points); + glBindVertexArray(vao_overlay_points); + glGenBuffers(1, &vbo_points_F); + glGenBuffers(1, &vbo_points_V); + glGenBuffers(1, &vbo_points_V_colors); + + dirty = MeshGL::DIRTY_ALL; +} + +IGL_INLINE void igl::opengl::MeshGL::free_buffers() +{ + if (is_initialized) + { + glDeleteVertexArrays(1, &vao_mesh); + glDeleteVertexArrays(1, &vao_overlay_lines); + glDeleteVertexArrays(1, &vao_overlay_points); + + glDeleteBuffers(1, &vbo_V); + glDeleteBuffers(1, &vbo_V_normals); + glDeleteBuffers(1, &vbo_V_ambient); + glDeleteBuffers(1, &vbo_V_diffuse); + glDeleteBuffers(1, &vbo_V_specular); + glDeleteBuffers(1, &vbo_V_uv); + glDeleteBuffers(1, &vbo_F); + glDeleteBuffers(1, &vbo_lines_F); + glDeleteBuffers(1, &vbo_lines_V); + glDeleteBuffers(1, &vbo_lines_V_colors); + glDeleteBuffers(1, &vbo_points_F); + glDeleteBuffers(1, &vbo_points_V); + glDeleteBuffers(1, &vbo_points_V_colors); + + glDeleteTextures(1, &vbo_tex); + } +} + +IGL_INLINE void igl::opengl::MeshGL::bind_mesh() +{ + glBindVertexArray(vao_mesh); + glUseProgram(shader_mesh); + bind_vertex_attrib_array(shader_mesh,"position", vbo_V, V_vbo, dirty & MeshGL::DIRTY_POSITION); + bind_vertex_attrib_array(shader_mesh,"normal", vbo_V_normals, V_normals_vbo, dirty & MeshGL::DIRTY_NORMAL); + bind_vertex_attrib_array(shader_mesh,"Ka", vbo_V_ambient, V_ambient_vbo, dirty & MeshGL::DIRTY_AMBIENT); + bind_vertex_attrib_array(shader_mesh,"Kd", vbo_V_diffuse, V_diffuse_vbo, dirty & MeshGL::DIRTY_DIFFUSE); + bind_vertex_attrib_array(shader_mesh,"Ks", vbo_V_specular, V_specular_vbo, dirty & MeshGL::DIRTY_SPECULAR); + bind_vertex_attrib_array(shader_mesh,"texcoord", vbo_V_uv, V_uv_vbo, dirty & MeshGL::DIRTY_UV); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_F); + if (dirty & MeshGL::DIRTY_FACE) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*F_vbo.size(), F_vbo.data(), GL_DYNAMIC_DRAW); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, vbo_tex); + if (dirty & MeshGL::DIRTY_TEXTURE) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_u, tex_v, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.data()); + } + glUniform1i(glGetUniformLocation(shader_mesh,"tex"), 0); + dirty &= ~MeshGL::DIRTY_MESH; +} + +IGL_INLINE void igl::opengl::MeshGL::bind_overlay_lines() +{ + bool is_dirty = dirty & MeshGL::DIRTY_OVERLAY_LINES; + + glBindVertexArray(vao_overlay_lines); + glUseProgram(shader_overlay_lines); + bind_vertex_attrib_array(shader_overlay_lines,"position", vbo_lines_V, lines_V_vbo, is_dirty); + bind_vertex_attrib_array(shader_overlay_lines,"color", vbo_lines_V_colors, lines_V_colors_vbo, is_dirty); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_lines_F); + if (is_dirty) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*lines_F_vbo.size(), lines_F_vbo.data(), GL_DYNAMIC_DRAW); + + dirty &= ~MeshGL::DIRTY_OVERLAY_LINES; +} + +IGL_INLINE void igl::opengl::MeshGL::bind_overlay_points() +{ + bool is_dirty = dirty & MeshGL::DIRTY_OVERLAY_POINTS; + + glBindVertexArray(vao_overlay_points); + glUseProgram(shader_overlay_points); + bind_vertex_attrib_array(shader_overlay_points,"position", vbo_points_V, points_V_vbo, is_dirty); + bind_vertex_attrib_array(shader_overlay_points,"color", vbo_points_V_colors, points_V_colors_vbo, is_dirty); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_points_F); + if (is_dirty) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*points_F_vbo.size(), points_F_vbo.data(), GL_DYNAMIC_DRAW); + + dirty &= ~MeshGL::DIRTY_OVERLAY_POINTS; +} + +IGL_INLINE void igl::opengl::MeshGL::draw_mesh(bool solid) +{ + glPolygonMode(GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE); + + /* Avoid Z-buffer fighting between filled triangles & wireframe lines */ + if (solid) + { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0, 1.0); + } + glDrawElements(GL_TRIANGLES, 3*F_vbo.rows(), GL_UNSIGNED_INT, 0); + + glDisable(GL_POLYGON_OFFSET_FILL); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +} + +IGL_INLINE void igl::opengl::MeshGL::draw_overlay_lines() +{ + glDrawElements(GL_LINES, lines_F_vbo.rows(), GL_UNSIGNED_INT, 0); +} + +IGL_INLINE void igl::opengl::MeshGL::draw_overlay_points() +{ + glDrawElements(GL_POINTS, points_F_vbo.rows(), GL_UNSIGNED_INT, 0); +} + +IGL_INLINE void igl::opengl::MeshGL::init() +{ + if(is_initialized) + { + return; + } + is_initialized = true; + std::string mesh_vertex_shader_string = +R"(#version 150 + uniform mat4 view; + uniform mat4 proj; + uniform mat4 normal_matrix; + in vec3 position; + in vec3 normal; + out vec3 position_eye; + out vec3 normal_eye; + in vec4 Ka; + in vec4 Kd; + in vec4 Ks; + in vec2 texcoord; + out vec2 texcoordi; + out vec4 Kai; + out vec4 Kdi; + out vec4 Ksi; + + void main() + { + position_eye = vec3 (view * vec4 (position, 1.0)); + normal_eye = vec3 (normal_matrix * vec4 (normal, 0.0)); + normal_eye = normalize(normal_eye); + gl_Position = proj * vec4 (position_eye, 1.0); //proj * view * vec4(position, 1.0);" + Kai = Ka; + Kdi = Kd; + Ksi = Ks; + texcoordi = texcoord; + } +)"; + + std::string mesh_fragment_shader_string = +R"(#version 150 + uniform mat4 view; + uniform mat4 proj; + uniform vec4 fixed_color; + in vec3 position_eye; + in vec3 normal_eye; + uniform vec3 light_position_eye; + vec3 Ls = vec3 (1, 1, 1); + vec3 Ld = vec3 (1, 1, 1); + vec3 La = vec3 (1, 1, 1); + in vec4 Ksi; + in vec4 Kdi; + in vec4 Kai; + in vec2 texcoordi; + uniform sampler2D tex; + uniform float specular_exponent; + uniform float lighting_factor; + uniform float texture_factor; + out vec4 outColor; + void main() + { + vec3 Ia = La * vec3(Kai); // ambient intensity + + vec3 vector_to_light_eye = light_position_eye - position_eye; + vec3 direction_to_light_eye = normalize (vector_to_light_eye); + float dot_prod = dot (direction_to_light_eye, normalize(normal_eye)); + float clamped_dot_prod = max (dot_prod, 0.0); + vec3 Id = Ld * vec3(Kdi) * clamped_dot_prod; // Diffuse intensity + + vec3 reflection_eye = reflect (-direction_to_light_eye, normalize(normal_eye)); + vec3 surface_to_viewer_eye = normalize (-position_eye); + float dot_prod_specular = dot (reflection_eye, surface_to_viewer_eye); + dot_prod_specular = float(abs(dot_prod)==dot_prod) * max (dot_prod_specular, 0.0); + float specular_factor = pow (dot_prod_specular, specular_exponent); + vec3 Is = Ls * vec3(Ksi) * specular_factor; // specular intensity + vec4 color = vec4(lighting_factor * (Is + Id) + Ia + (1.0-lighting_factor) * vec3(Kdi),(Kai.a+Ksi.a+Kdi.a)/3); + outColor = mix(vec4(1,1,1,1), texture(tex, texcoordi), texture_factor) * color; + if (fixed_color != vec4(0.0)) outColor = fixed_color; + } +)"; + + std::string overlay_vertex_shader_string = +R"(#version 150 + uniform mat4 view; + uniform mat4 proj; + in vec3 position; + in vec3 color; + out vec3 color_frag; + + void main() + { + gl_Position = proj * view * vec4 (position, 1.0); + color_frag = color; + } +)"; + + std::string overlay_fragment_shader_string = +R"(#version 150 + in vec3 color_frag; + out vec4 outColor; + void main() + { + outColor = vec4(color_frag, 1.0); + } +)"; + + std::string overlay_point_fragment_shader_string = +R"(#version 150 + in vec3 color_frag; + out vec4 outColor; + void main() + { + if (length(gl_PointCoord - vec2(0.5)) > 0.5) + discard; + outColor = vec4(color_frag, 1.0); + } +)"; + + init_buffers(); + create_shader_program( + mesh_vertex_shader_string, + mesh_fragment_shader_string, + {}, + shader_mesh); + create_shader_program( + overlay_vertex_shader_string, + overlay_fragment_shader_string, + {}, + shader_overlay_lines); + create_shader_program( + overlay_vertex_shader_string, + overlay_point_fragment_shader_string, + {}, + shader_overlay_points); +} + +IGL_INLINE void igl::opengl::MeshGL::free() +{ + const auto free = [](GLuint & id) + { + if(id) + { + destroy_shader_program(id); + id = 0; + } + }; + + if (is_initialized) + { + free(shader_mesh); + free(shader_overlay_lines); + free(shader_overlay_points); + free_buffers(); + } +} diff --git a/src/igl/opengl/MeshGL.h b/src/igl/opengl/MeshGL.h new file mode 100644 index 000000000..74c358903 --- /dev/null +++ b/src/igl/opengl/MeshGL.h @@ -0,0 +1,133 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_MESHGL_H +#define IGL_OPENGL_MESHGL_H + +// Coverts mesh data inside a igl::ViewerData class in an OpenGL +// compatible format The class includes a shader and the opengl calls to plot +// the data + +#include +#include + +namespace igl +{ +namespace opengl +{ + +class MeshGL +{ +public: + typedef unsigned int GLuint; + + enum DirtyFlags + { + DIRTY_NONE = 0x0000, + DIRTY_POSITION = 0x0001, + DIRTY_UV = 0x0002, + DIRTY_NORMAL = 0x0004, + DIRTY_AMBIENT = 0x0008, + DIRTY_DIFFUSE = 0x0010, + DIRTY_SPECULAR = 0x0020, + DIRTY_TEXTURE = 0x0040, + DIRTY_FACE = 0x0080, + DIRTY_MESH = 0x00FF, + DIRTY_OVERLAY_LINES = 0x0100, + DIRTY_OVERLAY_POINTS = 0x0200, + DIRTY_ALL = 0x03FF + }; + + bool is_initialized = false; + GLuint vao_mesh; + GLuint vao_overlay_lines; + GLuint vao_overlay_points; + GLuint shader_mesh; + GLuint shader_overlay_lines; + GLuint shader_overlay_points; + + GLuint vbo_V; // Vertices of the current mesh (#V x 3) + GLuint vbo_V_uv; // UV coordinates for the current mesh (#V x 2) + GLuint vbo_V_normals; // Vertices of the current mesh (#V x 3) + GLuint vbo_V_ambient; // Ambient material (#V x 3) + GLuint vbo_V_diffuse; // Diffuse material (#V x 3) + GLuint vbo_V_specular; // Specular material (#V x 3) + + GLuint vbo_F; // Faces of the mesh (#F x 3) + GLuint vbo_tex; // Texture + + GLuint vbo_lines_F; // Indices of the line overlay + GLuint vbo_lines_V; // Vertices of the line overlay + GLuint vbo_lines_V_colors; // Color values of the line overlay + GLuint vbo_points_F; // Indices of the point overlay + GLuint vbo_points_V; // Vertices of the point overlay + GLuint vbo_points_V_colors; // Color values of the point overlay + + // Temporary copy of the content of each VBO + typedef Eigen::Matrix RowMatrixXf; + RowMatrixXf V_vbo; + RowMatrixXf V_normals_vbo; + RowMatrixXf V_ambient_vbo; + RowMatrixXf V_diffuse_vbo; + RowMatrixXf V_specular_vbo; + RowMatrixXf V_uv_vbo; + RowMatrixXf lines_V_vbo; + RowMatrixXf lines_V_colors_vbo; + RowMatrixXf points_V_vbo; + RowMatrixXf points_V_colors_vbo; + + int tex_u; + int tex_v; + Eigen::Matrix tex; + + Eigen::Matrix F_vbo; + Eigen::Matrix lines_F_vbo; + Eigen::Matrix points_F_vbo; + + // Marks dirty buffers that need to be uploaded to OpenGL + uint32_t dirty; + + // Initialize shaders and buffers + IGL_INLINE void init(); + + // Release all resources + IGL_INLINE void free(); + + // Create a new set of OpenGL buffer objects + IGL_INLINE void init_buffers(); + + // Bind the underlying OpenGL buffer objects for subsequent mesh draw calls + IGL_INLINE void bind_mesh(); + + /// Draw the currently buffered mesh (either solid or wireframe) + IGL_INLINE void draw_mesh(bool solid); + + // Bind the underlying OpenGL buffer objects for subsequent line overlay draw calls + IGL_INLINE void bind_overlay_lines(); + + /// Draw the currently buffered line overlay + IGL_INLINE void draw_overlay_lines(); + + // Bind the underlying OpenGL buffer objects for subsequent point overlay draw calls + IGL_INLINE void bind_overlay_points(); + + /// Draw the currently buffered point overlay + IGL_INLINE void draw_overlay_points(); + + // Release the OpenGL buffer objects + IGL_INLINE void free_buffers(); + +}; + +} +} + +#ifndef IGL_STATIC_LIBRARY +# include "MeshGL.cpp" +#endif + +#endif diff --git a/src/igl/opengl/ViewerCore.cpp b/src/igl/opengl/ViewerCore.cpp new file mode 100644 index 000000000..fc5f4664b --- /dev/null +++ b/src/igl/opengl/ViewerCore.cpp @@ -0,0 +1,391 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "ViewerCore.h" +#include "gl.h" +#include "../quat_to_mat.h" +#include "../snap_to_fixed_up.h" +#include "../look_at.h" +#include "../frustum.h" +#include "../ortho.h" +#include "../massmatrix.h" +#include "../barycenter.h" +#include "../PI.h" +#include +#include + +IGL_INLINE void igl::opengl::ViewerCore::align_camera_center( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F) +{ + if(V.rows() == 0) + return; + + get_scale_and_shift_to_fit_mesh(V,F,camera_base_zoom,camera_base_translation); + // Rather than crash on empty mesh... + if(V.size() > 0) + { + object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm(); + } +} + +IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + float& zoom, + Eigen::Vector3f& shift) +{ + if (V.rows() == 0) + return; + + Eigen::MatrixXd BC; + if (F.rows() <= 1) + { + BC = V; + } else + { + igl::barycenter(V,F,BC); + } + return get_scale_and_shift_to_fit_mesh(BC,zoom,shift); +} + +IGL_INLINE void igl::opengl::ViewerCore::align_camera_center( + const Eigen::MatrixXd& V) +{ + if(V.rows() == 0) + return; + + get_scale_and_shift_to_fit_mesh(V,camera_base_zoom,camera_base_translation); + // Rather than crash on empty mesh... + if(V.size() > 0) + { + object_scale = (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm(); + } +} + +IGL_INLINE void igl::opengl::ViewerCore::get_scale_and_shift_to_fit_mesh( + const Eigen::MatrixXd& V, + float& zoom, + Eigen::Vector3f& shift) +{ + if (V.rows() == 0) + return; + + auto min_point = V.colwise().minCoeff(); + auto max_point = V.colwise().maxCoeff(); + auto centroid = (0.5*(min_point + max_point)).eval(); + shift.setConstant(0); + shift.head(centroid.size()) = -centroid.cast(); + zoom = 2.0 / (max_point-min_point).array().abs().maxCoeff(); +} + + +IGL_INLINE void igl::opengl::ViewerCore::clear_framebuffers() +{ + glClearColor(background_color[0], + background_color[1], + background_color[2], + background_color[3]); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +IGL_INLINE void igl::opengl::ViewerCore::draw( + ViewerData& data, + bool update_matrices) +{ + using namespace std; + using namespace Eigen; + + if (depth_test) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* Bind and potentially refresh mesh/line/point data */ + if (data.dirty) + { + data.updateGL(data, data.invert_normals,data.meshgl); + data.dirty = MeshGL::DIRTY_NONE; + } + data.meshgl.bind_mesh(); + + // Initialize uniform + glViewport(viewport(0), viewport(1), viewport(2), viewport(3)); + + if(update_matrices) + { + view = Eigen::Matrix4f::Identity(); + proj = Eigen::Matrix4f::Identity(); + norm = Eigen::Matrix4f::Identity(); + + float width = viewport(2); + float height = viewport(3); + + // Set view + look_at( camera_eye, camera_center, camera_up, view); + view = view + * (trackball_angle * Eigen::Scaling(camera_zoom * camera_base_zoom) + * Eigen::Translation3f(camera_translation + camera_base_translation)).matrix(); + + norm = view.inverse().transpose(); + + // Set projection + if (orthographic) + { + float length = (camera_eye - camera_center).norm(); + float h = tan(camera_view_angle/360.0 * igl::PI) * (length); + ortho(-h*width/height, h*width/height, -h, h, camera_dnear, camera_dfar,proj); + } + else + { + float fH = tan(camera_view_angle / 360.0 * igl::PI) * camera_dnear; + float fW = fH * (double)width/(double)height; + frustum(-fW, fW, -fH, fH, camera_dnear, camera_dfar,proj); + } + } + + // Send transformations to the GPU + GLint viewi = glGetUniformLocation(data.meshgl.shader_mesh,"view"); + GLint proji = glGetUniformLocation(data.meshgl.shader_mesh,"proj"); + GLint normi = glGetUniformLocation(data.meshgl.shader_mesh,"normal_matrix"); + glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); + glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); + glUniformMatrix4fv(normi, 1, GL_FALSE, norm.data()); + + // Light parameters + GLint specular_exponenti = glGetUniformLocation(data.meshgl.shader_mesh,"specular_exponent"); + GLint light_position_eyei = glGetUniformLocation(data.meshgl.shader_mesh,"light_position_eye"); + GLint lighting_factori = glGetUniformLocation(data.meshgl.shader_mesh,"lighting_factor"); + GLint fixed_colori = glGetUniformLocation(data.meshgl.shader_mesh,"fixed_color"); + GLint texture_factori = glGetUniformLocation(data.meshgl.shader_mesh,"texture_factor"); + + glUniform1f(specular_exponenti, data.shininess); + glUniform3fv(light_position_eyei, 1, light_position.data()); + glUniform1f(lighting_factori, lighting_factor); // enables lighting + glUniform4f(fixed_colori, 0.0, 0.0, 0.0, 0.0); + + if (data.V.rows()>0) + { + // Render fill + if (data.show_faces) + { + // Texture + glUniform1f(texture_factori, data.show_texture ? 1.0f : 0.0f); + data.meshgl.draw_mesh(true); + glUniform1f(texture_factori, 0.0f); + } + + // Render wireframe + if (data.show_lines) + { + glLineWidth(data.line_width); + glUniform4f(fixed_colori, + data.line_color[0], + data.line_color[1], + data.line_color[2], 1.0f); + data.meshgl.draw_mesh(false); + glUniform4f(fixed_colori, 0.0f, 0.0f, 0.0f, 0.0f); + } + } + + if (data.show_overlay) + { + if (data.show_overlay_depth) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + if (data.lines.rows() > 0) + { + data.meshgl.bind_overlay_lines(); + viewi = glGetUniformLocation(data.meshgl.shader_overlay_lines,"view"); + proji = glGetUniformLocation(data.meshgl.shader_overlay_lines,"proj"); + + glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); + glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); + // This must be enabled, otherwise glLineWidth has no effect + glEnable(GL_LINE_SMOOTH); + glLineWidth(data.line_width); + + data.meshgl.draw_overlay_lines(); + } + + if (data.points.rows() > 0) + { + data.meshgl.bind_overlay_points(); + viewi = glGetUniformLocation(data.meshgl.shader_overlay_points,"view"); + proji = glGetUniformLocation(data.meshgl.shader_overlay_points,"proj"); + + glUniformMatrix4fv(viewi, 1, GL_FALSE, view.data()); + glUniformMatrix4fv(proji, 1, GL_FALSE, proj.data()); + glPointSize(data.point_size); + + data.meshgl.draw_overlay_points(); + } + + glEnable(GL_DEPTH_TEST); + } + +} + +IGL_INLINE void igl::opengl::ViewerCore::draw_buffer(ViewerData& data, + bool update_matrices, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A) +{ + assert(R.rows() == G.rows() && G.rows() == B.rows() && B.rows() == A.rows()); + assert(R.cols() == G.cols() && G.cols() == B.cols() && B.cols() == A.cols()); + + unsigned width = R.rows(); + unsigned height = R.cols(); + + // https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing + unsigned int framebuffer; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + // create a multisampled color attachment texture + unsigned int textureColorBufferMultiSampled; + glGenTextures(1, &textureColorBufferMultiSampled); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, width, height, GL_TRUE); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled, 0); + // create a (also multisampled) renderbuffer object for depth and stencil attachments + unsigned int rbo; + glGenRenderbuffers(1, &rbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // configure second post-processing framebuffer + unsigned int intermediateFBO; + glGenFramebuffers(1, &intermediateFBO); + glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO); + // create a color attachment texture + unsigned int screenTexture; + glGenTextures(1, &screenTexture); + glBindTexture(GL_TEXTURE_2D, screenTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); // we only need a color buffer + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + // Clear the buffer + glClearColor(background_color(0), background_color(1), background_color(2), 0.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Save old viewport + Eigen::Vector4f viewport_ori = viewport; + viewport << 0,0,width,height; + // Draw + draw(data,update_matrices); + // Restore viewport + viewport = viewport_ori; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO); + // Copy back in the given Eigen matrices + GLubyte* pixels = (GLubyte*)calloc(width*height*4,sizeof(GLubyte)); + glReadPixels(0, 0,width, height,GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Clean up + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteTextures(1, &screenTexture); + glDeleteTextures(1, &textureColorBufferMultiSampled); + glDeleteFramebuffers(1, &framebuffer); + glDeleteFramebuffers(1, &intermediateFBO); + glDeleteRenderbuffers(1, &rbo); + + int count = 0; + for (unsigned j=0; j +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_VIEWERCORE_H +#define IGL_OPENGL_VIEWERCORE_H + +#include +#include + +#include +#include +#include + +namespace igl +{ +namespace opengl +{ + +// Basic class of the 3D mesh viewer +// TODO: write documentation + +class ViewerCore +{ +public: + IGL_INLINE ViewerCore(); + + // Initialization + IGL_INLINE void init(); + + // Shutdown + IGL_INLINE void shut(); + + // Serialization code + IGL_INLINE void InitSerialization(); + + + // ------------------- Camera control functions + + // Adjust the view to see the entire model + IGL_INLINE void align_camera_center( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F); + + // Determines how much to zoom and shift such that the mesh fills the unit + // box (centered at the origin) + IGL_INLINE void get_scale_and_shift_to_fit_mesh( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + float & zoom, + Eigen::Vector3f& shift); + + // Adjust the view to see the entire model + IGL_INLINE void align_camera_center( + const Eigen::MatrixXd& V); + + // Determines how much to zoom and shift such that the mesh fills the unit + // box (centered at the origin) + IGL_INLINE void get_scale_and_shift_to_fit_mesh( + const Eigen::MatrixXd& V, + float & zoom, + Eigen::Vector3f& shift); + + // ------------------- Drawing functions + + // Clear the frame buffers + IGL_INLINE void clear_framebuffers(); + + // Draw everything + // + // data cannot be const because it is being set to "clean" + IGL_INLINE void draw(ViewerData& data, bool update_matrices = true); + IGL_INLINE void draw_buffer( + ViewerData& data, + bool update_matrices, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A); + + // Trackball angle (quaternion) + enum RotationType + { + ROTATION_TYPE_TRACKBALL = 0, + ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP = 1, + ROTATION_TYPE_NO_ROTATION = 2, + NUM_ROTATION_TYPES = 3 + }; + IGL_INLINE void set_rotation_type(const RotationType & value); + + // ------------------- Properties + + // Colors + Eigen::Vector4f background_color; + + // Lighting + Eigen::Vector3f light_position; + float lighting_factor; + + RotationType rotation_type; + Eigen::Quaternionf trackball_angle; + + // Camera parameters + float camera_base_zoom; + float camera_zoom; + bool orthographic; + Eigen::Vector3f camera_base_translation; + Eigen::Vector3f camera_translation; + Eigen::Vector3f camera_eye; + Eigen::Vector3f camera_up; + Eigen::Vector3f camera_center; + float camera_view_angle; + float camera_dnear; + float camera_dfar; + + bool depth_test; + + // Animation + bool is_animating; + double animation_max_fps; + + // Caches the two-norm between the min/max point of the bounding box + float object_scale; + + // Viewport size + Eigen::Vector4f viewport; + + // Save the OpenGL transformation matrices used for the previous rendering pass + Eigen::Matrix4f view; + Eigen::Matrix4f proj; + Eigen::Matrix4f norm; + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW +}; + +} +} + +#include +namespace igl { + namespace serialization { + + inline void serialization(bool s, igl::opengl::ViewerCore& obj, std::vector& buffer) + { + + SERIALIZE_MEMBER(background_color); + + SERIALIZE_MEMBER(light_position); + SERIALIZE_MEMBER(lighting_factor); + + SERIALIZE_MEMBER(trackball_angle); + SERIALIZE_MEMBER(rotation_type); + + SERIALIZE_MEMBER(camera_base_zoom); + SERIALIZE_MEMBER(camera_zoom); + SERIALIZE_MEMBER(orthographic); + SERIALIZE_MEMBER(camera_base_translation); + SERIALIZE_MEMBER(camera_translation); + SERIALIZE_MEMBER(camera_view_angle); + SERIALIZE_MEMBER(camera_dnear); + SERIALIZE_MEMBER(camera_dfar); + SERIALIZE_MEMBER(camera_eye); + SERIALIZE_MEMBER(camera_center); + SERIALIZE_MEMBER(camera_up); + + SERIALIZE_MEMBER(depth_test); + SERIALIZE_MEMBER(is_animating); + SERIALIZE_MEMBER(animation_max_fps); + + SERIALIZE_MEMBER(object_scale); + + SERIALIZE_MEMBER(viewport); + SERIALIZE_MEMBER(view); + SERIALIZE_MEMBER(proj); + SERIALIZE_MEMBER(norm); + } + + template<> + inline void serialize(const igl::opengl::ViewerCore& obj, std::vector& buffer) + { + serialization(true, const_cast(obj), buffer); + } + + template<> + inline void deserialize(igl::opengl::ViewerCore& obj, const std::vector& buffer) + { + serialization(false, obj, const_cast&>(buffer)); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "ViewerCore.cpp" +#endif + +#endif diff --git a/src/igl/opengl/ViewerData.cpp b/src/igl/opengl/ViewerData.cpp new file mode 100644 index 000000000..0a7f2c44b --- /dev/null +++ b/src/igl/opengl/ViewerData.cpp @@ -0,0 +1,691 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "ViewerData.h" + +#include "../per_face_normals.h" +#include "../material_colors.h" +#include "../parula.h" +#include "../per_vertex_normals.h" + +#include + + +IGL_INLINE igl::opengl::ViewerData::ViewerData() +: dirty(MeshGL::DIRTY_ALL), + show_faces(true), + show_lines(true), + invert_normals(false), + show_overlay(true), + show_overlay_depth(true), + show_vertid(false), + show_faceid(false), + show_texture(false), + point_size(30), + line_width(0.5f), + line_color(0,0,0,1), + shininess(35.0f), + id(-1) +{ + clear(); +}; + +IGL_INLINE void igl::opengl::ViewerData::set_face_based(bool newvalue) +{ + if (face_based != newvalue) + { + face_based = newvalue; + dirty = MeshGL::DIRTY_ALL; + } +} + +// Helpers that draws the most common meshes +IGL_INLINE void igl::opengl::ViewerData::set_mesh( + const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F) +{ + using namespace std; + + Eigen::MatrixXd V_temp; + + // If V only has two columns, pad with a column of zeros + if (_V.cols() == 2) + { + V_temp = Eigen::MatrixXd::Zero(_V.rows(),3); + V_temp.block(0,0,_V.rows(),2) = _V; + } + else + V_temp = _V; + + if (V.rows() == 0 && F.rows() == 0) + { + V = V_temp; + F = _F; + + compute_normals(); + uniform_colors( + Eigen::Vector3d(GOLD_AMBIENT[0], GOLD_AMBIENT[1], GOLD_AMBIENT[2]), + Eigen::Vector3d(GOLD_DIFFUSE[0], GOLD_DIFFUSE[1], GOLD_DIFFUSE[2]), + Eigen::Vector3d(GOLD_SPECULAR[0], GOLD_SPECULAR[1], GOLD_SPECULAR[2])); + + grid_texture(); + } + else + { + if (_V.rows() == V.rows() && _F.rows() == F.rows()) + { + V = V_temp; + F = _F; + } + else + cerr << "ERROR (set_mesh): The new mesh has a different number of vertices/faces. Please clear the mesh before plotting."<0 && C.cols() == 1) + { + Eigen::MatrixXd C3; + igl::parula(C,true,C3); + return set_colors(C3); + } + // Ambient color should be darker color + const auto ambient = [](const MatrixXd & C)->MatrixXd + { + MatrixXd T = 0.1*C; + T.col(3) = C.col(3); + return T; + }; + // Specular color should be a less saturated and darker color: dampened + // highlights + const auto specular = [](const MatrixXd & C)->MatrixXd + { + const double grey = 0.3; + MatrixXd T = grey+0.1*(C.array()-grey); + T.col(3) = C.col(3); + return T; + }; + if (C.rows() == 1) + { + for (unsigned i=0;i& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B) +{ + texture_R = R; + texture_G = G; + texture_B = B; + texture_A = Eigen::Matrix::Constant(R.rows(),R.cols(),255); + dirty |= MeshGL::DIRTY_TEXTURE; +} + +IGL_INLINE void igl::opengl::ViewerData::set_texture( + const Eigen::Matrix& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B, + const Eigen::Matrix& A) +{ + texture_R = R; + texture_G = G; + texture_B = B; + texture_A = A; + dirty |= MeshGL::DIRTY_TEXTURE; +} + +IGL_INLINE void igl::opengl::ViewerData::set_points( + const Eigen::MatrixXd& P, + const Eigen::MatrixXd& C) +{ + // clear existing points + points.resize(0,0); + add_points(P,C); +} + +IGL_INLINE void igl::opengl::ViewerData::add_points(const Eigen::MatrixXd& P, const Eigen::MatrixXd& C) +{ + Eigen::MatrixXd P_temp; + + // If P only has two columns, pad with a column of zeros + if (P.cols() == 2) + { + P_temp = Eigen::MatrixXd::Zero(P.rows(),3); + P_temp.block(0,0,P.rows(),2) = P; + } + else + P_temp = P; + + int lastid = points.rows(); + points.conservativeResize(points.rows() + P_temp.rows(),6); + for (unsigned i=0; i=size2 && j>=size2)) + texture_R(i,j) = 255; + } + } + + texture_G = texture_R; + texture_B = texture_R; + texture_A = Eigen::Matrix::Constant(texture_R.rows(),texture_R.cols(),255); + dirty |= MeshGL::DIRTY_TEXTURE; +} + +IGL_INLINE void igl::opengl::ViewerData::updateGL( + const igl::opengl::ViewerData& data, + const bool invert_normals, + igl::opengl::MeshGL& meshgl + ) +{ + if (!meshgl.is_initialized) + { + meshgl.init(); + } + + bool per_corner_uv = (data.F_uv.rows() == data.F.rows()); + bool per_corner_normals = (data.F_normals.rows() == 3 * data.F.rows()); + + meshgl.dirty |= data.dirty; + + // Input: + // X #F by dim quantity + // Output: + // X_vbo #F*3 by dim scattering per corner + const auto per_face = [&data]( + const Eigen::MatrixXd & X, + MeshGL::RowMatrixXf & X_vbo) + { + assert(X.cols() == 4); + X_vbo.resize(data.F.rows()*3,4); + for (unsigned i=0; i(); + }; + + // Input: + // X #V by dim quantity + // Output: + // X_vbo #F*3 by dim scattering per corner + const auto per_corner = [&data]( + const Eigen::MatrixXd & X, + MeshGL::RowMatrixXf & X_vbo) + { + X_vbo.resize(data.F.rows()*3,X.cols()); + for (unsigned i=0; i(); + }; + + if (!data.face_based) + { + if (!(per_corner_uv || per_corner_normals)) + { + // Vertex positions + if (meshgl.dirty & MeshGL::DIRTY_POSITION) + meshgl.V_vbo = data.V.cast(); + + // Vertex normals + if (meshgl.dirty & MeshGL::DIRTY_NORMAL) + { + meshgl.V_normals_vbo = data.V_normals.cast(); + if (invert_normals) + meshgl.V_normals_vbo = -meshgl.V_normals_vbo; + } + + // Per-vertex material settings + if (meshgl.dirty & MeshGL::DIRTY_AMBIENT) + meshgl.V_ambient_vbo = data.V_material_ambient.cast(); + if (meshgl.dirty & MeshGL::DIRTY_DIFFUSE) + meshgl.V_diffuse_vbo = data.V_material_diffuse.cast(); + if (meshgl.dirty & MeshGL::DIRTY_SPECULAR) + meshgl.V_specular_vbo = data.V_material_specular.cast(); + + // Face indices + if (meshgl.dirty & MeshGL::DIRTY_FACE) + meshgl.F_vbo = data.F.cast(); + + // Texture coordinates + if (meshgl.dirty & MeshGL::DIRTY_UV) + { + meshgl.V_uv_vbo = data.V_uv.cast(); + } + } + else + { + + // Per vertex properties with per corner UVs + if (meshgl.dirty & MeshGL::DIRTY_POSITION) + { + per_corner(data.V,meshgl.V_vbo); + } + + if (meshgl.dirty & MeshGL::DIRTY_AMBIENT) + { + meshgl.V_ambient_vbo.resize(data.F.rows()*3,4); + for (unsigned i=0; i(); + } + if (meshgl.dirty & MeshGL::DIRTY_DIFFUSE) + { + meshgl.V_diffuse_vbo.resize(data.F.rows()*3,4); + for (unsigned i=0; i(); + } + if (meshgl.dirty & MeshGL::DIRTY_SPECULAR) + { + meshgl.V_specular_vbo.resize(data.F.rows()*3,4); + for (unsigned i=0; i(); + } + + if (meshgl.dirty & MeshGL::DIRTY_NORMAL) + { + meshgl.V_normals_vbo.resize(data.F.rows()*3,3); + for (unsigned i=0; i() : + data.V_normals.row(data.F(i,j)).cast(); + + + if (invert_normals) + meshgl.V_normals_vbo = -meshgl.V_normals_vbo; + } + + if (meshgl.dirty & MeshGL::DIRTY_FACE) + { + meshgl.F_vbo.resize(data.F.rows(),3); + for (unsigned i=0; i(); + } + } + } + else + { + if (meshgl.dirty & MeshGL::DIRTY_POSITION) + { + per_corner(data.V,meshgl.V_vbo); + } + if (meshgl.dirty & MeshGL::DIRTY_AMBIENT) + { + per_face(data.F_material_ambient,meshgl.V_ambient_vbo); + } + if (meshgl.dirty & MeshGL::DIRTY_DIFFUSE) + { + per_face(data.F_material_diffuse,meshgl.V_diffuse_vbo); + } + if (meshgl.dirty & MeshGL::DIRTY_SPECULAR) + { + per_face(data.F_material_specular,meshgl.V_specular_vbo); + } + + if (meshgl.dirty & MeshGL::DIRTY_NORMAL) + { + meshgl.V_normals_vbo.resize(data.F.rows()*3,3); + for (unsigned i=0; i() : + data.F_normals.row(i).cast(); + + if (invert_normals) + meshgl.V_normals_vbo = -meshgl.V_normals_vbo; + } + + if (meshgl.dirty & MeshGL::DIRTY_FACE) + { + meshgl.F_vbo.resize(data.F.rows(),3); + for (unsigned i=0; i(); + } + } + + if (meshgl.dirty & MeshGL::DIRTY_TEXTURE) + { + meshgl.tex_u = data.texture_R.rows(); + meshgl.tex_v = data.texture_R.cols(); + meshgl.tex.resize(data.texture_R.size()*4); + for (unsigned i=0;i(i, 0).cast(); + meshgl.lines_V_vbo.row(2*i+1) = data.lines.block<1, 3>(i, 3).cast(); + meshgl.lines_V_colors_vbo.row(2*i+0) = data.lines.block<1, 3>(i, 6).cast(); + meshgl.lines_V_colors_vbo.row(2*i+1) = data.lines.block<1, 3>(i, 6).cast(); + meshgl.lines_F_vbo(2*i+0) = 2*i+0; + meshgl.lines_F_vbo(2*i+1) = 2*i+1; + } + } + + if (meshgl.dirty & MeshGL::DIRTY_OVERLAY_POINTS) + { + meshgl.points_V_vbo.resize(data.points.rows(),3); + meshgl.points_V_colors_vbo.resize(data.points.rows(),3); + meshgl.points_F_vbo.resize(data.points.rows(),1); + for (unsigned i=0; i(i, 0).cast(); + meshgl.points_V_colors_vbo.row(i) = data.points.block<1, 3>(i, 3).cast(); + meshgl.points_F_vbo(i) = i; + } + } +} diff --git a/src/igl/opengl/ViewerData.h b/src/igl/opengl/ViewerData.h new file mode 100644 index 000000000..b3ec9178c --- /dev/null +++ b/src/igl/opengl/ViewerData.h @@ -0,0 +1,276 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VIEWERDATA_H +#define IGL_VIEWERDATA_H + +#include "../igl_inline.h" +#include "MeshGL.h" +#include +#include +#include +#include +#include + +// Alec: This is a mesh class containing a variety of data types (normals, +// overlays, material colors, etc.) +// +namespace igl +{ + +// TODO: write documentation +namespace opengl +{ + +class ViewerData +{ +public: + ViewerData(); + + // Empty all fields + IGL_INLINE void clear(); + + // Change the visualization mode, invalidating the cache if necessary + IGL_INLINE void set_face_based(bool newvalue); + + // Helpers that can draw the most common meshes + IGL_INLINE void set_mesh(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F); + IGL_INLINE void set_vertices(const Eigen::MatrixXd& V); + IGL_INLINE void set_normals(const Eigen::MatrixXd& N); + + // Set the color of the mesh + // + // Inputs: + // C #V|#F|1 by 3 list of colors + IGL_INLINE void set_colors(const Eigen::MatrixXd &C); + // Set per-vertex UV coordinates + // + // Inputs: + // UV #V by 2 list of UV coordinates (indexed by F) + IGL_INLINE void set_uv(const Eigen::MatrixXd& UV); + // Set per-corner UV coordinates + // + // Inputs: + // UV_V #UV by 2 list of UV coordinates + // UV_F #F by 3 list of UV indices into UV_V + IGL_INLINE void set_uv(const Eigen::MatrixXd& UV_V, const Eigen::MatrixXi& UV_F); + // Set the texture associated with the mesh. + // + // Inputs: + // R width by height image matrix of red channel + // G width by height image matrix of green channel + // B width by height image matrix of blue channel + // + IGL_INLINE void set_texture( + const Eigen::Matrix& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B); + + // Set the texture associated with the mesh. + // + // Inputs: + // R width by height image matrix of red channel + // G width by height image matrix of green channel + // B width by height image matrix of blue channel + // A width by height image matrix of alpha channel + // + IGL_INLINE void set_texture( + const Eigen::Matrix& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B, + const Eigen::Matrix& A); + + // Sets points given a list of point vertices. In constrast to `set_points` + // this will (purposefully) clober existing points. + // + // Inputs: + // P #P by 3 list of vertex positions + // C #P|1 by 3 color(s) + IGL_INLINE void set_points( + const Eigen::MatrixXd& P, + const Eigen::MatrixXd& C); + IGL_INLINE void add_points(const Eigen::MatrixXd& P, const Eigen::MatrixXd& C); + // Sets edges given a list of edge vertices and edge indices. In constrast + // to `add_edges` this will (purposefully) clober existing edges. + // + // Inputs: + // P #P by 3 list of vertex positions + // E #E by 2 list of edge indices into P + // C #E|1 by 3 color(s) + IGL_INLINE void set_edges (const Eigen::MatrixXd& P, const Eigen::MatrixXi& E, const Eigen::MatrixXd& C); + // Alec: This is very confusing. Why does add_edges have a different API from + // set_edges? + IGL_INLINE void add_edges (const Eigen::MatrixXd& P1, const Eigen::MatrixXd& P2, const Eigen::MatrixXd& C); + IGL_INLINE void add_label (const Eigen::VectorXd& P, const std::string& str); + + // Computes the normals of the mesh + IGL_INLINE void compute_normals(); + + // Assigns uniform colors to all faces/vertices + IGL_INLINE void uniform_colors( + const Eigen::Vector3d& diffuse, + const Eigen::Vector3d& ambient, + const Eigen::Vector3d& specular); + + // Assigns uniform colors to all faces/vertices + IGL_INLINE void uniform_colors( + const Eigen::Vector4d& ambient, + const Eigen::Vector4d& diffuse, + const Eigen::Vector4d& specular); + + // Generates a default grid texture + IGL_INLINE void grid_texture(); + + Eigen::MatrixXd V; // Vertices of the current mesh (#V x 3) + Eigen::MatrixXi F; // Faces of the mesh (#F x 3) + + // Per face attributes + Eigen::MatrixXd F_normals; // One normal per face + + Eigen::MatrixXd F_material_ambient; // Per face ambient color + Eigen::MatrixXd F_material_diffuse; // Per face diffuse color + Eigen::MatrixXd F_material_specular; // Per face specular color + + // Per vertex attributes + Eigen::MatrixXd V_normals; // One normal per vertex + + Eigen::MatrixXd V_material_ambient; // Per vertex ambient color + Eigen::MatrixXd V_material_diffuse; // Per vertex diffuse color + Eigen::MatrixXd V_material_specular; // Per vertex specular color + + // UV parametrization + Eigen::MatrixXd V_uv; // UV vertices + Eigen::MatrixXi F_uv; // optional faces for UVs + + // Texture + Eigen::Matrix texture_R; + Eigen::Matrix texture_G; + Eigen::Matrix texture_B; + Eigen::Matrix texture_A; + + // Overlays + + // Lines plotted over the scene + // (Every row contains 9 doubles in the following format S_x, S_y, S_z, T_x, T_y, T_z, C_r, C_g, C_b), + // with S and T the coordinates of the two vertices of the line in global coordinates, and C the color in floating point rgb format + Eigen::MatrixXd lines; + + // Points plotted over the scene + // (Every row contains 6 doubles in the following format P_x, P_y, P_z, C_r, C_g, C_b), + // with P the position in global coordinates of the center of the point, and C the color in floating point rgb format + Eigen::MatrixXd points; + + // Text labels plotted over the scene + // Textp contains, in the i-th row, the position in global coordinates where the i-th label should be anchored + // Texts contains in the i-th position the text of the i-th label + Eigen::MatrixXd labels_positions; + std::vector labels_strings; + + // Marks dirty buffers that need to be uploaded to OpenGL + uint32_t dirty; + + // Enable per-face or per-vertex properties + bool face_based; + + // Visualization options + bool show_overlay; + bool show_overlay_depth; + bool show_texture; + bool show_faces; + bool show_lines; + bool show_vertid; + bool show_faceid; + bool invert_normals; + + // Point size / line width + float point_size; + float line_width; + Eigen::Vector4f line_color; + + // Shape material + float shininess; + + // Unique identifier + int id; + + // OpenGL representation of the mesh + igl::opengl::MeshGL meshgl; + + // Update contents from a 'Data' instance + IGL_INLINE void updateGL( + const igl::opengl::ViewerData& data, + const bool invert_normals, + igl::opengl::MeshGL& meshgl); +}; + +} // namespace opengl +} // namespace igl + +//////////////////////////////////////////////////////////////////////////////// + +#include +namespace igl +{ + namespace serialization + { + inline void serialization(bool s, igl::opengl::ViewerData& obj, std::vector& buffer) + { + SERIALIZE_MEMBER(V); + SERIALIZE_MEMBER(F); + SERIALIZE_MEMBER(F_normals); + SERIALIZE_MEMBER(F_material_ambient); + SERIALIZE_MEMBER(F_material_diffuse); + SERIALIZE_MEMBER(F_material_specular); + SERIALIZE_MEMBER(V_normals); + SERIALIZE_MEMBER(V_material_ambient); + SERIALIZE_MEMBER(V_material_diffuse); + SERIALIZE_MEMBER(V_material_specular); + SERIALIZE_MEMBER(V_uv); + SERIALIZE_MEMBER(F_uv); + SERIALIZE_MEMBER(texture_R); + SERIALIZE_MEMBER(texture_G); + SERIALIZE_MEMBER(texture_B); + SERIALIZE_MEMBER(texture_A); + SERIALIZE_MEMBER(lines); + SERIALIZE_MEMBER(points); + SERIALIZE_MEMBER(labels_positions); + SERIALIZE_MEMBER(labels_strings); + SERIALIZE_MEMBER(dirty); + SERIALIZE_MEMBER(face_based); + SERIALIZE_MEMBER(show_faces); + SERIALIZE_MEMBER(show_lines); + SERIALIZE_MEMBER(invert_normals); + SERIALIZE_MEMBER(show_overlay); + SERIALIZE_MEMBER(show_overlay_depth); + SERIALIZE_MEMBER(show_vertid); + SERIALIZE_MEMBER(show_faceid); + SERIALIZE_MEMBER(show_texture); + SERIALIZE_MEMBER(point_size); + SERIALIZE_MEMBER(line_width); + SERIALIZE_MEMBER(line_color); + SERIALIZE_MEMBER(shininess); + SERIALIZE_MEMBER(id); + } + template<> + inline void serialize(const igl::opengl::ViewerData& obj, std::vector& buffer) + { + serialization(true, const_cast(obj), buffer); + } + template<> + inline void deserialize(igl::opengl::ViewerData& obj, const std::vector& buffer) + { + serialization(false, obj, const_cast&>(buffer)); + obj.dirty = igl::opengl::MeshGL::DIRTY_ALL; + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "ViewerData.cpp" +#endif + +#endif diff --git a/src/igl/opengl/bind_vertex_attrib_array.cpp b/src/igl/opengl/bind_vertex_attrib_array.cpp new file mode 100644 index 000000000..4aa807c18 --- /dev/null +++ b/src/igl/opengl/bind_vertex_attrib_array.cpp @@ -0,0 +1,24 @@ +#include "bind_vertex_attrib_array.h" + +IGL_INLINE GLint igl::opengl::bind_vertex_attrib_array( + const GLuint program_shader, + const std::string &name, + GLuint bufferID, + const Eigen::Matrix &M, + bool refresh) +{ + GLint id = glGetAttribLocation(program_shader, name.c_str()); + if (id < 0) + return id; + if (M.size() == 0) + { + glDisableVertexAttribArray(id); + return id; + } + glBindBuffer(GL_ARRAY_BUFFER, bufferID); + if (refresh) + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*M.size(), M.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(id, M.cols(), GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(id); + return id; +} diff --git a/src/igl/opengl/bind_vertex_attrib_array.h b/src/igl/opengl/bind_vertex_attrib_array.h new file mode 100644 index 000000000..4e9443136 --- /dev/null +++ b/src/igl/opengl/bind_vertex_attrib_array.h @@ -0,0 +1,32 @@ +#ifndef IGL_OPENGL_BIND_VERTEX_ATTRIB_ARRAY_H +#define IGL_OPENGL_BIND_VERTEX_ATTRIB_ARRAY_H +#include "gl.h" +#include "../igl_inline.h" +#include +#include +namespace igl +{ + namespace opengl + { + // Bind a per-vertex array attribute and refresh its contents from an Eigen + // matrix + // + // Inputs: + // program_shader id of shader program + // name name of attribute in vertex shader + // bufferID id of buffer to bind to + // M #V by dim matrix of per-vertex data + // refresh whether to actually call glBufferData or just bind the buffer + // Returns id of named attribute in shader + IGL_INLINE GLint bind_vertex_attrib_array( + const GLuint program_shader, + const std::string &name, + GLuint bufferID, + const Eigen::Matrix &M, + bool refresh); + } +} +#ifndef IGL_STATIC_LIBRARY +#include "bind_vertex_attrib_array.cpp" +#endif +#endif diff --git a/src/igl/opengl/create_index_vbo.cpp b/src/igl/opengl/create_index_vbo.cpp new file mode 100644 index 000000000..76f6248c0 --- /dev/null +++ b/src/igl/opengl/create_index_vbo.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "create_index_vbo.h" + +// http://www.songho.ca/opengl/gl_vbo.html#create +IGL_INLINE void igl::opengl::create_index_vbo( + const Eigen::MatrixXi & F, + GLuint & F_vbo_id) +{ + // Generate Buffers + glGenBuffers(1,&F_vbo_id); + // Bind Buffers + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,F_vbo_id); + // Copy data to buffers + // We expect a matrix with each vertex position on a row, we then want to + // pass this data to OpenGL reading across rows (row-major) + if(F.Options & Eigen::RowMajor) + { + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + sizeof(int)*F.size(), + F.data(), + GL_STATIC_DRAW); + }else + { + // Create temporary copy of transpose + Eigen::MatrixXi FT = F.transpose(); + // If its column major then we need to temporarily store a transpose + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + sizeof(int)*F.size(), + FT.data(), + GL_STATIC_DRAW); + } + // bind with 0, so, switch back to normal pointer operation + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/opengl/create_index_vbo.h b/src/igl/opengl/create_index_vbo.h new file mode 100644 index 000000000..e48f3a489 --- /dev/null +++ b/src/igl/opengl/create_index_vbo.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_CREATE_INDEX_VBO_H +#define IGL_OPENGL_CREATE_INDEX_VBO_H +#include "../igl_inline.h" +#include "gl.h" +#include + +// Create a VBO (Vertex Buffer Object) for a list of indices: +// GL_ELEMENT_ARRAY_BUFFER_ARB for the triangle indices (F) +namespace igl +{ + namespace opengl + { + // Inputs: + // F #F by 3 eigen Matrix of face (triangle) indices + // Outputs: + // F_vbo_id buffer id for face indices + // + IGL_INLINE void create_index_vbo( + const Eigen::MatrixXi & F, + GLuint & F_vbo_id); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "create_index_vbo.cpp" +#endif + +#endif diff --git a/src/igl/opengl/create_mesh_vbo.cpp b/src/igl/opengl/create_mesh_vbo.cpp new file mode 100644 index 000000000..3876e1dfa --- /dev/null +++ b/src/igl/opengl/create_mesh_vbo.cpp @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "create_mesh_vbo.h" + +#include "create_vector_vbo.h" +#include "create_index_vbo.h" + +// http://www.songho.ca/opengl/gl_vbo.html#create +IGL_INLINE void igl::opengl::create_mesh_vbo( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + GLuint & V_vbo_id, + GLuint & F_vbo_id) +{ + // Create VBO for vertex position vectors + create_vector_vbo(V,V_vbo_id); + // Create VBO for face index lists + create_index_vbo(F,F_vbo_id); +} + +// http://www.songho.ca/opengl/gl_vbo.html#create +IGL_INLINE void igl::opengl::create_mesh_vbo( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + GLuint & V_vbo_id, + GLuint & F_vbo_id, + GLuint & N_vbo_id) +{ + // Create VBOs for faces and vertices + create_mesh_vbo(V,F,V_vbo_id,F_vbo_id); + // Create VBO for normal vectors + create_vector_vbo(N,N_vbo_id); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/opengl/create_mesh_vbo.h b/src/igl/opengl/create_mesh_vbo.h new file mode 100644 index 000000000..ecb303b09 --- /dev/null +++ b/src/igl/opengl/create_mesh_vbo.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_CREATE_MESH_VBO_H +#define IGL_OPENGL_CREATE_MESH_VBO_H +#include "../igl_inline.h" +#include "gl.h" +#include + +// Create a VBO (Vertex Buffer Object) for a mesh. Actually two VBOs: one +// GL_ARRAY_BUFFER for the vertex positions (V) and one +// GL_ELEMENT_ARRAY_BUFFER for the triangle indices (F) +namespace igl +{ + namespace opengl + { + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // Outputs: + // V_vbo_id buffer id for vertex positions + // F_vbo_id buffer id for face indices + // + // NOTE: when using glDrawElements VBOs for V and F using MatrixXd and + // MatrixXi will have types GL_DOUBLE and GL_UNSIGNED_INT respectively + // + IGL_INLINE void create_mesh_vbo( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + GLuint & V_vbo_id, + GLuint & F_vbo_id); + + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // N #V by 3 eigen Matrix of mesh vertex 3D normals + // Outputs: + // V_vbo_id buffer id for vertex positions + // F_vbo_id buffer id for face indices + // N_vbo_id buffer id for vertex positions + IGL_INLINE void create_mesh_vbo( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + GLuint & V_vbo_id, + GLuint & F_vbo_id, + GLuint & N_vbo_id); + } + +} + +#ifndef IGL_STATIC_LIBRARY +# include "create_mesh_vbo.cpp" +#endif + +#endif diff --git a/src/igl/opengl/create_shader_program.cpp b/src/igl/opengl/create_shader_program.cpp new file mode 100644 index 000000000..6c4b31a2f --- /dev/null +++ b/src/igl/opengl/create_shader_program.cpp @@ -0,0 +1,141 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "create_shader_program.h" + +#include "load_shader.h" +#include "print_program_info_log.h" +#include +#include + +IGL_INLINE bool igl::opengl::create_shader_program( + const std::string & geom_source, + const std::string & vert_source, + const std::string & frag_source, + const std::map & attrib, + GLuint & id) +{ + using namespace std; + if(vert_source == "" && frag_source == "") + { + cerr<< + "create_shader_program() could not create shader program," + " both .vert and .frag source given were empty"<::const_iterator ait = attrib.begin(); + ait != attrib.end(); + ait++) + { + glBindAttribLocation( + id, + (*ait).second, + (*ait).first.c_str()); + } + // Link program + glLinkProgram(id); + const auto & detach = [&id](const GLuint shader) + { + if(shader) + { + glDetachShader(id,shader); + glDeleteShader(shader); + } + }; + detach(g); + detach(f); + detach(v); + + // print log if any + igl::opengl::print_program_info_log(id); + + return true; +} + +IGL_INLINE bool igl::opengl::create_shader_program( + const std::string & vert_source, + const std::string & frag_source, + const std::map & attrib, + GLuint & prog_id) +{ + return create_shader_program("",vert_source,frag_source,attrib,prog_id); +} + + +IGL_INLINE GLuint igl::opengl::create_shader_program( + const std::string & geom_source, + const std::string & vert_source, + const std::string & frag_source, + const std::map & attrib) +{ + GLuint prog_id = 0; + create_shader_program(geom_source,vert_source,frag_source,attrib,prog_id); + return prog_id; +} + +IGL_INLINE GLuint igl::opengl::create_shader_program( + const std::string & vert_source, + const std::string & frag_source, + const std::map & attrib) +{ + GLuint prog_id = 0; + create_shader_program(vert_source,frag_source,attrib,prog_id); + return prog_id; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif + diff --git a/src/igl/opengl/create_shader_program.h b/src/igl/opengl/create_shader_program.h new file mode 100644 index 000000000..5dfdc701d --- /dev/null +++ b/src/igl/opengl/create_shader_program.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_CREATE_SHADER_PROGRAM_H +#define IGL_OPENGL_CREATE_SHADER_PROGRAM_H +#include "../igl_inline.h" +#include "gl.h" +#include +#include + +namespace igl +{ + namespace opengl + { + // Create a shader program with a vertex and fragments shader loading from + // source strings and vertex attributes assigned from a map before linking the + // shaders to the program, making it ready to use with glUseProgram(id) + // Inputs: + // geom_source string containing source code of geometry shader (can be + // "" to mean use default pass-through) + // vert_source string containing source code of vertex shader + // frag_source string containing source code of fragment shader + // attrib map containing table of vertex attribute strings add their + // correspondingly ids (generated previously using glBindAttribLocation) + // Outputs: + // id index id of created shader, set to 0 on error + // Returns true on success, false on error + // + // Note: Caller is responsible for making sure that current value of id is not + // leaking a shader (since it will be overwritten) + // + // See also: destroy_shader_program + IGL_INLINE bool create_shader_program( + const std::string &geom_source, + const std::string &vert_source, + const std::string &frag_source, + const std::map &attrib, + GLuint & id); + IGL_INLINE bool create_shader_program( + const std::string &vert_source, + const std::string &frag_source, + const std::map &attrib, + GLuint & id); + IGL_INLINE GLuint create_shader_program( + const std::string & geom_source, + const std::string & vert_source, + const std::string & frag_source, + const std::map &attrib); + IGL_INLINE GLuint create_shader_program( + const std::string & vert_source, + const std::string & frag_source, + const std::map &attrib); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "create_shader_program.cpp" +#endif + +#endif diff --git a/src/igl/opengl/create_vector_vbo.cpp b/src/igl/opengl/create_vector_vbo.cpp new file mode 100644 index 000000000..8690359b7 --- /dev/null +++ b/src/igl/opengl/create_vector_vbo.cpp @@ -0,0 +1,57 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "create_vector_vbo.h" + +#include + +// http://www.songho.ca/opengl/gl_vbo.html#create +template +IGL_INLINE void igl::opengl::create_vector_vbo( + const Eigen::Matrix & V, + GLuint & V_vbo_id) +{ + //// Expects that input is list of 3D vectors along rows + //assert(V.cols() == 3); + + // Generate Buffers + glGenBuffers(1,&V_vbo_id); + // Bind Buffers + glBindBuffer(GL_ARRAY_BUFFER,V_vbo_id); + // Copy data to buffers + // We expect a matrix with each vertex position on a row, we then want to + // pass this data to OpenGL reading across rows (row-major) + if(V.Options & Eigen::RowMajor) + { + glBufferData( + GL_ARRAY_BUFFER, + sizeof(T)*V.size(), + V.data(), + GL_STATIC_DRAW); + }else + { + // Create temporary copy of transpose + Eigen::Matrix VT = V.transpose(); + // If its column major then we need to temporarily store a transpose + glBufferData( + GL_ARRAY_BUFFER, + sizeof(T)*V.size(), + VT.data(), + GL_STATIC_DRAW); + } + // bind with 0, so, switch back to normal pointer operation + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::opengl::create_vector_vbo(Eigen::Matrix const&, unsigned int&); +// generated by autoexplicit.sh +template void igl::opengl::create_vector_vbo(Eigen::Matrix const&, unsigned int&); +#endif + diff --git a/src/igl/opengl/create_vector_vbo.h b/src/igl/opengl/create_vector_vbo.h new file mode 100644 index 000000000..757c1a199 --- /dev/null +++ b/src/igl/opengl/create_vector_vbo.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_CREATE_VECTOR_VBO_H +#define IGL_OPENGL_CREATE_VECTOR_VBO_H +#include "../igl_inline.h" +#include "gl.h" +#include + +// Create a VBO (Vertex Buffer Object) for a list of vectors: +// GL_ARRAY_BUFFER for the vectors (V) +namespace igl +{ + namespace opengl + { + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // V m by n eigen Matrix of type T values + // Outputs: + // V_vbo_id buffer id for vectors + // + template + IGL_INLINE void create_vector_vbo( + const Eigen::Matrix & V, + GLuint & V_vbo_id); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "create_vector_vbo.cpp" +#endif + +#endif diff --git a/src/igl/opengl/destroy_shader_program.cpp b/src/igl/opengl/destroy_shader_program.cpp new file mode 100644 index 000000000..2ceace7c2 --- /dev/null +++ b/src/igl/opengl/destroy_shader_program.cpp @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "destroy_shader_program.h" +#include "report_gl_error.h" +#include + +IGL_INLINE bool igl::opengl::destroy_shader_program(const GLuint id) +{ + // Don't try to destroy id == 0 (no shader program) + if(id == 0) + { + fprintf(stderr,"Error: destroy_shader_program() id = %d" + " but must should be positive\n",id); + return false; + } + // Get each attached shader one by one and detach and delete it + GLsizei count; + // shader id + GLuint s; + do + { + // Try to get at most *1* attached shader + glGetAttachedShaders(id,1,&count,&s); + GLenum err = igl::opengl::report_gl_error(); + if (GL_NO_ERROR != err) + { + return false; + } + // Check that we actually got *1* + if(count == 1) + { + // Detach and delete this shader + glDetachShader(id,s); + glDeleteShader(s); + } + }while(count > 0); + // Now that all of the shaders are gone we can just delete the program + glDeleteProgram(id); + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif + diff --git a/src/igl/opengl/destroy_shader_program.h b/src/igl/opengl/destroy_shader_program.h new file mode 100644 index 000000000..5d53d8c3e --- /dev/null +++ b/src/igl/opengl/destroy_shader_program.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_DESTROY_SHADER_PROGRAM_H +#define IGL_OPENGL_DESTROY_SHADER_PROGRAM_H +#include "../igl_inline.h" +#include "gl.h" + +namespace igl +{ + namespace opengl + { + // Properly destroy a shader program. Detach and delete each of its shaders + // and delete it + // Inputs: + // id index id of created shader, set to 0 on error + // Returns true on success, false on error + // + // Note: caller is responsible for making sure he doesn't foolishly continue + // to use id as if it still contains a program + // + // See also: create_shader_program + IGL_INLINE bool destroy_shader_program(const GLuint id); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "destroy_shader_program.cpp" +#endif + +#endif diff --git a/src/igl/opengl/gl.h b/src/igl/opengl/gl.h new file mode 100644 index 000000000..fc037c198 --- /dev/null +++ b/src/igl/opengl/gl.h @@ -0,0 +1,25 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013, 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GL_H +#define IGL_OPENGL_GL_H + +#ifdef IGL_OPENGL2_GL_H +# error "igl/opengl2/gl.h already included" +#endif + +// Always use this: +// #include "gl.h" +// Instead of: +// #include +// or +// #include +// + +#include + +#endif diff --git a/src/igl/opengl/gl_type_size.cpp b/src/igl/opengl/gl_type_size.cpp new file mode 100644 index 000000000..0fd02bf05 --- /dev/null +++ b/src/igl/opengl/gl_type_size.cpp @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "gl_type_size.h" +#include + +IGL_INLINE int igl::opengl::gl_type_size(const GLenum type) +{ + switch(type) + { + case GL_DOUBLE: + return 8; + break; + case GL_FLOAT: + return 4; + break; + case GL_INT: + return 4; + break; + default: + // should handle all other GL_[types] + assert(false && "Implementation incomplete."); + break; + } + return -1; +} diff --git a/src/igl/opengl/gl_type_size.h b/src/igl/opengl/gl_type_size.h new file mode 100644 index 000000000..a74119549 --- /dev/null +++ b/src/igl/opengl/gl_type_size.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GL_TYPE_SIZE_H +#define IGL_OPENGL_GL_TYPE_SIZE_H +#include "../igl_inline.h" +#include "gl.h" + +namespace igl +{ + namespace opengl + { + // Return the number of bytes for a given OpenGL type // Inputs: + // type enum value of opengl type + // Returns size in bytes of type + IGL_INLINE int gl_type_size(const GLenum type); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "gl_type_size.cpp" +#endif + +#endif diff --git a/src/igl/opengl/glfw/Viewer.cpp b/src/igl/opengl/glfw/Viewer.cpp new file mode 100644 index 000000000..76d1604a0 --- /dev/null +++ b/src/igl/opengl/glfw/Viewer.cpp @@ -0,0 +1,950 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "Viewer.h" + +#include +#include + +#include + +#include "../gl.h" +#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 +#include +#include + +// Internal global variables used for glfw event handling +static igl::opengl::glfw::Viewer * __viewer; +static double highdpi = 1; +static double scroll_x = 0; +static double scroll_y = 0; + +static void glfw_mouse_press(GLFWwindow* window, int button, int action, int modifier) +{ + + igl::opengl::glfw::Viewer::MouseButton mb; + + if (button == GLFW_MOUSE_BUTTON_1) + mb = igl::opengl::glfw::Viewer::MouseButton::Left; + else if (button == GLFW_MOUSE_BUTTON_2) + mb = igl::opengl::glfw::Viewer::MouseButton::Right; + else //if (button == GLFW_MOUSE_BUTTON_3) + mb = igl::opengl::glfw::Viewer::MouseButton::Middle; + + if (action == GLFW_PRESS) + __viewer->mouse_down(mb,modifier); + else + __viewer->mouse_up(mb,modifier); +} + +static void glfw_error_callback(int error, const char* description) +{ + fputs(description, stderr); +} + +static void glfw_char_mods_callback(GLFWwindow* window, unsigned int codepoint, int modifier) +{ + __viewer->key_pressed(codepoint, modifier); +} + +static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int modifier) +{ + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + glfwSetWindowShouldClose(window, GL_TRUE); + + if (action == GLFW_PRESS) + __viewer->key_down(key, modifier); + else if(action == GLFW_RELEASE) + __viewer->key_up(key, modifier); +} + +static void glfw_window_size(GLFWwindow* window, int width, int height) +{ + int w = width*highdpi; + int h = height*highdpi; + + __viewer->post_resize(w, h); + +} + +static void glfw_mouse_move(GLFWwindow* window, double x, double y) +{ + __viewer->mouse_move(x*highdpi, y*highdpi); +} + +static void glfw_mouse_scroll(GLFWwindow* window, double x, double y) +{ + using namespace std; + scroll_x += x; + scroll_y += y; + + __viewer->mouse_scroll(y); +} + +static void glfw_drop_callback(GLFWwindow *window,int count,const char **filenames) +{ +} + +namespace igl +{ +namespace opengl +{ +namespace glfw +{ + + IGL_INLINE int Viewer::launch(bool resizable,bool fullscreen) + { + // TODO return values are being ignored... + launch_init(resizable,fullscreen); + launch_rendering(true); + launch_shut(); + return EXIT_SUCCESS; + } + + IGL_INLINE int Viewer::launch_init(bool resizable,bool fullscreen) + { + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) + { + return EXIT_FAILURE; + } + glfwWindowHint(GLFW_SAMPLES, 8); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + #ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + #endif + if(fullscreen) + { + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + window = glfwCreateWindow(mode->width,mode->height,"libigl viewer",monitor,nullptr); + } + else + { + if (core.viewport.tail<2>().any()) { + window = glfwCreateWindow(core.viewport(2),core.viewport(3),"libigl viewer",nullptr,nullptr); + } else { + window = glfwCreateWindow(1280,800,"libigl viewer",nullptr,nullptr); + } + } + if (!window) + { + glfwTerminate(); + return EXIT_FAILURE; + } + glfwMakeContextCurrent(window); + // Load OpenGL and its extensions + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) + { + printf("Failed to load OpenGL and its extensions\n"); + return(-1); + } + #if defined(DEBUG) || defined(_DEBUG) + printf("OpenGL Version %d.%d loaded\n", GLVersion.major, GLVersion.minor); + int major, minor, rev; + major = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR); + minor = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR); + rev = glfwGetWindowAttrib(window, GLFW_CONTEXT_REVISION); + printf("OpenGL version received: %d.%d.%d\n", major, minor, rev); + printf("Supported OpenGL is %s\n", (const char*)glGetString(GL_VERSION)); + printf("Supported GLSL is %s\n", (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + #endif + glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_NORMAL); + // Initialize FormScreen + __viewer = this; + // Register callbacks + glfwSetKeyCallback(window, glfw_key_callback); + glfwSetCursorPosCallback(window,glfw_mouse_move); + glfwSetWindowSizeCallback(window,glfw_window_size); + glfwSetMouseButtonCallback(window,glfw_mouse_press); + glfwSetScrollCallback(window,glfw_mouse_scroll); + glfwSetCharModsCallback(window,glfw_char_mods_callback); + glfwSetDropCallback(window,glfw_drop_callback); + // Handle retina displays (windows and mac) + int width, height; + glfwGetFramebufferSize(window, &width, &height); + int width_window, height_window; + glfwGetWindowSize(window, &width_window, &height_window); + highdpi = width/width_window; + glfw_window_size(window,width_window,height_window); + //opengl.init(); + core.align_camera_center(data().V,data().F); + // Initialize IGL viewer + init(); + return EXIT_SUCCESS; + } + + + + IGL_INLINE bool Viewer::launch_rendering(bool loop) + { + // glfwMakeContextCurrent(window); + // Rendering loop + const int num_extra_frames = 5; + int frame_counter = 0; + while (!glfwWindowShouldClose(window)) + { + double tic = get_seconds(); + draw(); + glfwSwapBuffers(window); + if(core.is_animating || frame_counter++ < num_extra_frames) + { + glfwPollEvents(); + // In microseconds + double duration = 1000000.*(get_seconds()-tic); + const double min_duration = 1000000./core.animation_max_fps; + if(durationinit(this); + } + } + + IGL_INLINE void Viewer::shutdown_plugins() + { + for (unsigned int i = 0; ishutdown(); + } + } + + IGL_INLINE Viewer::Viewer(): + data_list(1), + selected_data_index(0), + next_data_id(1) + { + window = nullptr; + data_list.front().id = 0; + + // Temporary variables initialization + down = false; + hack_never_moved = true; + scroll_position = 0.0f; + + // Per face + data().set_face_based(false); + + // C-style callbacks + callback_init = nullptr; + callback_pre_draw = nullptr; + callback_post_draw = nullptr; + callback_mouse_down = nullptr; + callback_mouse_up = nullptr; + callback_mouse_move = nullptr; + callback_mouse_scroll = nullptr; + callback_key_down = nullptr; + callback_key_up = nullptr; + + callback_init_data = nullptr; + callback_pre_draw_data = nullptr; + callback_post_draw_data = nullptr; + callback_mouse_down_data = nullptr; + callback_mouse_up_data = nullptr; + callback_mouse_move_data = nullptr; + callback_mouse_scroll_data = nullptr; + callback_key_down_data = nullptr; + callback_key_up_data = nullptr; + +#ifndef IGL_VIEWER_VIEWER_QUIET + const std::string usage(R"(igl::opengl::glfw::Viewer usage: + [drag] Rotate scene + A,a Toggle animation (tight draw loop) + F,f Toggle face based + I,i Toggle invert normals + L,l Toggle wireframe + O,o Toggle orthographic/perspective projection + T,t Toggle filled faces + Z Snap to canonical view + [,] Toggle between rotation control types (trackball, two-axis + valuator with fixed up, 2D mode with no rotation)) + <,> Toggle between models + ; Toggle vertex labels + : Toggle face labels)" +); + std::cout<load(mesh_file_name_string)) + { + return true; + } + } + + // Create new data slot and set to selected + if(!(data().F.rows() == 0 && data().V.rows() == 0)) + { + append_mesh(); + } + data().clear(); + + size_t last_dot = mesh_file_name_string.rfind('.'); + if (last_dot == std::string::npos) + { + std::cerr<<"Error: No file extension found in "<< + mesh_file_name_string<post_load()) + return true; + + return true; + } + + IGL_INLINE bool Viewer::save_mesh_to_file( + const std::string & mesh_file_name_string) + { + // first try to load it with a plugin + for (unsigned int i = 0; isave(mesh_file_name_string)) + return true; + + size_t last_dot = mesh_file_name_string.rfind('.'); + if (last_dot == std::string::npos) + { + // No file type determined + std::cerr<<"Error: No file extension found in "<< + mesh_file_name_string<key_pressed(unicode_key, modifiers)) + { + return true; + } + } + + switch(unicode_key) + { + case 'A': + case 'a': + { + core.is_animating = !core.is_animating; + return true; + } + case 'F': + case 'f': + { + data().set_face_based(!data().face_based); + return true; + } + case 'I': + case 'i': + { + data().dirty |= MeshGL::DIRTY_NORMAL; + data().invert_normals = !data().invert_normals; + return true; + } + case 'L': + case 'l': + { + data().show_lines = !data().show_lines; + return true; + } + case 'O': + case 'o': + { + core.orthographic = !core.orthographic; + return true; + } + case 'T': + case 't': + { + data().show_faces = !data().show_faces; + return true; + } + case 'Z': + { + snap_to_canonical_quaternion(); + return true; + } + case '[': + case ']': + { + if(core.rotation_type == ViewerCore::ROTATION_TYPE_TRACKBALL) + core.set_rotation_type(ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP); + else + core.set_rotation_type(ViewerCore::ROTATION_TYPE_TRACKBALL); + + return true; + } + case '<': + case '>': + { + selected_data_index = + (selected_data_index + data_list.size() + (unicode_key=='>'?1:-1))%data_list.size(); + return true; + } + case ';': + data().show_vertid = !data().show_vertid; + return true; + case ':': + data().show_faceid = !data().show_faceid; + return true; + default: break;//do nothing + } + return false; + } + + IGL_INLINE bool Viewer::key_down(int key,int modifiers) + { + if (callback_key_down) + if (callback_key_down(*this,key,modifiers)) + return true; + for (unsigned int i = 0; ikey_down(key, modifiers)) + return true; + return false; + } + + IGL_INLINE bool Viewer::key_up(int key,int modifiers) + { + if (callback_key_up) + if (callback_key_up(*this,key,modifiers)) + return true; + + for (unsigned int i = 0; ikey_up(key, modifiers)) + return true; + + return false; + } + + IGL_INLINE bool Viewer::mouse_down(MouseButton button,int modifier) + { + // Remember mouse location at down even if used by callback/plugin + down_mouse_x = current_mouse_x; + down_mouse_y = current_mouse_y; + + if (callback_mouse_down) + if (callback_mouse_down(*this,static_cast(button),modifier)) + return true; + + for (unsigned int i = 0; imouse_down(static_cast(button),modifier)) + return true; + + down = true; + + down_translation = core.camera_translation; + + + // Initialization code for the trackball + Eigen::RowVector3d center; + if (data().V.rows() == 0) + { + center << 0,0,0; + }else + { + center = data().V.colwise().sum()/data().V.rows(); + } + + Eigen::Vector3f coord = + igl::project( + Eigen::Vector3f(center(0),center(1),center(2)), + core.view, + core.proj, + core.viewport); + down_mouse_z = coord[2]; + down_rotation = core.trackball_angle; + + mouse_mode = MouseMode::Rotation; + + switch (button) + { + case MouseButton::Left: + if (core.rotation_type == ViewerCore::ROTATION_TYPE_NO_ROTATION) { + mouse_mode = MouseMode::Translation; + } else { + mouse_mode = MouseMode::Rotation; + } + break; + + case MouseButton::Right: + mouse_mode = MouseMode::Translation; + break; + + default: + mouse_mode = MouseMode::None; + break; + } + + return true; + } + + IGL_INLINE bool Viewer::mouse_up(MouseButton button,int modifier) + { + down = false; + + if (callback_mouse_up) + if (callback_mouse_up(*this,static_cast(button),modifier)) + return true; + + for (unsigned int i = 0; imouse_up(static_cast(button),modifier)) + return true; + + mouse_mode = MouseMode::None; + + return true; + } + + IGL_INLINE bool Viewer::mouse_move(int mouse_x,int mouse_y) + { + if(hack_never_moved) + { + down_mouse_x = mouse_x; + down_mouse_y = mouse_y; + hack_never_moved = false; + } + current_mouse_x = mouse_x; + current_mouse_y = mouse_y; + + if (callback_mouse_move) + if (callback_mouse_move(*this,mouse_x,mouse_y)) + return true; + + for (unsigned int i = 0; imouse_move(mouse_x, mouse_y)) + return true; + + if (down) + { + switch (mouse_mode) + { + case MouseMode::Rotation: + { + switch(core.rotation_type) + { + default: + assert(false && "Unknown rotation type"); + case ViewerCore::ROTATION_TYPE_NO_ROTATION: + break; + case ViewerCore::ROTATION_TYPE_TRACKBALL: + igl::trackball( + core.viewport(2), + core.viewport(3), + 2.0f, + down_rotation, + down_mouse_x, + down_mouse_y, + mouse_x, + mouse_y, + core.trackball_angle); + break; + case ViewerCore::ROTATION_TYPE_TWO_AXIS_VALUATOR_FIXED_UP: + igl::two_axis_valuator_fixed_up( + core.viewport(2),core.viewport(3), + 2.0, + down_rotation, + down_mouse_x, down_mouse_y, mouse_x, mouse_y, + core.trackball_angle); + break; + } + //Eigen::Vector4f snapq = core.trackball_angle; + + break; + } + + case MouseMode::Translation: + { + //translation + Eigen::Vector3f pos1 = igl::unproject(Eigen::Vector3f(mouse_x, core.viewport[3] - mouse_y, down_mouse_z), core.view, core.proj, core.viewport); + Eigen::Vector3f pos0 = igl::unproject(Eigen::Vector3f(down_mouse_x, core.viewport[3] - down_mouse_y, down_mouse_z), core.view, core.proj, core.viewport); + + Eigen::Vector3f diff = pos1 - pos0; + core.camera_translation = down_translation + Eigen::Vector3f(diff[0],diff[1],diff[2]); + + break; + } + case MouseMode::Zoom: + { + float delta = 0.001f * (mouse_x - down_mouse_x + mouse_y - down_mouse_y); + core.camera_zoom *= 1 + delta; + down_mouse_x = mouse_x; + down_mouse_y = mouse_y; + break; + } + + default: + break; + } + } + return true; + } + + IGL_INLINE bool Viewer::mouse_scroll(float delta_y) + { + scroll_position += delta_y; + + if (callback_mouse_scroll) + if (callback_mouse_scroll(*this,delta_y)) + return true; + + for (unsigned int i = 0; imouse_scroll(delta_y)) + return true; + + // Only zoom if there's actually a change + if(delta_y != 0) + { + float mult = (1.0+((delta_y>0)?1.:-1.)*0.05); + const float min_zoom = 0.1f; + core.camera_zoom = (core.camera_zoom * mult > min_zoom ? core.camera_zoom * mult : min_zoom); + } + return true; + } + + IGL_INLINE bool Viewer::load_scene() + { + std::string fname = igl::file_dialog_open(); + if(fname.length() == 0) + return false; + return load_scene(fname); + } + + IGL_INLINE bool Viewer::load_scene(std::string fname) + { + igl::deserialize(core,"Core",fname.c_str()); + igl::deserialize(data(),"Data",fname.c_str()); + return true; + } + + IGL_INLINE bool Viewer::save_scene() + { + std::string fname = igl::file_dialog_save(); + if (fname.length() == 0) + return false; + return save_scene(fname); + } + + IGL_INLINE bool Viewer::save_scene(std::string fname) + { + igl::serialize(core,"Core",fname.c_str(),true); + igl::serialize(data(),"Data",fname.c_str()); + + return true; + } + + IGL_INLINE void Viewer::draw() + { + using namespace std; + using namespace Eigen; + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + int width_window, height_window; + glfwGetWindowSize(window, &width_window, &height_window); + + auto highdpi_tmp = width/width_window; + + if(fabs(highdpi_tmp-highdpi)>1e-8) + { + post_resize(width, height); + highdpi=highdpi_tmp; + } + + core.clear_framebuffers(); + if (callback_pre_draw) + { + if (callback_pre_draw(*this)) + { + return; + } + } + for (unsigned int i = 0; ipre_draw()) + { + return; + } + } + for(int i = 0;ipost_draw()) + { + break; + } + } + } + + IGL_INLINE void Viewer::resize(int w,int h) + { + if (window) { + glfwSetWindowSize(window, w/highdpi, h/highdpi); + } + post_resize(w, h); + } + + IGL_INLINE void Viewer::post_resize(int w,int h) + { + core.viewport = Eigen::Vector4f(0,0,w,h); + for (unsigned int i = 0; ipost_resize(w, h); + } + } + + IGL_INLINE void Viewer::snap_to_canonical_quaternion() + { + Eigen::Quaternionf snapq = this->core.trackball_angle; + igl::snap_to_canonical_view_quat(snapq,1.0f,this->core.trackball_angle); + } + + IGL_INLINE void Viewer::open_dialog_load_mesh() + { + std::string fname = igl::file_dialog_open(); + + if (fname.length() == 0) + return; + + this->load_mesh_from_file(fname.c_str()); + } + + IGL_INLINE void Viewer::open_dialog_save_mesh() + { + std::string fname = igl::file_dialog_save(); + + if(fname.length() == 0) + return; + + this->save_mesh_to_file(fname.c_str()); + } + + IGL_INLINE ViewerData& Viewer::data() + { + assert(!data_list.empty() && "data_list should never be empty"); + assert( + (selected_data_index >= 0 && selected_data_index < data_list.size()) && + "selected_data_index should be in bounds"); + return data_list[selected_data_index]; + } + + IGL_INLINE int Viewer::append_mesh() + { + assert(data_list.size() >= 1); + + data_list.emplace_back(); + selected_data_index = data_list.size()-1; + data_list.back().id = next_data_id++; + return data_list.back().id; + } + + IGL_INLINE bool Viewer::erase_mesh(const size_t index) + { + assert((index >= 0 && index < data_list.size()) && "index should be in bounds"); + assert(data_list.size() >= 1); + if(data_list.size() == 1) + { + // Cannot remove last mesh + return false; + } + data_list[index].meshgl.free(); + data_list.erase(data_list.begin() + index); + if(selected_data_index >= index && selected_data_index>0) + { + selected_data_index--; + } + return true; + } + + IGL_INLINE size_t Viewer::mesh_index(const int id) const { + for (size_t i = 0; i < data_list.size(); ++i) + { + if (data_list[i].id == id) + return i; + } + return 0; + } + + +} // end namespace +} // end namespace +} diff --git a/src/igl/opengl/glfw/Viewer.h b/src/igl/opengl/glfw/Viewer.h new file mode 100644 index 000000000..35aa7dfa2 --- /dev/null +++ b/src/igl/opengl/glfw/Viewer.h @@ -0,0 +1,178 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GLFW_VIEWER_H +#define IGL_OPENGL_GLFW_VIEWER_H + +#ifndef IGL_OPENGL_4 +#define IGL_OPENGL_4 +#endif + +#include "../../igl_inline.h" +#include "../MeshGL.h" +#include "../ViewerCore.h" +#include "../ViewerData.h" +#include "ViewerPlugin.h" + +#include +#include + +#include +#include +#include + +#define IGL_MOD_SHIFT 0x0001 +#define IGL_MOD_CONTROL 0x0002 +#define IGL_MOD_ALT 0x0004 +#define IGL_MOD_SUPER 0x0008 + +struct GLFWwindow; + +namespace igl +{ +namespace opengl +{ +namespace glfw +{ + // GLFW-based mesh viewer + class Viewer + { + public: + // UI Enumerations + enum class MouseButton {Left, Middle, Right}; + enum class MouseMode { None, Rotation, Zoom, Pan, Translation} mouse_mode; + IGL_INLINE int launch(bool resizable = true,bool fullscreen = false); + IGL_INLINE int launch_init(bool resizable = true,bool fullscreen = false); + IGL_INLINE bool launch_rendering(bool loop = true); + IGL_INLINE void launch_shut(); + IGL_INLINE void init(); + IGL_INLINE void init_plugins(); + IGL_INLINE void shutdown_plugins(); + Viewer(); + ~Viewer(); + // Mesh IO + IGL_INLINE bool load_mesh_from_file(const std::string & mesh_file_name); + IGL_INLINE bool save_mesh_to_file(const std::string & mesh_file_name); + // Callbacks + IGL_INLINE bool key_pressed(unsigned int unicode_key,int modifier); + IGL_INLINE bool key_down(int key,int modifier); + IGL_INLINE bool key_up(int key,int modifier); + IGL_INLINE bool mouse_down(MouseButton button,int modifier); + IGL_INLINE bool mouse_up(MouseButton button,int modifier); + IGL_INLINE bool mouse_move(int mouse_x,int mouse_y); + IGL_INLINE bool mouse_scroll(float delta_y); + // Scene IO + IGL_INLINE bool load_scene(); + IGL_INLINE bool load_scene(std::string fname); + IGL_INLINE bool save_scene(); + IGL_INLINE bool save_scene(std::string fname); + // Draw everything + IGL_INLINE void draw(); + // OpenGL context resize + IGL_INLINE void resize(int w,int h); // explicitly set window size + IGL_INLINE void post_resize(int w,int h); // external resize due to user interaction + // Helper functions + IGL_INLINE void snap_to_canonical_quaternion(); + IGL_INLINE void open_dialog_load_mesh(); + IGL_INLINE void open_dialog_save_mesh(); + IGL_INLINE ViewerData& data(); + + // Append a new "slot" for a mesh (i.e., create empty entires at the end of + // the data_list and opengl_state_list. + // + // Returns the id of the last appended mesh + // + // Side Effects: + // selected_data_index is set this newly created, last entry (i.e., + // #meshes-1) + IGL_INLINE int append_mesh(); + + // Erase a mesh (i.e., its corresponding data and state entires in data_list + // and opengl_state_list) + // + // Inputs: + // index index of mesh to erase + // Returns whether erasure was successful <=> cannot erase last mesh + // + // Side Effects: + // If selected_data_index is greater than or equal to index then it is + // decremented + // Example: + // // Erase all mesh slots except first and clear remaining mesh + // viewer.selected_data_index = viewer.data_list.size()-1; + // while(viewer.erase_mesh(viewer.selected_data_index)){}; + // viewer.data().clear(); + // + IGL_INLINE bool erase_mesh(const size_t index); + + // Retrieve mesh index from its unique identifier + // Returns 0 if not found + IGL_INLINE size_t mesh_index(const int id) const; + + // Alec: I call this data_list instead of just data to avoid confusion with + // old "data" variable. + // Stores all the data that should be visualized + std::vector data_list; + + size_t selected_data_index; + int next_data_id; + GLFWwindow* window; + // Stores all the viewing options + ViewerCore core; + // List of registered plugins + std::vector plugins; + // Temporary data stored when the mouse button is pressed + Eigen::Quaternionf down_rotation; + int current_mouse_x; + int current_mouse_y; + int down_mouse_x; + int down_mouse_y; + float down_mouse_z; + Eigen::Vector3f down_translation; + bool down; + bool hack_never_moved; + // Keep track of the global position of the scrollwheel + float scroll_position; + // C++-style functions + // + // Returns **true** if action should be cancelled. + std::function callback_init; + std::function callback_pre_draw; + std::function callback_post_draw; + std::function callback_mouse_down; + std::function callback_mouse_up; + std::function callback_mouse_move; + std::function callback_mouse_scroll; + std::function callback_key_pressed; + // THESE SHOULD BE DEPRECATED: + std::function callback_key_down; + std::function callback_key_up; + // Pointers to per-callback data + void* callback_init_data; + void* callback_pre_draw_data; + void* callback_post_draw_data; + void* callback_mouse_down_data; + void* callback_mouse_up_data; + void* callback_mouse_move_data; + void* callback_mouse_scroll_data; + void* callback_key_pressed_data; + void* callback_key_down_data; + void* callback_key_up_data; + + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + }; + +} // end namespace +} // end namespace +} // end namespace + +#ifndef IGL_STATIC_LIBRARY +# include "Viewer.cpp" +#endif + +#endif diff --git a/src/igl/opengl/glfw/ViewerPlugin.h b/src/igl/opengl/glfw/ViewerPlugin.h new file mode 100644 index 000000000..9ba7903a8 --- /dev/null +++ b/src/igl/opengl/glfw/ViewerPlugin.h @@ -0,0 +1,182 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GLFW_VIEWERPLUGIN_H +#define IGL_OPENGL_GLFW_VIEWERPLUGIN_H + +// TODO: +// * create plugins/skeleton.h +// * pass time in draw function +// * remove Preview3D from comments +// * clean comments +#include +#include +#include + +namespace igl +{ +namespace opengl +{ +namespace glfw +{ + +// Abstract class for plugins +// All plugins MUST have this class as their parent and may implement any/all +// the callbacks marked `virtual` here. +// +// /////For an example of a basic plugins see plugins/skeleton.h +// +// Return value of callbacks: returning true to any of the callbacks tells +// Viewer that the event has been handled and that it should not be passed to +// other plugins or to other internal functions of Viewer + +// Forward declaration of the viewer +class Viewer; + +class ViewerPlugin +{ +public: + IGL_INLINE ViewerPlugin() + {plugin_name = "dummy";} + + virtual ~ViewerPlugin(){} + + // This function is called when the viewer is initialized (no mesh will be loaded at this stage) + IGL_INLINE virtual void init(Viewer *_viewer) + { + viewer = _viewer; + } + + // This function is called before shutdown + IGL_INLINE virtual void shutdown() + { + } + + // This function is called before a mesh is loaded + IGL_INLINE virtual bool load(std::string filename) + { + return false; + } + + // This function is called before a mesh is saved + IGL_INLINE virtual bool save(std::string filename) + { + return false; + } + + // This function is called when the scene is serialized + IGL_INLINE virtual bool serialize(std::vector& buffer) const + { + return false; + } + + // This function is called when the scene is deserialized + IGL_INLINE virtual bool deserialize(const std::vector& buffer) + { + return false; + } + + // Runs immediately after a new mesh has been loaded. + IGL_INLINE virtual bool post_load() + { + return false; + } + + // This function is called before the draw procedure of Preview3D + IGL_INLINE virtual bool pre_draw() + { + return false; + } + + // This function is called after the draw procedure of Preview3D + IGL_INLINE virtual bool post_draw() + { + return false; + } + + // This function is called after the window has been resized + IGL_INLINE virtual void post_resize(int w, int h) + { + } + + // This function is called when the mouse button is pressed + // - button can be GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON + // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT; + IGL_INLINE virtual bool mouse_down(int button, int modifier) + { + return false; + } + + // This function is called when the mouse button is released + // - button can be GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON + // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT; + IGL_INLINE virtual bool mouse_up(int button, int modifier) + { + return false; + } + + // This function is called every time the mouse is moved + // - mouse_x and mouse_y are the new coordinates of the mouse pointer in screen coordinates + IGL_INLINE virtual bool mouse_move(int mouse_x, int mouse_y) + { + return false; + } + + // This function is called every time the scroll wheel is moved + // Note: this callback is not working with every glut implementation + IGL_INLINE virtual bool mouse_scroll(float delta_y) + { + return false; + } + + // This function is called when a keyboard key is pressed. Unlike key_down + // this will reveal the actual character being sent (not just the physical + // key) + // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT; + IGL_INLINE virtual bool key_pressed(unsigned int key, int modifiers) + { + return false; + } + + // This function is called when a keyboard key is down + // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT; + IGL_INLINE virtual bool key_down(int key, int modifiers) + { + return false; + } + + // This function is called when a keyboard key is release + // - modifiers is a bitfield that might one or more of the following bits Preview3D::NO_KEY, Preview3D::SHIFT, Preview3D::CTRL, Preview3D::ALT; + IGL_INLINE virtual bool key_up(int key, int modifiers) + { + return false; + } + + std::string plugin_name; +protected: + // Pointer to the main Viewer class + Viewer *viewer; +}; + +namespace serialization +{ + inline void serialize(const ViewerPlugin& obj,std::vector& buffer) + { + obj.serialize(buffer); + } + + inline void deserialize(ViewerPlugin& obj,const std::vector& buffer) + { + obj.deserialize(buffer); + } +} + +} +} +} + +#endif diff --git a/src/igl/opengl/glfw/background_window.cpp b/src/igl/opengl/glfw/background_window.cpp new file mode 100644 index 000000000..964202cc2 --- /dev/null +++ b/src/igl/opengl/glfw/background_window.cpp @@ -0,0 +1,30 @@ +#include "background_window.h" + +#include + +IGL_INLINE bool igl::opengl::glfw::background_window(GLFWwindow* & window) +{ + if(!glfwInit()) return false; + glfwSetErrorCallback([](int id,const char* m){std::cerr< + +namespace igl +{ + namespace opengl + { + namespace glfw + { + // Create a background window with a valid core profile opengl context + // set to current. + // + // After you're finished with this window you may call + // `glfwDestroyWindow(window)` + // + // After you're finished with glfw you should call `glfwTerminate()` + // + // Outputs: + // window pointer to glfw window + // Returns true iff success + IGL_INLINE bool background_window(GLFWwindow* & window); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "background_window.cpp" +#endif + +#endif diff --git a/src/igl/opengl/glfw/imgui/ImGuiHelpers.h b/src/igl/opengl/glfw/imgui/ImGuiHelpers.h new file mode 100644 index 000000000..892274b0f --- /dev/null +++ b/src/igl/opengl/glfw/imgui/ImGuiHelpers.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Jérémie Dumas +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GLFW_IMGUI_IMGUIHELPERS_H +#define IGL_OPENGL_GLFW_IMGUI_IMGUIHELPERS_H + +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +//////////////////////////////////////////////////////////////////////////////// + +// Extend ImGui by populating its namespace directly +// +// Code snippets taken from there: +// https://eliasdaler.github.io/using-imgui-with-sfml-pt2/ +namespace ImGui +{ + +static auto vector_getter = [](void* vec, int idx, const char** out_text) +{ + auto& vector = *static_cast*>(vec); + if (idx < 0 || idx >= static_cast(vector.size())) { return false; } + *out_text = vector.at(idx).c_str(); + return true; +}; + +inline bool Combo(const char* label, int* idx, std::vector& values) +{ + if (values.empty()) { return false; } + return Combo(label, idx, vector_getter, + static_cast(&values), values.size()); +} + +inline bool Combo(const char* label, int* idx, std::function getter, int items_count) +{ + auto func = [](void* data, int i, const char** out_text) { + auto &getter = *reinterpret_cast *>(data); + const char *s = getter(i); + if (s) { *out_text = s; return true; } + else { return false; } + }; + return Combo(label, idx, func, reinterpret_cast(&getter), items_count); +} + +inline bool ListBox(const char* label, int* idx, std::vector& values) +{ + if (values.empty()) { return false; } + return ListBox(label, idx, vector_getter, + static_cast(&values), values.size()); +} + +inline bool InputText(const char* label, std::string &str, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL) +{ + char buf[1024]; + std::fill_n(buf, 1024, 0); + std::copy_n(str.begin(), std::min(1024, (int) str.size()), buf); + if (ImGui::InputText(label, buf, 1024, flags, callback, user_data)) + { + str = std::string(buf); + return true; + } + return false; +} + +} // namespace ImGui + +#endif // IGL_OPENGL_GLFW_IMGUI_IMGUIHELPERS_H diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.cpp b/src/igl/opengl/glfw/imgui/ImGuiMenu.cpp new file mode 100644 index 000000000..627e055cb --- /dev/null +++ b/src/igl/opengl/glfw/imgui/ImGuiMenu.cpp @@ -0,0 +1,385 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Jérémie Dumas +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +//////////////////////////////////////////////////////////////////////////////// +#include "ImGuiMenu.h" +#include +#include +#include +#include +#include +#include +//////////////////////////////////////////////////////////////////////////////// + +namespace igl +{ +namespace opengl +{ +namespace glfw +{ +namespace imgui +{ + +IGL_INLINE void ImGuiMenu::init(igl::opengl::glfw::Viewer *_viewer) +{ + ViewerPlugin::init(_viewer); + // Setup ImGui binding + if (_viewer) + { + if (context_ == nullptr) + { + context_ = ImGui::CreateContext(); + } + ImGui_ImplGlfwGL3_Init(viewer->window, false); + ImGui::GetIO().IniFilename = nullptr; + ImGui::StyleColorsDark(); + ImGuiStyle& style = ImGui::GetStyle(); + style.FrameRounding = 5.0f; + reload_font(); + } +} + +IGL_INLINE void ImGuiMenu::reload_font(int font_size) +{ + hidpi_scaling_ = hidpi_scaling(); + pixel_ratio_ = pixel_ratio(); + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + io.Fonts->AddFontFromMemoryCompressedTTF(droid_sans_compressed_data, + droid_sans_compressed_size, font_size * hidpi_scaling_); + io.FontGlobalScale = 1.0 / pixel_ratio_; +} + +IGL_INLINE void ImGuiMenu::shutdown() +{ + // Cleanup + ImGui_ImplGlfwGL3_Shutdown(); + ImGui::DestroyContext(context_); + context_ = nullptr; +} + +IGL_INLINE bool ImGuiMenu::pre_draw() +{ + glfwPollEvents(); + + // Check whether window dpi has changed + float scaling = hidpi_scaling(); + if (std::abs(scaling - hidpi_scaling_) > 1e-5) + { + reload_font(); + ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); + } + + ImGui_ImplGlfwGL3_NewFrame(); + return false; +} + +IGL_INLINE bool ImGuiMenu::post_draw() +{ + draw_menu(); + ImGui::Render(); + return false; +} + +IGL_INLINE void ImGuiMenu::post_resize(int width, int height) +{ + if (context_) + { + ImGui::GetIO().DisplaySize.x = float(width); + ImGui::GetIO().DisplaySize.y = float(height); + } +} + +// Mouse IO +IGL_INLINE bool ImGuiMenu::mouse_down(int button, int modifier) +{ + ImGui_ImplGlfwGL3_MouseButtonCallback(viewer->window, button, GLFW_PRESS, modifier); + return ImGui::GetIO().WantCaptureMouse; +} + +IGL_INLINE bool ImGuiMenu::mouse_up(int button, int modifier) +{ + return ImGui::GetIO().WantCaptureMouse; +} + +IGL_INLINE bool ImGuiMenu::mouse_move(int mouse_x, int mouse_y) +{ + return ImGui::GetIO().WantCaptureMouse; +} + +IGL_INLINE bool ImGuiMenu::mouse_scroll(float delta_y) +{ + ImGui_ImplGlfwGL3_ScrollCallback(viewer->window, 0.f, delta_y); + return ImGui::GetIO().WantCaptureMouse; +} + +// Keyboard IO +IGL_INLINE bool ImGuiMenu::key_pressed(unsigned int key, int modifiers) +{ + ImGui_ImplGlfwGL3_CharCallback(nullptr, key); + return ImGui::GetIO().WantCaptureKeyboard; +} + +IGL_INLINE bool ImGuiMenu::key_down(int key, int modifiers) +{ + ImGui_ImplGlfwGL3_KeyCallback(viewer->window, key, 0, GLFW_PRESS, modifiers); + return ImGui::GetIO().WantCaptureKeyboard; +} + +IGL_INLINE bool ImGuiMenu::key_up(int key, int modifiers) +{ + ImGui_ImplGlfwGL3_KeyCallback(viewer->window, key, 0, GLFW_RELEASE, modifiers); + return ImGui::GetIO().WantCaptureKeyboard; +} + +// Draw menu +IGL_INLINE void ImGuiMenu::draw_menu() +{ + // Text labels + draw_labels_window(); + + // Viewer settings + if (callback_draw_viewer_window) { callback_draw_viewer_window(); } + else { draw_viewer_window(); } + + // Other windows + if (callback_draw_custom_window) { callback_draw_custom_window(); } + else { draw_custom_window(); } +} + +IGL_INLINE void ImGuiMenu::draw_viewer_window() +{ + float menu_width = 180.f * menu_scaling(); + ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints(ImVec2(menu_width, -1.0f), ImVec2(menu_width, -1.0f)); + bool _viewer_menu_visible = true; + ImGui::Begin( + "Viewer", &_viewer_menu_visible, + ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_AlwaysAutoResize + ); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.4f); + if (callback_draw_viewer_menu) { callback_draw_viewer_menu(); } + else { draw_viewer_menu(); } + ImGui::PopItemWidth(); + ImGui::End(); +} + +IGL_INLINE void ImGuiMenu::draw_viewer_menu() +{ + // Workspace + if (ImGui::CollapsingHeader("Workspace", ImGuiTreeNodeFlags_DefaultOpen)) + { + float w = ImGui::GetContentRegionAvailWidth(); + float p = ImGui::GetStyle().FramePadding.x; + if (ImGui::Button("Load##Workspace", ImVec2((w-p)/2.f, 0))) + { + viewer->load_scene(); + } + ImGui::SameLine(0, p); + if (ImGui::Button("Save##Workspace", ImVec2((w-p)/2.f, 0))) + { + viewer->save_scene(); + } + } + + // Mesh + if (ImGui::CollapsingHeader("Mesh", ImGuiTreeNodeFlags_DefaultOpen)) + { + float w = ImGui::GetContentRegionAvailWidth(); + float p = ImGui::GetStyle().FramePadding.x; + if (ImGui::Button("Load##Mesh", ImVec2((w-p)/2.f, 0))) + { + viewer->open_dialog_load_mesh(); + } + ImGui::SameLine(0, p); + if (ImGui::Button("Save##Mesh", ImVec2((w-p)/2.f, 0))) + { + viewer->open_dialog_save_mesh(); + } + } + + // Viewing options + if (ImGui::CollapsingHeader("Viewing Options", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::Button("Center object", ImVec2(-1, 0))) + { + viewer->core.align_camera_center(viewer->data().V, viewer->data().F); + } + if (ImGui::Button("Snap canonical view", ImVec2(-1, 0))) + { + viewer->snap_to_canonical_quaternion(); + } + + // Zoom + ImGui::PushItemWidth(80 * menu_scaling()); + ImGui::DragFloat("Zoom", &(viewer->core.camera_zoom), 0.05f, 0.1f, 20.0f); + + // Select rotation type + int rotation_type = static_cast(viewer->core.rotation_type); + static Eigen::Quaternionf trackball_angle = Eigen::Quaternionf::Identity(); + static bool orthographic = true; + if (ImGui::Combo("Camera Type", &rotation_type, "Trackball\0Two Axes\0002D Mode\0\0")) + { + using RT = igl::opengl::ViewerCore::RotationType; + auto new_type = static_cast(rotation_type); + if (new_type != viewer->core.rotation_type) + { + if (new_type == RT::ROTATION_TYPE_NO_ROTATION) + { + trackball_angle = viewer->core.trackball_angle; + orthographic = viewer->core.orthographic; + viewer->core.trackball_angle = Eigen::Quaternionf::Identity(); + viewer->core.orthographic = true; + } + else if (viewer->core.rotation_type == RT::ROTATION_TYPE_NO_ROTATION) + { + viewer->core.trackball_angle = trackball_angle; + viewer->core.orthographic = orthographic; + } + viewer->core.set_rotation_type(new_type); + } + } + + // Orthographic view + ImGui::Checkbox("Orthographic view", &(viewer->core.orthographic)); + ImGui::PopItemWidth(); + } + + // Draw options + if (ImGui::CollapsingHeader("Draw Options", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::Checkbox("Face-based", &(viewer->data().face_based))) + { + viewer->data().set_face_based(viewer->data().face_based); + } + ImGui::Checkbox("Show texture", &(viewer->data().show_texture)); + if (ImGui::Checkbox("Invert normals", &(viewer->data().invert_normals))) + { + viewer->data().dirty |= igl::opengl::MeshGL::DIRTY_NORMAL; + } + ImGui::Checkbox("Show overlay", &(viewer->data().show_overlay)); + ImGui::Checkbox("Show overlay depth", &(viewer->data().show_overlay_depth)); + ImGui::ColorEdit4("Background", viewer->core.background_color.data(), + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_PickerHueWheel); + ImGui::ColorEdit4("Line color", viewer->data().line_color.data(), + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_PickerHueWheel); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f); + ImGui::DragFloat("Shininess", &(viewer->data().shininess), 0.05f, 0.0f, 100.0f); + ImGui::PopItemWidth(); + } + + // Overlays + if (ImGui::CollapsingHeader("Overlays", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Wireframe", &(viewer->data().show_lines)); + ImGui::Checkbox("Fill", &(viewer->data().show_faces)); + ImGui::Checkbox("Show vertex labels", &(viewer->data().show_vertid)); + ImGui::Checkbox("Show faces labels", &(viewer->data().show_faceid)); + } +} + +IGL_INLINE void ImGuiMenu::draw_labels_window() +{ + // Text labels + ImGui::SetNextWindowPos(ImVec2(0,0), ImGuiSetCond_Always); + ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize, ImGuiSetCond_Always); + bool visible = true; + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::Begin("ViewerLabels", &visible, + ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoScrollWithMouse + | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_NoInputs); + for (const auto & data : viewer->data_list) + { + draw_labels(data); + } + ImGui::End(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); +} + +IGL_INLINE void ImGuiMenu::draw_labels(const igl::opengl::ViewerData &data) +{ + if (data.show_vertid) + { + for (int i = 0; i < data.V.rows(); ++i) + { + draw_text(data.V.row(i), data.V_normals.row(i), std::to_string(i)); + } + } + + if (data.show_faceid) + { + for (int i = 0; i < data.F.rows(); ++i) + { + Eigen::RowVector3d p = Eigen::RowVector3d::Zero(); + for (int j = 0; j < data.F.cols(); ++j) + { + p += data.V.row(data.F(i,j)); + } + p /= (double) data.F.cols(); + + draw_text(p, data.F_normals.row(i), std::to_string(i)); + } + } + + if (data.labels_positions.rows() > 0) + { + for (int i = 0; i < data.labels_positions.rows(); ++i) + { + draw_text(data.labels_positions.row(i), Eigen::Vector3d(0.0,0.0,0.0), + data.labels_strings[i]); + } + } +} + +IGL_INLINE void ImGuiMenu::draw_text(Eigen::Vector3d pos, Eigen::Vector3d normal, const std::string &text) +{ + pos += normal * 0.005f * viewer->core.object_scale; + Eigen::Vector3f coord = igl::project(Eigen::Vector3f(pos.cast()), + viewer->core.view, viewer->core.proj, viewer->core.viewport); + + // Draw text labels slightly bigger than normal text + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize() * 1.2, + ImVec2(coord[0]/pixel_ratio_, (viewer->core.viewport[3] - coord[1])/pixel_ratio_), + ImGui::GetColorU32(ImVec4(0, 0, 10, 255)), + &text[0], &text[0] + text.size()); +} + +IGL_INLINE float ImGuiMenu::pixel_ratio() +{ + // Computes pixel ratio for hidpi devices + int buf_size[2]; + int win_size[2]; + GLFWwindow* window = glfwGetCurrentContext(); + glfwGetFramebufferSize(window, &buf_size[0], &buf_size[1]); + glfwGetWindowSize(window, &win_size[0], &win_size[1]); + return (float) buf_size[0] / (float) win_size[0]; +} + +IGL_INLINE float ImGuiMenu::hidpi_scaling() +{ + // Computes scaling factor for hidpi devices + float xscale, yscale; + GLFWwindow* window = glfwGetCurrentContext(); + glfwGetWindowContentScale(window, &xscale, &yscale); + return 0.5 * (xscale + yscale); +} + +} // end namespace +} // end namespace +} // end namespace +} // end namespace diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.h b/src/igl/opengl/glfw/imgui/ImGuiMenu.h new file mode 100644 index 000000000..e7144a675 --- /dev/null +++ b/src/igl/opengl/glfw/imgui/ImGuiMenu.h @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Jérémie Dumas +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GLFW_IMGUI_IMGUIMENU_H +#define IGL_OPENGL_GLFW_IMGUI_IMGUIMENU_H + +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +//////////////////////////////////////////////////////////////////////////////// + +// Forward declarations +struct ImGuiContext; + +namespace igl +{ +namespace opengl +{ +namespace glfw +{ +namespace imgui +{ + +class ImGuiMenu : public igl::opengl::glfw::ViewerPlugin +{ +protected: + // Hidpi scaling to be used for text rendering. + float hidpi_scaling_; + + // Ratio between the framebuffer size and the window size. + // May be different from the hipdi scaling! + float pixel_ratio_; + + // ImGui Context + ImGuiContext * context_ = nullptr; + +public: + IGL_INLINE virtual void init(igl::opengl::glfw::Viewer *_viewer) override; + + IGL_INLINE virtual void reload_font(int font_size = 13); + + IGL_INLINE virtual void shutdown() override; + + IGL_INLINE virtual bool pre_draw() override; + + IGL_INLINE virtual bool post_draw() override; + + IGL_INLINE virtual void post_resize(int width, int height) override; + + // Mouse IO + IGL_INLINE virtual bool mouse_down(int button, int modifier) override; + + IGL_INLINE virtual bool mouse_up(int button, int modifier) override; + + IGL_INLINE virtual bool mouse_move(int mouse_x, int mouse_y) override; + + IGL_INLINE virtual bool mouse_scroll(float delta_y) override; + + // Keyboard IO + IGL_INLINE virtual bool key_pressed(unsigned int key, int modifiers) override; + + IGL_INLINE virtual bool key_down(int key, int modifiers) override; + + IGL_INLINE virtual bool key_up(int key, int modifiers) override; + + // Draw menu + IGL_INLINE virtual void draw_menu(); + + // Can be overwritten by `callback_draw_viewer_window` + IGL_INLINE virtual void draw_viewer_window(); + + // Can be overwritten by `callback_draw_viewer_menu` + IGL_INLINE virtual void draw_viewer_menu(); + + // Can be overwritten by `callback_draw_custom_window` + IGL_INLINE virtual void draw_custom_window() { } + + // Easy-to-customize callbacks + std::function callback_draw_viewer_window; + std::function callback_draw_viewer_menu; + std::function callback_draw_custom_window; + + IGL_INLINE void draw_labels_window(); + + IGL_INLINE void draw_labels(const igl::opengl::ViewerData &data); + + IGL_INLINE void draw_text(Eigen::Vector3d pos, Eigen::Vector3d normal, const std::string &text); + + IGL_INLINE float pixel_ratio(); + + IGL_INLINE float hidpi_scaling(); + + float menu_scaling() { return hidpi_scaling_ / pixel_ratio_; } +}; + +} // end namespace +} // end namespace +} // end namespace +} // end namespace + +#ifndef IGL_STATIC_LIBRARY +# include "ImGuiMenu.cpp" +#endif + +#endif // IGL_OPENGL_GLFW_IMGUI_IMGUIMENU_H diff --git a/src/igl/opengl/glfw/map_texture.cpp b/src/igl/opengl/glfw/map_texture.cpp new file mode 100644 index 000000000..7975d87d4 --- /dev/null +++ b/src/igl/opengl/glfw/map_texture.cpp @@ -0,0 +1,211 @@ +#ifdef IGL_OPENGL_4 + +#include "map_texture.h" +#include "background_window.h" +#include "../create_shader_program.h" + +#include "../gl.h" +#include + +#include +#include + +template +IGL_INLINE bool igl::opengl::glfw::map_texture( + const Eigen::MatrixBase & _V, + const Eigen::MatrixBase & _F, + const Eigen::MatrixBase & _U, + const unsigned char * in_data, + const int w, + const int h, + const int nc, + std::vector & out_data) +{ + int out_w = w; + int out_h = h; + int out_nc = nc; + return map_texture(_V,_F,_U,in_data,w,h,nc,out_data,out_w,out_h,out_nc); +} + + +template +IGL_INLINE bool igl::opengl::glfw::map_texture( + const Eigen::MatrixBase & _V, + const Eigen::MatrixBase & _F, + const Eigen::MatrixBase & _U, + const unsigned char * in_data, + const int w, + const int h, + const int nc, + std::vector & out_data, + int & out_w, + int & out_h, + int & out_nc) +{ + const auto fail = [](const std::string msg) + { + std::cerr< V = _V.template cast(); + Eigen::Matrix< + double, + DerivedU::RowsAtCompileTime, + DerivedU::ColsAtCompileTime, + Eigen::RowMajor> U = _U.template cast(); + Eigen::Matrix< + int, + DerivedF::RowsAtCompileTime, + DerivedF::ColsAtCompileTime, + Eigen::RowMajor> F = _F.template cast(); + const int dim = U.cols(); + GLFWwindow * window; + if(!background_window(window)) + { + fail("Could not initialize glfw window"); + } + + // Compile each shader + std::string vertex_shader = dim == 2 ? + R"( +#version 330 core +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 tex_coord_v; +out vec2 tex_coord_f; +void main() +{ + tex_coord_f = vec2(tex_coord_v.x,1.-tex_coord_v.y); + gl_Position = vec4( 2.*position.x-1., 2.*(1.-position.y)-1., 0.,1.); +} +)" + : + R"( +#version 330 core +layout(location = 0) in vec3 position; +layout(location = 1) in vec2 tex_coord_v; +out vec2 tex_coord_f; +void main() +{ + tex_coord_f = vec2(tex_coord_v.x,1.-tex_coord_v.y); + gl_Position = vec4( 2.*position.x-1., 2.*(1.-position.y)-1., position.z,1.); +} +)" + ; + std::string fragment_shader = R"( +#version 330 core +layout(location = 0) out vec3 color; +uniform sampler2D tex; +in vec2 tex_coord_f; +void main() +{ + color = texture(tex,tex_coord_f).rgb; +} +)"; + GLuint prog_id = + igl::opengl::create_shader_program(vertex_shader,fragment_shader,{}); + glUniform1i(glGetUniformLocation(prog_id, "tex"),0); + // Generate and attach buffers to vertex array + glDisable(GL_CULL_FACE); + GLuint VAO = 0; + glGenVertexArrays(1,&VAO); + glBindVertexArray(VAO); + GLuint ibo,vbo,tbo; + glGenBuffers(1,&ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*F.size(), F.data(), GL_STATIC_DRAW); + glGenBuffers(1,&vbo); + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER,vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(double)*U.size(), U.data(), GL_STATIC_DRAW); + glVertexAttribLPointer(0, U.cols(), GL_DOUBLE, U.cols() * sizeof(GLdouble), (GLvoid*)0); + glGenBuffers(1,&tbo); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER,tbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(double)*V.size(), V.data(), GL_STATIC_DRAW); + glVertexAttribLPointer(1, V.cols(), GL_DOUBLE, V.cols() * sizeof(GLdouble), (GLvoid*)0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + // Prepare texture + GLuint in_tex; + GLenum format; + { + format = nc==1 ? GL_RED : (nc==3 ? GL_RGB : (nc == 4 ? GL_RGBA : GL_FALSE)); + glGenTextures(1, &in_tex); + glBindTexture(GL_TEXTURE_2D, in_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, w, h, 0,format, GL_UNSIGNED_BYTE, in_data); + } + // Prepare framebuffer + GLuint fb = 0; + glGenFramebuffers(1, &fb); + glBindFramebuffer(GL_FRAMEBUFFER, fb); + GLuint out_tex; + glGenTextures(1, &out_tex); + glBindTexture(GL_TEXTURE_2D, out_tex); + // always use float for internal storage + assert(out_nc == 3); + glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, out_w, out_h, 0,GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, out_tex, 0); + { + GLenum bufs[1] = {GL_COLOR_ATTACHMENT0}; + glDrawBuffers(1, bufs); + } + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + fail("framebuffer setup failed."); + } + glBindFramebuffer(GL_FRAMEBUFFER, fb); + // clear screen and set viewport + glClearColor(0.0,1.0,0.0,0.); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0,0,out_w,out_h); + // Attach shader program + glUseProgram(prog_id); + glActiveTexture(GL_TEXTURE0 + 0); + glBindTexture(GL_TEXTURE_2D, in_tex); + // Draw mesh as wireframe + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, F.size(), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + // Write into memory + assert(out_nc == 3); + out_data.resize(out_nc*out_w*out_h); + glBindTexture(GL_TEXTURE_2D, out_tex); + glGetTexImage(GL_TEXTURE_2D, 0, format, GL_UNSIGNED_BYTE, &out_data[0]); + // OpenGL cleanup + glDeleteBuffers(1,&fb); + glDeleteBuffers(1,&ibo); + glDeleteBuffers(1,&vbo); + glDeleteBuffers(1,&tbo); + glDeleteTextures(1,&in_tex); + glDeleteTextures(1,&out_tex); + glDeleteVertexArrays(1,&VAO); + glUseProgram(0); + glDeleteProgram(prog_id); + // GLFW cleanup + glfwDestroyWindow(window); + glfwTerminate(); + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::opengl::glfw::map_texture, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, unsigned char const*, int, int, int, std::vector >&); +#endif + +#endif // IGL_OPENGL_4 diff --git a/src/igl/opengl/glfw/map_texture.h b/src/igl/opengl/glfw/map_texture.h new file mode 100644 index 000000000..ee3b30a45 --- /dev/null +++ b/src/igl/opengl/glfw/map_texture.h @@ -0,0 +1,63 @@ +#ifndef IGL_OPENGL_GLFW_MAP_TEXTURE_H +#define IGL_OPENGL_GLFW_MAP_TEXTURE_H + +#ifdef IGL_OPENGL_4 + +#include "../../igl_inline.h" +#include +#include + +namespace igl +{ + namespace opengl + { + namespace glfw + { + // Given a mesh (V,F) in [0,1]² and new positions (U) and a texture image + // (in_data), _render_ a new image (out_data) of the same size. + // Inputs: + // V #V by 2 list of undeformed mesh vertex positions (matching texture) + // F #F by 3 list of mesh triangle indices into V + // U #U by 2 list of deformed vertex positions + // in_data w*h*nc array of color values, channels, then columns, then + // rows (e.g., what stbi_image returns and expects) + // w width + // h height + // nc number of channels + // Outputs: + // out_data h*w*nc list of output colors in same order as input + // + template + IGL_INLINE bool map_texture( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & U, + const unsigned char * in_data, + const int w, + const int h, + const int nc, + std::vector & out_data); + template + IGL_INLINE bool map_texture( + const Eigen::MatrixBase & _V, + const Eigen::MatrixBase & _F, + const Eigen::MatrixBase & _U, + const unsigned char * in_data, + const int w, + const int h, + const int nc, + std::vector & out_data, + int & out_w, + int & out_h, + int & out_nc); + } + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "map_texture.cpp" +#endif + +#endif // IGL_OPENGL_4 + +#endif diff --git a/src/igl/opengl/init_render_to_texture.cpp b/src/igl/opengl/init_render_to_texture.cpp new file mode 100644 index 000000000..7fbcfea10 --- /dev/null +++ b/src/igl/opengl/init_render_to_texture.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "init_render_to_texture.h" +#include "gl.h" +#include + +IGL_INLINE void igl::opengl::init_render_to_texture( + const size_t width, + const size_t height, + const bool depth_texture, + GLuint & tex_id, + GLuint & fbo_id, + GLuint & d_id) +{ + using namespace std; + // http://www.opengl.org/wiki/Framebuffer_Object_Examples#Quick_example.2C_render_to_texture_.282D.29 + const auto & gen_tex = [](GLuint & tex_id) + { + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + }; + gen_tex(tex_id); + //NULL means reserve texture memory, but texels are undefined + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_BGRA, GL_FLOAT, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &fbo_id); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); + //Attach 2D texture to this FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_id, 0); + if(depth_texture) + { + // Generate a depth texture + gen_tex(d_id); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_DEPTH_COMPONENT32, + width, + height, + 0, + GL_DEPTH_COMPONENT, + GL_FLOAT, + NULL); + glFramebufferTexture2D( + GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,d_id,0); + }else + { + // Attach a depth buffer + glGenRenderbuffers(1, &d_id); + glBindRenderbuffer(GL_RENDERBUFFER, d_id); + glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT24,width,height); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,d_id); + } + + //Does the GPU support current FBO configuration? + GLenum status; + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + assert(status == GL_FRAMEBUFFER_COMPLETE); + // Unbind to clean up + if(!depth_texture) + { + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +IGL_INLINE void igl::opengl::init_render_to_texture( + const size_t width, + const size_t height, + GLuint & tex_id, + GLuint & fbo_id, + GLuint & dfbo_id) +{ + return init_render_to_texture(width,height,false,tex_id,fbo_id,dfbo_id); +} diff --git a/src/igl/opengl/init_render_to_texture.h b/src/igl/opengl/init_render_to_texture.h new file mode 100644 index 000000000..ae854e770 --- /dev/null +++ b/src/igl/opengl/init_render_to_texture.h @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_INIT_RENDER_TO_TEXTURE_H +#define IGL_OPENGL_INIT_RENDER_TO_TEXTURE_H +#include "../igl_inline.h" +#include "gl.h" +#include +namespace igl +{ + namespace opengl + { + // Create a frame buffer that renders color to a RGBA texture a depth to a + // "render buffer". + // + // After calling this, you can use with something like: + // + // glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); + // if(!depth_texture) + // { + // glBindRenderbuffer(GL_RENDERBUFFER, d_id); + // } + // // + // // draw scene ... + // // + // // clean up + // glBindFramebuffer(GL_FRAMEBUFFER,0); + // if(!depth_texture) + // { + // glBindRenderbuffer(GL_RENDERBUFFER, 0); + // } + // // Later ... + // glActiveTexture(GL_TEXTURE0+0); + // glBindTexture(GL_TEXTURE_2D,tex_id); + // if(depth_texture) + // { + // glActiveTexture(GL_TEXTURE0+1); + // glBindTexture(GL_TEXTURE_2D,d_id); + // } + // // draw textures + // + // + // + // Inputs: + // width image width + // height image height + // depth_texture whether to create a texture for depth or to create a + // render buffer for depth + // Outputs: + // tex_id id of the texture + // fbo_id id of the frame buffer object + // d_id id of the depth texture or frame buffer object + // + IGL_INLINE void init_render_to_texture( + const size_t width, + const size_t height, + const bool depth_texture, + GLuint & tex_id, + GLuint & fbo_id, + GLuint & d_id); + // Wrapper with depth_texture = false for legacy reasons + IGL_INLINE void init_render_to_texture( + const size_t width, + const size_t height, + GLuint & tex_id, + GLuint & fbo_id, + GLuint & dfbo_id); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "init_render_to_texture.cpp" +#endif +#endif diff --git a/src/igl/opengl/load_shader.cpp b/src/igl/opengl/load_shader.cpp new file mode 100644 index 000000000..0ee0a978f --- /dev/null +++ b/src/igl/opengl/load_shader.cpp @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "load_shader.h" + +// Copyright Denis Kovacs 4/10/08 +#include "print_shader_info_log.h" +#include +IGL_INLINE GLuint igl::opengl::load_shader( + const std::string & src,const GLenum type) +{ + if(src.empty()) + { + return (GLuint) 0; + } + + GLuint s = glCreateShader(type); + if(s == 0) + { + fprintf(stderr,"Error: load_shader() failed to create shader.\n"); + return 0; + } + // Pass shader source string + const char *c = src.c_str(); + glShaderSource(s, 1, &c, NULL); + glCompileShader(s); + // Print info log (if any) + igl::opengl::print_shader_info_log(s); + return s; +} diff --git a/src/igl/opengl/load_shader.h b/src/igl/opengl/load_shader.h new file mode 100644 index 000000000..6c234d8fc --- /dev/null +++ b/src/igl/opengl/load_shader.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_LOAD_SHADER_H +#define IGL_OPENGL_LOAD_SHADER_H +#include "../igl_inline.h" +#include "gl.h" +#include + +namespace igl +{ + namespace opengl + { + // Creates and compiles a shader from a given string + // + // Inputs: + // src string containing GLSL shader code + // type GLSL type of shader, one of: + // GL_VERTEX_SHADER + // GL_FRAGMENT_SHADER + // GL_GEOMETRY_SHADER + // Returns index id of the newly created shader, 0 on error + // + // Will immediately return 0 if src is empty. + IGL_INLINE GLuint load_shader( + const std::string & src,const GLenum type); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "load_shader.cpp" +#endif + +#endif diff --git a/src/igl/opengl/print_program_info_log.cpp b/src/igl/opengl/print_program_info_log.cpp new file mode 100644 index 000000000..41b37b7fb --- /dev/null +++ b/src/igl/opengl/print_program_info_log.cpp @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "print_program_info_log.h" + +#include +#include +// Copyright Denis Kovacs 4/10/08 +IGL_INLINE void igl::opengl::print_program_info_log(const GLuint obj) +{ + GLint infologLength = 0; + GLint charsWritten = 0; + char *infoLog; + + glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength); + + if (infologLength > 0) + { + infoLog = (char *)malloc(infologLength); + glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog); + printf("%s\n",infoLog); + free(infoLog); + } +} diff --git a/src/igl/opengl/print_program_info_log.h b/src/igl/opengl/print_program_info_log.h new file mode 100644 index 000000000..43bcc0974 --- /dev/null +++ b/src/igl/opengl/print_program_info_log.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_PRINT_PROGRAM_INFO_LOG_H +#define IGL_OPENGL_PRINT_PROGRAM_INFO_LOG_H +#include "../igl_inline.h" +#include "gl.h" + +namespace igl +{ + namespace opengl + { + // Inputs: + // obj OpenGL index of program to print info log about + IGL_INLINE void print_program_info_log(const GLuint obj); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "print_program_info_log.cpp" +#endif + +#endif diff --git a/src/igl/opengl/print_shader_info_log.cpp b/src/igl/opengl/print_shader_info_log.cpp new file mode 100644 index 000000000..2d3554387 --- /dev/null +++ b/src/igl/opengl/print_shader_info_log.cpp @@ -0,0 +1,29 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "print_shader_info_log.h" + +#include +#include +// Copyright Denis Kovacs 4/10/08 +IGL_INLINE void igl::opengl::print_shader_info_log(const GLuint obj) +{ + GLint infologLength = 0; + GLint charsWritten = 0; + char *infoLog; + + // Get shader info log from opengl + glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength); + // Only print if there is something in the log + if (infologLength > 0) + { + infoLog = (char *)malloc(infologLength); + glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog); + printf("%s\n",infoLog); + free(infoLog); + } +} diff --git a/src/igl/opengl/print_shader_info_log.h b/src/igl/opengl/print_shader_info_log.h new file mode 100644 index 000000000..5a571fb11 --- /dev/null +++ b/src/igl/opengl/print_shader_info_log.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_PRINT_SHADER_INFO_LOG_H +#define IGL_OPENGL_PRINT_SHADER_INFO_LOG_H +#include "../igl_inline.h" +#include "gl.h" + +namespace igl +{ + namespace opengl + { + // Inputs: + // obj OpenGL index of shader to print info log about + IGL_INLINE void print_shader_info_log(const GLuint obj); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "print_shader_info_log.cpp" +#endif + +#endif diff --git a/src/igl/opengl/report_gl_error.cpp b/src/igl/opengl/report_gl_error.cpp new file mode 100644 index 000000000..2dcc0b632 --- /dev/null +++ b/src/igl/opengl/report_gl_error.cpp @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "report_gl_error.h" +#include "../verbose.h" +#include + +IGL_INLINE GLenum igl::opengl::report_gl_error(const std::string id) +{ + // http://stackoverflow.com/q/28485180/148668 + + // gluErrorString was deprecated + const auto gluErrorString = [](GLenum errorCode)->const char * + { + switch(errorCode) + { + default: + return "unknown error code"; + case GL_NO_ERROR: + return "no error"; + case GL_INVALID_ENUM: + return "invalid enumerant"; + case GL_INVALID_VALUE: + return "invalid value"; + case GL_INVALID_OPERATION: + return "invalid operation"; +#ifndef GL_VERSION_3_0 + case GL_STACK_OVERFLOW: + return "stack overflow"; + case GL_STACK_UNDERFLOW: + return "stack underflow"; + case GL_TABLE_TOO_LARGE: + return "table too large"; +#endif + case GL_OUT_OF_MEMORY: + return "out of memory"; +#ifdef GL_EXT_framebuffer_object + case GL_INVALID_FRAMEBUFFER_OPERATION_EXT: + return "invalid framebuffer operation"; +#endif + } + }; + + GLenum err = glGetError(); + if(GL_NO_ERROR != err) + { + verbose("GL_ERROR: "); + fprintf(stderr,"%s%s\n",id.c_str(),gluErrorString(err)); + } + return err; +} + +IGL_INLINE GLenum igl::opengl::report_gl_error() +{ + return igl::opengl::report_gl_error(std::string("")); +} + diff --git a/src/igl/opengl/report_gl_error.h b/src/igl/opengl/report_gl_error.h new file mode 100644 index 000000000..70e5c7d74 --- /dev/null +++ b/src/igl/opengl/report_gl_error.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_REPORT_GL_ERROR_H +#define IGL_OPENGL_REPORT_GL_ERROR_H +#include "../igl_inline.h" + +// Hack to allow both opengl/ and opengl2 to use this (we shouldn't allow this) +#ifndef __gl_h_ +# include "gl.h" +#endif +#include + +namespace igl +{ + namespace opengl + { + // Print last OpenGL error to stderr prefixed by specified id string + // Inputs: + // id string to appear before any error msgs + // Returns result of glGetError() + IGL_INLINE GLenum report_gl_error(const std::string id); + // No prefix + IGL_INLINE GLenum report_gl_error(); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "report_gl_error.cpp" +#endif + +#endif diff --git a/src/igl/opengl/uniform_type_to_string.cpp b/src/igl/opengl/uniform_type_to_string.cpp new file mode 100644 index 000000000..b2f9a2fdb --- /dev/null +++ b/src/igl/opengl/uniform_type_to_string.cpp @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "uniform_type_to_string.h" + +IGL_INLINE std::string igl::opengl::uniform_type_to_string(const GLenum type) +{ + switch(type) + { + case GL_FLOAT: + return "GL_FLOAT"; + case GL_FLOAT_VEC2: + return "GL_FLOAT_VEC2"; + case GL_FLOAT_VEC3: + return "GL_FLOAT_VEC3"; + case GL_FLOAT_VEC4: + return "GL_FLOAT_VEC4"; + case GL_INT: + return "GL_INT"; + case GL_INT_VEC2: + return "GL_INT_VEC2"; + case GL_INT_VEC3: + return "GL_INT_VEC3"; + case GL_INT_VEC4: + return "GL_INT_VEC4"; + case GL_BOOL: + return "GL_BOOL"; + case GL_BOOL_VEC2: + return "GL_BOOL_VEC2"; + case GL_BOOL_VEC3: + return "GL_BOOL_VEC3"; + case GL_BOOL_VEC4: + return "GL_BOOL_VEC4"; + case GL_FLOAT_MAT2: + return "GL_FLOAT_MAT2"; + case GL_FLOAT_MAT3: + return "GL_FLOAT_MAT3"; + case GL_FLOAT_MAT4: + return "GL_FLOAT_MAT4"; + case GL_FLOAT_MAT2x3: + return "GL_FLOAT_MAT2x3"; + case GL_FLOAT_MAT2x4: + return "GL_FLOAT_MAT2x4"; + case GL_FLOAT_MAT3x2: + return "GL_FLOAT_MAT3x2"; + case GL_FLOAT_MAT3x4: + return "GL_FLOAT_MAT3x4"; + case GL_FLOAT_MAT4x2: + return "GL_FLOAT_MAT4x2"; + case GL_FLOAT_MAT4x3: + return "GL_FLOAT_MAT4x3"; + case GL_SAMPLER_1D: + return "GL_SAMPLER_1D"; + case GL_SAMPLER_2D: + return "GL_SAMPLER_2D"; + case GL_SAMPLER_3D: + return "GL_SAMPLER_3D"; + case GL_SAMPLER_CUBE: + return "GL_SAMPLER_CUBE"; + case GL_SAMPLER_1D_SHADOW: + return "GL_SAMPLER_1D_SHADOW"; + case GL_SAMPLER_2D_SHADOW: + return "GL_SAMPLER_2D_SHADOW"; + default: + return "UNKNOWN_TYPE"; + } +} diff --git a/src/igl/opengl/uniform_type_to_string.h b/src/igl/opengl/uniform_type_to_string.h new file mode 100644 index 000000000..ed0a93e90 --- /dev/null +++ b/src/igl/opengl/uniform_type_to_string.h @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_UNIFORM_TYPE_TO_STRING_H +#define IGL_OPENGL_UNIFORM_TYPE_TO_STRING_H +#include "../igl_inline.h" +#include "gl.h" +#include + +namespace igl +{ + namespace opengl + { + // Convert a GL uniform variable type (say, returned from + // glGetActiveUniform) and output a string naming that type + // Inputs: + // type enum for given type + // Returns string name of that type + IGL_INLINE std::string uniform_type_to_string(const GLenum type); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "uniform_type_to_string.cpp" +#endif + +#endif diff --git a/src/igl/opengl/vertex_array.cpp b/src/igl/opengl/vertex_array.cpp new file mode 100644 index 000000000..c07113ad3 --- /dev/null +++ b/src/igl/opengl/vertex_array.cpp @@ -0,0 +1,61 @@ +#include "vertex_array.h" +#include + +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE void igl::opengl::vertex_array( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + GLuint & va_id, + GLuint & ab_id, + GLuint & eab_id) +{ + // Inputs should be in RowMajor storage. If not, we have no choice but to + // create a copy. + if(!(V.Options & Eigen::RowMajor)) + { + Eigen::Matrix< + typename DerivedV::Scalar, + DerivedV::RowsAtCompileTime, + DerivedV::ColsAtCompileTime, + Eigen::RowMajor> VR = V; + return vertex_array(VR,F,va_id,ab_id,eab_id); + } + if(!(F.Options & Eigen::RowMajor)) + { + Eigen::Matrix< + typename DerivedF::Scalar, + DerivedF::RowsAtCompileTime, + DerivedF::ColsAtCompileTime, + Eigen::RowMajor> FR = F; + return vertex_array(V,FR,va_id,ab_id,eab_id); + } + // Generate and attach buffers to vertex array + glGenVertexArrays(1, &va_id); + glGenBuffers(1, &ab_id); + glGenBuffers(1, &eab_id); + glBindVertexArray(va_id); + glBindBuffer(GL_ARRAY_BUFFER, ab_id); + const auto size_VScalar = sizeof(typename DerivedV::Scalar); + const auto size_FScalar = sizeof(typename DerivedF::Scalar); + glBufferData(GL_ARRAY_BUFFER,size_VScalar*V.size(),V.data(),GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eab_id); + assert(sizeof(GLuint) == size_FScalar && "F type does not match GLuint"); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*F.size(), F.data(), GL_STATIC_DRAW); + glVertexAttribPointer( + 0, + V.cols(), + size_VScalar==sizeof(float)?GL_FLOAT:GL_DOUBLE, + GL_FALSE, + V.cols()*size_VScalar, + (GLvoid*)0); + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::opengl::vertex_array, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned int&, unsigned int&, unsigned int&); +#endif diff --git a/src/igl/opengl/vertex_array.h b/src/igl/opengl/vertex_array.h new file mode 100644 index 000000000..1342a9c90 --- /dev/null +++ b/src/igl/opengl/vertex_array.h @@ -0,0 +1,38 @@ +#ifndef IGL_OPENGL_VERTEX_ARRAY_H +#define IGL_OPENGL_VERTEX_ARRAY_H +#include +#include +#include +namespace igl +{ + namespace opengl + { + // Create a GL_VERTEX_ARRAY for a given mesh (V,F) + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh triangle indices into V + // Outputs: + // va_id id of vertex array + // ab_id id of array buffer (vertex buffer object) + // eab_id id of element array buffer (element/face buffer object) + // + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE void vertex_array( + // Note: Unlike most libigl functions, the **input** Eigen matrices must + // be `Eigen::PlainObjectBase` because we want to directly access it's + // underlying storage. It cannot be `Eigen::MatrixBase` (see + // http://stackoverflow.com/questions/25094948/) + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + GLuint & va_id, + GLuint & ab_id, + GLuint & eab_id); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "vertex_array.cpp" +#endif +#endif diff --git a/src/igl/opengl2/MouseController.h b/src/igl/opengl2/MouseController.h new file mode 100644 index 000000000..eb38802ad --- /dev/null +++ b/src/igl/opengl2/MouseController.h @@ -0,0 +1,691 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_MOUSECONTROLLER_H +#define IGL_OPENGL2_MOUSECONTROLLER_H +// Needs to be included before others +#include +#include "RotateWidget.h" +#include "TranslateWidget.h" +#include +#include +#include + +// Class for control a skeletal FK rig with the mouse. +namespace igl +{ + namespace opengl2 + { + class MouseController + { + public: + typedef Eigen::VectorXi VectorXb; + // Propagate selection to descendants so that selected bones and their + // subtrees are all selected. + // + // Input: + // S #S list of whether selected + // P #S list of bone parents + // Output: + // T #S list of whether selected + static inline void propogate_to_descendants_if( + const VectorXb & S, + const Eigen::VectorXi & P, + VectorXb & T); + // Create a matrix of colors for the selection and their descendants. + // + // Inputs: + // selection #S list of whether a bone is selected + // selected_color color for selected bones + // unselected_color color for unselected bones + // Outputs: + // C #P by 4 list of colors + static inline void color_if( + const VectorXb & S, + const Eigen::Vector4f & selected_color, + const Eigen::Vector4f & unselected_color, + Eigen::MatrixXf & C); + enum WidgetMode + { + WIDGET_MODE_ROTATE = 0, + WIDGET_MODE_TRANSLATE = 1, + NUM_WIDGET_MODES = 2, + }; + private: + // m_is_selecting whether currently selecting + // m_selection #m_rotations list of whether a bone is selected + // m_down_x x-coordinate of mouse location at down + // m_down_y y-coordinate 〃 + // m_drag_x x-coordinate of mouse location at drag + // m_drag_y y-coordinate 〃 + // m_widget rotation widget for selected bone + // m_width width of containing window + // m_height height 〃 + // m_rotations list of rotations for each bone + // m_rotations_at_selection list of rotations for each bone at time of + // selection + // m_translations list of translations for each bone + // m_fk_rotations_at_selection list of rotations for each bone at time of + // selection + // m_root_enabled Whether root is enabled + bool m_is_selecting; + VectorXb m_selection; + int m_down_x,m_down_y,m_drag_x,m_drag_y; + int m_width,m_height; + igl::opengl2::RotateWidget m_widget; + igl::opengl2::TranslateWidget m_trans_widget; + Eigen::Quaterniond m_widget_rot_at_selection; + //Eigen::Vector3d m_trans_widget_trans_at_selection; + typedef std::vector< + Eigen::Quaterniond, + Eigen::aligned_allocator > RotationList; + typedef std::vector< Eigen::Vector3d > TranslationList; + RotationList + m_rotations, + m_rotations_at_selection, + m_fk_rotations_at_selection, + m_parent_rotations_at_selection; + TranslationList + m_translations, + m_translations_at_selection, + m_fk_translations_at_selection; + bool m_root_enabled; + WidgetMode m_widget_mode; + public: + MouseController(); + // Returns const reference to m_selection + inline const VectorXb & selection() const{return m_selection;}; + // 〃 m_is_selecting + inline const bool & is_selecting() const{return m_is_selecting;} + inline bool is_widget_down() const{return m_widget.is_down();} + inline bool is_trans_widget_down() const{return m_trans_widget.is_down();} + // 〃 m_rotations + inline const RotationList & rotations() const{return m_rotations;} + inline const TranslationList & translations() const{return m_translations;} + // Returns non-const reference to m_root_enabled + inline bool & root_enabled(){ return m_root_enabled;} + inline void reshape(const int w, const int h); + // Process down, drag, up mouse events + // + // Inputs: + // x x-coordinate of mouse click with respect to container + // y y-coordinate 〃 + // Returns true if accepted (action taken). + inline bool down(const int x, const int y); + inline bool drag(const int x, const int y); + inline bool up(const int x, const int y); + // Draw selection box and widget + inline void draw() const; + // Set `m_selection` based on the last drag selection and initialize + // widget. + // + // Inputs: + // C #C by dim list of joint positions at rest + // BE #BE by 2 list of bone indices at rest + // P #P list of bone parents + inline void set_selection_from_last_drag( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const Eigen::VectorXi & RP); + // Set from explicit selection + inline void set_selection( + const Eigen::VectorXi & S, + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const Eigen::VectorXi & P, + const Eigen::VectorXi & RP); + // Set size of skeleton + // + // Inputs: + // n number of bones + inline void set_size(const int n); + // Resets m_rotation elements to identity + inline void reset(); + inline void reset_selected(); + inline void reset_rotations(); + inline void reset_selected_rotations(); + inline void reset_translations(); + inline void reset_selected_translations(); + inline bool set_rotations(const RotationList & vQ); + inline bool set_translations(const TranslationList & vT); + // Sets all entries in m_selection to false + inline void clear_selection(); + // Returns true iff some element in m_selection is true + inline bool any_selection() const; + inline void set_widget_mode(const WidgetMode & mode); + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + }; + } +} + +// Implementation +#include "../line_segment_in_rectangle.h" +#include "draw_rectangular_marquee.h" +#include "project.h" +#include "../forward_kinematics.h" +#include +#include +#include + +inline void igl::opengl2::MouseController::propogate_to_descendants_if( + const VectorXb & S, + const Eigen::VectorXi & P, + VectorXb & T) +{ + using namespace std; + const int n = S.rows(); + assert(P.rows() == n); + // dynamic programming + T = S; + vector seen(n,false); + // Recursively look up chain and see if ancestor is selected + const function look_up = [&](int e) -> bool + { + if(e==-1) + { + return false; + } + if(!seen[e]) + { + seen[e] = true; + T(e) |= look_up(P(e)); + } + return T(e); + }; + for(int e = 0;e > vQ; + vector vT; + forward_kinematics(C,BE,P,m_rotations,m_translations,vQ,vT); + // Loop over deformed bones + for(int e = 0;e > & vQ = + m_fk_rotations_at_selection; + vector & vT = m_fk_translations_at_selection; + forward_kinematics(C,BE,P,m_rotations,m_translations,vQ,vT); + m_parent_rotations_at_selection.resize( + m_rotations.size(),Quaterniond::Identity()); + for(size_t r = 0;r=0) + { + m_parent_rotations_at_selection[r] = vQ[P(r)]; + } + } + + + if(&m_selection != &S) + { + m_selection = S; + } + assert(m_selection.rows() == BE.rows()); + assert(BE.rows() == P.rows()); + assert(BE.rows() == RP.rows()); + // Zero-out S up a path of ones from e + auto propagate = [&](const int e, const VectorXb & S, VectorXb & N) + { + if(S(e)) + { + int f = e; + while(true) + { + int p = P(f); + if(p==-1||!S(p)) + { + break; + } + N(f) = false; + f = p; + } + } + }; + VectorXb prev_selection = m_selection; + // Combine upward, group rigid parts, repeat + while(true) + { + // Spread selection across rigid pieces + VectorXb SRP(VectorXb::Zero(RP.maxCoeff()+1)); + for(int e = 0;e +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_ROTATE_WIDGET_H +#define IGL_OPENGL2_ROTATE_WIDGET_H +#include "../material_colors.h" +#include +#include +#include + +namespace igl +{ + namespace opengl2 + { + // 3D Rotate tool widget similar to Maya's. Works best if field of view angle + // is less than ~25. + class RotateWidget + { + // If a is true then use A else use desaturated A + static inline void glColor4fv(const bool a, const Eigen::Vector4f & A); + public: + inline static Eigen::Quaterniond axis_q(const int a); + inline static Eigen::Vector3d view_direction(const int x, const int y); + inline static Eigen::Vector3d view_direction(const Eigen::Vector3d & pos); + Eigen::Vector3d pos; + Eigen::Quaterniond rot,down_rot; + Eigen::Vector2d down_xy,drag_xy,down_dir; + Eigen::Vector3d udown,udrag; + double outer_radius_on_screen; + double outer_over_inner; + bool m_is_enabled; + enum DownType + { + DOWN_TYPE_X = 0, + DOWN_TYPE_Y = 1, + DOWN_TYPE_Z = 2, + DOWN_TYPE_OUTLINE = 3, + DOWN_TYPE_TRACKBALL = 4, + DOWN_TYPE_NONE = 5, + NUM_DOWN_TYPES = 6 + } down_type, selected_type; + inline RotateWidget(); + // Vector from origin to mouse click "Unprojected" onto plane with depth of + // origin and scale to so that outer radius is 1 + // + // Inputs: + // x mouse x position + // y mouse y position + // Returns vector + inline Eigen::Vector3d unproject_onto(const int x, const int y) const; + // Shoot ray from mouse click to sphere + // + // Inputs: + // x mouse x position + // y mouse y position + // Outputs: + // hit position of hit + // Returns true only if there was a hit + inline bool intersect( + const int x, + const int y, + Eigen::Vector3d & hit) const; + inline double unprojected_inner_radius() const; + inline bool down(const int x, const int y); + inline bool drag(const int x, const int y); + inline bool up(const int x, const int y); + inline bool is_down() const; + inline void draw() const; + inline void draw_guide() const; + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + }; + } +} + +// Implementation +#include "../PI.h" +#include "../EPS.h" +#include "../ray_sphere_intersect.h" +#include "../mat_to_quat.h" +#include "../trackball.h" +#include "gl.h" +#include "project.h" +#include "unproject.h" +#include +#include + +inline void igl::opengl2::RotateWidget::glColor4fv( + const bool a, + const Eigen::Vector4f & A) +{ + if(a) + { + ::glColor4fv(A.data()); + }else + { + Eigen::Vector4f B; + const double f = 0.95; // desaturate by 95% + const double L = 0.3*A(0) + 0.6*A(1) + 0.1*A(2); + B.head(3) = A.head(3).array() + f*(L-A.head(3).array()); + B(3) = A(3); + ::glColor4fv(B.data()); + } +} + +inline Eigen::Quaterniond igl::opengl2::RotateWidget::axis_q(const int a) +{ + assert(a<3 && a>=0); + const Eigen::Quaterniond axes[3] = { + Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(0,1,0))), + Eigen::Quaterniond(Eigen::AngleAxisd(igl::PI*0.5,Eigen::Vector3d(1,0,0))), + Eigen::Quaterniond::Identity()}; + return axes[a]; +} + +inline Eigen::Vector3d igl::opengl2::RotateWidget::view_direction(const int x, const int y) +{ + using namespace Eigen; + const Vector3d win_s(x,y,0), win_d(x,y,1); + const Vector3d s = unproject(win_s); + const Vector3d d = unproject(win_d); + return d-s; +} + +inline Eigen::Vector3d igl::opengl2::RotateWidget::view_direction(const Eigen::Vector3d & pos) +{ + using namespace Eigen; + const Vector3d ppos = project(pos); + return view_direction(ppos(0),ppos(1)); +} + +inline igl::opengl2::RotateWidget::RotateWidget(): + pos(0,0,0), + rot(Eigen::Quaterniond::Identity()), + down_rot(rot), + down_xy(-1,-1),drag_xy(-1,-1), + outer_radius_on_screen(91.), + outer_over_inner(1.13684210526), + m_is_enabled(true), + down_type(DOWN_TYPE_NONE), + selected_type(DOWN_TYPE_NONE) +{ +} + +inline Eigen::Vector3d igl::opengl2::RotateWidget::unproject_onto( + const int x, + const int y) const +{ + using namespace Eigen; + // KNOWN BUG: This projects to same depths as pos. I think what we actually + // want is The intersection with the plane perpendicular to the view + // direction at pos. If the field of view angle is small then this difference + // is negligible. + //const Vector3d ppos = project(pos); + //const Vector3d uxy = unproject( Vector3d(x,y,ppos(2))); + // http://en.wikipedia.org/wiki/Line-plane_intersection + // + // Hrrmmm. There's still something wrong here if the ball's in the corner of + // the screen. Am I somehow not accounting for perspective correctly? + // + // Q: What about just projecting the circle's equation and solving for the + // distance? + const Vector3d l0 = unproject(Vector3d(x,y,0)); + const Vector3d l = unproject(Vector3d(x,y,1))-l0; + const Vector3d n = view_direction(pos); + const double t = (pos-l0).dot(n)/l.dot(n); + const Vector3d uxy = l0+t*l; + return (uxy-pos)/unprojected_inner_radius()*outer_over_inner*outer_over_inner; +} + +inline bool igl::opengl2::RotateWidget::intersect( + const int x, + const int y, + Eigen::Vector3d & hit) const +{ + using namespace Eigen; + Vector3d view = view_direction(x,y); + const Vector3d ppos = project(pos); + Vector3d uxy = unproject(Vector3d(x,y,ppos(2))); + double t0,t1; + if(!ray_sphere_intersect(uxy,view,pos,unprojected_inner_radius(),t0,t1)) + { + return false; + } + hit = uxy+t0*view; + return true; +} + + +inline double igl::opengl2::RotateWidget::unprojected_inner_radius() const +{ + using namespace Eigen; + Vector3d off,ppos,ppos_off,pos_off; + project(pos,ppos); + ppos_off = ppos; + ppos_off(0) += outer_radius_on_screen/outer_over_inner; + unproject(ppos_off,pos_off); + return (pos-pos_off).norm(); +} +inline bool igl::opengl2::RotateWidget::down(const int x, const int y) +{ + using namespace Eigen; + using namespace std; + if(!m_is_enabled) + { + return false; + } + down_type = DOWN_TYPE_NONE; + selected_type = DOWN_TYPE_NONE; + down_xy = Vector2d(x,y); + drag_xy = down_xy; + down_rot = rot; + Vector3d ppos = project(pos); + const double r = (ppos.head(2) - down_xy).norm(); + const double thresh = 3; + if(fabs(r - outer_radius_on_screen) bool + { + // project onto rotate plane + pl_hit = hit-pos; + pl_hit = (m.conjugate()*rot.conjugate()*pl_hit).eval(); + pl_hit(2) = 0; + pl_hit = (rot*m*pl_hit).eval(); + pl_hit.normalize(); + pl_hit *= unprojected_inner_radius(); + pl_hit += pos; + return (project(pl_hit).head(2)-project(hit).head(2)).norm()<2*thresh; + }; + udown = (hit-pos).normalized()/outer_radius_on_screen; + udrag = udown; + for(int a = 0;a<3;a++) + { + Vector3d pl_hit; + if(on_meridian(hit,rot,Quaterniond(axis_q(a)),pl_hit)) + { + udown = (pl_hit-pos).normalized()/outer_radius_on_screen; + udrag = udown; + down_type = DownType(DOWN_TYPE_X+a); + selected_type = down_type; + { + Vector3d dir3 = axis_q(a).conjugate()*down_rot.conjugate()*(hit-pos); + dir3 = AngleAxisd(-PI*0.5,Vector3d(0,0,1))*dir3; + dir3 = (rot*axis_q(a)*dir3).eval(); + down_dir = (project((hit+dir3).eval())-project(hit)).head(2); + down_dir.normalize(); + //// flip y because y coordinate is going to be given backwards in + //// drag() + //down_dir(1) *= -1; + } + return true; + } + } + //assert(is_hit); + down_type = DOWN_TYPE_TRACKBALL; + selected_type = DOWN_TYPE_TRACKBALL; + return true; + }else + { + return false; + } +} + +inline bool igl::opengl2::RotateWidget::drag(const int x, const int y) +{ + using namespace std; + using namespace Eigen; + if(!m_is_enabled) + { + return false; + } + drag_xy = Vector2d(x,y); + switch(down_type) + { + case DOWN_TYPE_NONE: + return false; + default: + { + const Quaterniond & q = axis_q(down_type-DOWN_TYPE_X); + const double dtheta = -(drag_xy - down_xy).dot(down_dir)/ + outer_radius_on_screen/outer_over_inner*PI/2.; + Quaterniond dq(AngleAxisd(dtheta,down_rot*q*Vector3d(0,0,1))); + rot = dq * down_rot; + udrag = dq * udown; + return true; + } + case DOWN_TYPE_OUTLINE: + { + Vector3d ppos = project(pos); + // project mouse to same depth as pos + udrag = unproject_onto(x,y); + const Vector2d A = down_xy - ppos.head(2); + const Vector2d B = drag_xy - ppos.head(2); + const double dtheta = atan2(A(0)*B(1)-A(1)*B(0),A(0)*B(0)+A(1)*B(1)); + Vector3d n = view_direction(pos).normalized(); + Quaterniond dq(AngleAxisd(dtheta,-n)); + //Vector3d n = udrag.cross(udown).normalized(); + //Quaterniond dq(AngleAxisd(fabs(dtheta),-n)); + rot = dq * down_rot; + } + return true; + case DOWN_TYPE_TRACKBALL: + { + Vector3d ppos = project(pos); + const double r = (double)outer_radius_on_screen/outer_over_inner*2.0; + //const int h = w; + Vector4i vp; + glGetIntegerv(GL_VIEWPORT,vp.data()); + const int h = vp(3); + Quaterniond dq; + trackball( + r,r, + 1, + Quaterniond::Identity(), + double( down_xy(0)-ppos(0) )+r/2., + double((h-down_xy(1))-(h-ppos(1)))+r/2., + double( x-ppos(0) )+r/2., + double( (h-y)-(h-ppos(1)))+r/2., + dq); + // We've computed change in rotation according to this view: + // R = mv * r, R' = rot * (mv * r) + // But we only want new value for r: + // R' = mv * r' + // mv * r' = rot * (mv * r) + // r' = mv* * rot * mv * r + Matrix4d mv; + glGetDoublev(GL_MODELVIEW_MATRIX,mv.data()); + Quaterniond scene_rot; + // Convert modelview matrix to quaternion + mat4_to_quat(mv.data(),scene_rot.coeffs().data()); + scene_rot.normalize(); + rot = scene_rot.conjugate() * dq * scene_rot * down_rot; + } + return true; + } +} + +inline bool igl::opengl2::RotateWidget::up(const int /*x*/, const int /*y*/) +{ + // even if disabled process up + down_type = DOWN_TYPE_NONE; + return false; +} + +inline bool igl::opengl2::RotateWidget::is_down() const +{ + return down_type != DOWN_TYPE_NONE; +} + +inline void igl::opengl2::RotateWidget::draw() const +{ + using namespace Eigen; + using namespace std; + glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT | GL_LINE_BIT); + glDisable(GL_CLIP_PLANE0); + + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glLineWidth(2.0); + + double r = unprojected_inner_radius(); + Vector3d view = view_direction(pos).normalized(); + + auto draw_circle = [&](const bool cull) + { + Vector3d view = view_direction(pos).normalized(); + glBegin(GL_LINES); + const double th_step = (2.0*igl::PI/100.0); + for(double th = 0;th<2.0*igl::PI+th_step;th+=th_step) + { + Vector3d a(cos(th),sin(th),0.0); + Vector3d b(cos(th+th_step),sin(th+th_step),0.0); + if(!cull || (0.5*(a+b)).dot(view) +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_TRANSLATE_WIDGET_H +#define IGL_OPENGL2_TRANSLATE_WIDGET_H +#include "../material_colors.h" +#include +#include +#include + +namespace igl +{ + namespace opengl2 + { + class TranslateWidget + { +public: + // m_pos position of center + // m_trans translation vector + // m_down_xy mouse position on down + // m_drag_xy mouse position on drag + // m_is_enabled whether enabled + Eigen::Vector3d m_pos,m_trans,m_down_trans; + Eigen::Vector2d m_down_xy, m_drag_xy; + bool m_is_enabled; + double m_len; + enum DownType + { + DOWN_TYPE_X = 0, + DOWN_TYPE_Y = 1, + DOWN_TYPE_Z = 2, + DOWN_TYPE_CENTER = 3, + DOWN_TYPE_NONE = 4, + NUM_DOWN_TYPES = 5 + } m_down_type, m_selected_type; + inline TranslateWidget(const Eigen::Vector3d & pos = Eigen::Vector3d(0,0,0)); + inline bool down(const int x, const int y); + inline bool drag(const int x, const int y); + inline bool up(const int x, const int y); + inline bool is_down() const; + inline void draw() const; +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; + }; + } +} + +// Implementation +#include "project.h" +#include "unproject.h" + +inline igl::opengl2::TranslateWidget::TranslateWidget( + const Eigen::Vector3d & pos): + m_pos(pos), + m_trans(0,0,0), + m_down_xy(-1,-1), + m_drag_xy(-1,-1), + m_is_enabled(true), + m_len(50), + m_down_type(DOWN_TYPE_NONE), + m_selected_type(DOWN_TYPE_NONE) +{ +} + +inline bool igl::opengl2::TranslateWidget::down(const int x, const int y) +{ + using namespace Eigen; + using namespace std; + if(!m_is_enabled) + { + return false; + } + m_down_trans = m_trans; + m_down_xy = Vector2d(x,y); + m_drag_xy = m_down_xy; + m_down_type = DOWN_TYPE_NONE; + m_selected_type = DOWN_TYPE_NONE; + Vector3d ppos = project((m_pos+m_trans).eval()); + const double r = (ppos.head(2) - m_down_xy).norm(); + const double center_thresh = 10; + if(r < center_thresh) + { + m_down_type = DOWN_TYPE_CENTER; + m_selected_type = m_down_type; + return true; + }else if(r < m_len) + { + // Might be hit on lines + } + return false; +} + +inline bool igl::opengl2::TranslateWidget::drag(const int x, const int y) +{ + using namespace std; + using namespace Eigen; + if(!m_is_enabled) + { + return false; + } + m_drag_xy = Vector2d(x,y); + switch(m_down_type) + { + case DOWN_TYPE_NONE: + return false; + default: + { + Vector3d ppos = project((m_pos+m_trans).eval()); + Vector3d drag3(m_drag_xy(0),m_drag_xy(1),ppos(2)); + Vector3d down3(m_down_xy(0),m_down_xy(1),ppos(2)); + m_trans = m_down_trans + unproject(drag3)-unproject(down3); + return true; + } + } +} + +inline bool igl::opengl2::TranslateWidget::up(const int /*x*/, const int /*y*/) +{ + // even if disabled process up + m_down_type = DOWN_TYPE_NONE; + return false; +} + +inline bool igl::opengl2::TranslateWidget::is_down() const +{ + return m_down_type != DOWN_TYPE_NONE; +} + +inline void igl::opengl2::TranslateWidget::draw() const +{ + using namespace Eigen; + using namespace std; + glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT | GL_LINE_BIT); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + glLineWidth(2.0); + auto draw_axes = [&]() + { + glBegin(GL_LINES); + glColor3f(1,0,0); + glVertex3f(0,0,0); + glVertex3f(1,0,0); + glColor3f(0,1,0); + glVertex3f(0,0,0); + glVertex3f(0,1,0); + glColor3f(0,0,1); + glVertex3f(0,0,0); + glVertex3f(0,0,1); + glEnd(); + }; + auto draw_cube = [] + { + glBegin(GL_LINES); + glVertex3f(-1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, -1.0f, 1.0f); + glVertex3f(1.0f, -1.0f, 1.0f); + glVertex3f(-1.0f, -1.0f, 1.0f); + glVertex3f(-1.0f, -1.0f, 1.0f); + glVertex3f(-1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, -1.0f); + glVertex3f(1.0f, 1.0f, -1.0f); + glVertex3f(1.0f, -1.0f, -1.0f); + glVertex3f(1.0f, -1.0f, -1.0f); + glVertex3f(1.0f, -1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, -1.0f); + glVertex3f(-1.0f, 1.0f, -1.0f); + glVertex3f(-1.0f, -1.0f, -1.0f); + glVertex3f(1.0f, -1.0f, -1.0f); + glVertex3f(-1.0f, -1.0f, -1.0f); + glVertex3f(-1.0f, 1.0f, -1.0f); + glVertex3f(-1.0f, 1.0f, -1.0f); + glVertex3f(-1.0f, 1.0f, 1.0f); + glVertex3f(-1.0f, -1.0f, 1.0f); + glVertex3f(-1.0f, -1.0f, -1.0f); + glEnd(); + }; + glPushMatrix(); + glTranslated( m_pos(0)+m_trans(0), m_pos(1)+m_trans(1), m_pos(2)+m_trans(2)); + + { + Vector3d off,ppos,ppos_off,pos_off; + project((m_pos+m_trans).eval(),ppos); + ppos_off = ppos; + ppos_off(0) += m_len; + unproject(ppos_off,pos_off); + const double r = (m_pos+m_trans-pos_off).norm(); + glScaled(r,r,r); + } + + draw_axes(); + glScaled(0.05,0.05,0.05); + if(m_selected_type == DOWN_TYPE_CENTER) + { + glColor3fv(MAYA_YELLOW.data()); + }else + { + glColor3fv(MAYA_GREY.data()); + } + draw_cube(); + glPopMatrix(); + glPopAttrib(); +} + +#endif diff --git a/src/igl/opengl2/draw_beach_ball.cpp b/src/igl/opengl2/draw_beach_ball.cpp new file mode 100644 index 000000000..a661ce4ae --- /dev/null +++ b/src/igl/opengl2/draw_beach_ball.cpp @@ -0,0 +1,283 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "draw_beach_ball.h" +#include "gl.h" + +// I'm not sure why windows would need it this way: +// http://lists.cairographics.org/archives/cairo/2008-January/012722.html +#ifdef _MSC_VER +#define SAFE_INLINE __inline +#else +#define SAFE_INLINE inline +#endif + +#include +#include +#include + +// Most of this implementation comes from the AntTweakBar source code: +// TwMgr.cpp, TwMgr.h, TwColor.h, TwColor.cpp, TwOpenGL.h and TwOpenGL.cpp + +//////////////////////////////////////////////////////////////////////////// +// Begin Copied Straight from AntTweakBar +//////////////////////////////////////////////////////////////////////////// +enum EArrowParts { ARROW_CONE, ARROW_CONE_CAP, ARROW_CYL, ARROW_CYL_CAP }; + +template SAFE_INLINE const T& TClamp(const T& X, const T& Limit1, const T& Limit2) +{ + if( Limit1=Limit2) ? Limit2 : X ); + else + return (X<=Limit2) ? Limit2 : ( (X>=Limit1) ? Limit1 : X ); +} + +typedef unsigned int color32; +static SAFE_INLINE color32 Color32FromARGBi(int A, int R, int G, int B) +{ + return (((color32)TClamp(A, 0, 255))<<24) | (((color32)TClamp(R, 0, 255))<<16) | (((color32)TClamp(G, 0, 255))<<8) | ((color32)TClamp(B, 0, 255)); +} + +static SAFE_INLINE color32 Color32FromARGBf(float A, float R, float G, float B) +{ + return (((color32)TClamp(A*256.0f, 0.0f, 255.0f))<<24) | (((color32)TClamp(R*256.0f, 0.0f, 255.0f))<<16) | (((color32)TClamp(G*256.0f, 0.0f, 255.0f))<<8) | ((color32)TClamp(B*256.0f, 0.0f, 255.0f)); +} + +static SAFE_INLINE void Color32ToARGBi(color32 Color, int *A, int *R, int *G, int *B) +{ + if(A) *A = (Color>>24)&0xff; + if(R) *R = (Color>>16)&0xff; + if(G) *G = (Color>>8)&0xff; + if(B) *B = Color&0xff; +} + +static SAFE_INLINE void Color32ToARGBf(color32 Color, float *A, float *R, float *G, float *B) +{ + if(A) *A = (1.0f/255.0f)*float((Color>>24)&0xff); + if(R) *R = (1.0f/255.0f)*float((Color>>16)&0xff); + if(G) *G = (1.0f/255.0f)*float((Color>>8)&0xff); + if(B) *B = (1.0f/255.0f)*float(Color&0xff); +} + +static color32 ColorBlend(color32 Color1, color32 Color2, float S) +{ + float a1, r1, g1, b1, a2, r2, g2, b2; + Color32ToARGBf(Color1, &a1, &r1, &g1, &b1); + Color32ToARGBf(Color2, &a2, &r2, &g2, &b2); + float t = 1.0f-S; + return Color32FromARGBf(t*a1+S*a2, t*r1+S*r2, t*g1+S*g2, t*b1+S*b2); +} + +static std::vector s_SphTri; +static std::vector s_SphCol; +static void CreateSphere() +{ + const int SUBDIV = 7; + s_SphTri.clear(); + s_SphCol.clear(); + + const float A[8*3] = { 1,0,0, 0,0,-1, -1,0,0, 0,0,1, 0,0,1, 1,0,0, 0,0,-1, -1,0,0 }; + const float B[8*3] = { 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0 }; + const float C[8*3] = { 0,0,1, 1,0,0, 0,0,-1, -1,0,0, 1,0,0, 0,0,-1, -1,0,0, 0,0,1 }; + //const color32 COL_A[8] = { 0xffff8080, 0xff000080, 0xff800000, 0xff8080ff, 0xff8080ff, 0xffff8080, 0xff000080, 0xff800000 }; + //const color32 COL_B[8] = { 0xff80ff80, 0xff80ff80, 0xff80ff80, 0xff80ff80, 0xff008000, 0xff008000, 0xff008000, 0xff008000 }; + //const color32 COL_C[8] = { 0xff8080ff, 0xffff8080, 0xff000080, 0xff800000, 0xffff8080, 0xff000080, 0xff800000, 0xff8080ff }; + const color32 COL_A[8] = { 0xffffffff, 0xffffff40, 0xff40ff40, 0xff40ffff, 0xffff40ff, 0xffff4040, 0xff404040, 0xff4040ff }; + const color32 COL_B[8] = { 0xffffffff, 0xffffff40, 0xff40ff40, 0xff40ffff, 0xffff40ff, 0xffff4040, 0xff404040, 0xff4040ff }; + const color32 COL_C[8] = { 0xffffffff, 0xffffff40, 0xff40ff40, 0xff40ffff, 0xffff40ff, 0xffff4040, 0xff404040, 0xff4040ff }; + + int i, j, k, l; + float xa, ya, za, xb, yb, zb, xc, yc, zc, x, y, z, norm, u[3], v[3]; + color32 col; + for( i=0; i<8; ++i ) + { + xa = A[3*i+0]; ya = A[3*i+1]; za = A[3*i+2]; + xb = B[3*i+0]; yb = B[3*i+1]; zb = B[3*i+2]; + xc = C[3*i+0]; yc = C[3*i+1]; zc = C[3*i+2]; + for( j=0; j<=SUBDIV; ++j ) + for( k=0; k<=2*(SUBDIV-j); ++k ) + { + if( k%2==0 ) + { + u[0] = ((float)j)/(SUBDIV+1); + v[0] = ((float)(k/2))/(SUBDIV+1); + u[1] = ((float)(j+1))/(SUBDIV+1); + v[1] = ((float)(k/2))/(SUBDIV+1); + u[2] = ((float)j)/(SUBDIV+1); + v[2] = ((float)(k/2+1))/(SUBDIV+1); + } + else + { + u[0] = ((float)j)/(SUBDIV+1); + v[0] = ((float)(k/2+1))/(SUBDIV+1); + u[1] = ((float)(j+1))/(SUBDIV+1); + v[1] = ((float)(k/2))/(SUBDIV+1); + u[2] = ((float)(j+1))/(SUBDIV+1); + v[2] = ((float)(k/2+1))/(SUBDIV+1); + } + + for( l=0; l<3; ++l ) + { + x = (1.0f-u[l]-v[l])*xa + u[l]*xb + v[l]*xc; + y = (1.0f-u[l]-v[l])*ya + u[l]*yb + v[l]*yc; + z = (1.0f-u[l]-v[l])*za + u[l]*zb + v[l]*zc; + norm = sqrtf(x*x+y*y+z*z); + x /= norm; y /= norm; z /= norm; + s_SphTri.push_back(x); s_SphTri.push_back(y); s_SphTri.push_back(z); +static const float FLOAT_EPS = 1.0e-7f; + if( u[l]+v[l]>FLOAT_EPS ) + col = ColorBlend(COL_A[i], ColorBlend(COL_B[i], COL_C[i], v[l]/(u[l]+v[l])), u[l]+v[l]); + else + col = COL_A[i]; + //if( (j==0 && k==0) || (j==0 && k==2*SUBDIV) || (j==SUBDIV && k==0) ) + // col = 0xffff0000; + s_SphCol.push_back(col); + } + } + } + //s_SphTriProj.clear(); + //s_SphTriProj.resize(2*s_SphCol.size(), 0); + //s_SphColLight.clear(); + //s_SphColLight.resize(s_SphCol.size(), 0); +} + +static std::vector s_ArrowTri[4]; +static std::vector s_ArrowNorm[4]; +static void CreateArrow() +{ + const int SUBDIV = 15; + const float CYL_RADIUS = 0.08f; + const float CONE_RADIUS = 0.16f; + const float CONE_LENGTH = 0.25f; + const float ARROW_BGN = -1.1f; + const float ARROW_END = 1.15f; + int i; + for(i=0; i<4; ++i) + { + s_ArrowTri[i].clear(); + s_ArrowNorm[i].clear(); + } + + float x0, x1, y0, y1, z0, z1, a0, a1, nx, nn; + for(i=0; i>16), GLubyte(s_SphCol[i]>>8), GLubyte(s_SphCol[i]), GLubyte(s_SphCol[i]>>24)); + glVertex3fv(&s_SphTri[i*3]); + } + glEnd(); + glPopMatrix(); + + CreateArrow(); + for(int k = 0;k<3;k++) + { + glPushMatrix(); + glColor3f(k==0,k==1,k==2); + glRotatef((k==2?-1.0:1.0)*90,k==0,k==2,k==1); + glBegin(GL_TRIANGLES); + for(int j = 0;j<4;j++) + { + for(int i = 0;i<(int)s_ArrowTri[j].size();i+=3) + { + glNormal3fv(&s_ArrowNorm[j][i]); + glVertex3fv(&s_ArrowTri[j][i]); + } + } + glEnd(); + glPopMatrix(); + } + + (cm ? glEnable(GL_COLOR_MATERIAL):glDisable(GL_COLOR_MATERIAL)); +} diff --git a/src/igl/opengl2/draw_beach_ball.h b/src/igl/opengl2/draw_beach_ball.h new file mode 100644 index 000000000..d430dc5ed --- /dev/null +++ b/src/igl/opengl2/draw_beach_ball.h @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_BEACH_BALL_H +#define IGL_OPENGL2_DRAW_BEACH_BALL_H +#include "../igl_inline.h" + +namespace igl +{ + namespace opengl2 + { + // Draw a beach ball icon/glyph (from AntTweakBar) at the current origin + // according to the current orientation: ball has radius 0.75 and axis have + // length 1.15 + IGL_INLINE void draw_beach_ball(); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "draw_beach_ball.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/draw_floor.cpp b/src/igl/opengl2/draw_floor.cpp new file mode 100644 index 000000000..61fa47491 --- /dev/null +++ b/src/igl/opengl2/draw_floor.cpp @@ -0,0 +1,161 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "draw_floor.h" +#include "gl.h" + +IGL_INLINE void igl::opengl2::draw_floor(const float * colorA, const float * colorB, + const int GridSizeX, + const int GridSizeY) +{ + const float SizeX = 0.5f*100./(double)GridSizeX; + const float SizeY = 0.5f*100./(double)GridSizeY; + // old settings + int old_lighting=0,old_color_material=0; + glGetIntegerv(GL_LIGHTING,&old_lighting); + glGetIntegerv(GL_COLOR_MATERIAL,&old_color_material); + glDisable(GL_LIGHTING); + glColorMaterial( GL_FRONT, GL_EMISSION); + glEnable(GL_COLOR_MATERIAL); + glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE); + // Set material + const float black[] = {0.,0.,0.,1.}; + glMaterialfv(GL_FRONT, GL_AMBIENT, black); + glMaterialfv(GL_FRONT, GL_DIFFUSE, black); + glMaterialfv(GL_FRONT, GL_SPECULAR, black); + glMaterialfv(GL_FRONT, GL_EMISSION, black); + glMaterialf(GL_FRONT, GL_SHININESS,0); + const bool use_lighting = false; + if(use_lighting) + { + glEnable(GL_LIGHTING); + }else + { + glDisable(GL_LIGHTING); + } + + + glBegin(GL_QUADS); + glNormal3f(0,1,0); + for (int x =-GridSizeX/2;x +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_FLOOR_H +#define IGL_OPENGL2_DRAW_FLOOR_H +#include "../igl_inline.h" +namespace igl +{ + namespace opengl2 + { + + // Draw a checkerboard floor aligned with current (X,Z) plane using ../opengl/OpenGL_ + // calls. side=50 centered at (0,0): + // (-25,-25)-->(-25,25)-->(25,25)-->(25,-25) + // + // Use glPushMatrix(), glScaled(), glTranslated() to arrange the floor. + // + // Inputs: + // colorA float4 color + // colorB float4 color + // + // Example: + // // Draw a nice floor + // glPushMatrix(); + // glCullFace(GL_BACK); + // glEnable(GL_CULL_FACE); + // glEnable(GL_LIGHTING); + // glTranslated(0,-1,0); + // if(project(Vector3d(0,0,0))(2) - project(Vector3d(0,1,0))(2) > -FLOAT_EPS) + // { + // draw_floor_outline(); + // } + // draw_floor(); + // glPopMatrix(); + // glDisable(GL_CULL_FACE); + // + IGL_INLINE void draw_floor( + const float * colorA, + const float * colorB, + const int GridSizeX=100, + const int GridSizeY=100); + // Wrapper with default colors + IGL_INLINE void draw_floor(); + IGL_INLINE void draw_floor_outline( + const float * colorA, + const float * colorB, + const int GridSizeX=100, + const int GridSizeY=100); + // Wrapper with default colors + IGL_INLINE void draw_floor_outline(); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "draw_floor.cpp" +#endif +#endif diff --git a/src/igl/opengl2/draw_mesh.cpp b/src/igl/opengl2/draw_mesh.cpp new file mode 100644 index 000000000..aeaed12f0 --- /dev/null +++ b/src/igl/opengl2/draw_mesh.cpp @@ -0,0 +1,278 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "draw_mesh.h" + +IGL_INLINE void igl::opengl2::draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N) +{ + using namespace Eigen; + MatrixXd _d; + MatrixXi _i; + return draw_mesh(V,F,N,_i,_d,_d,_i,_d,0,_i,0); +} + +IGL_INLINE void igl::opengl2::draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C) +{ + using namespace Eigen; + MatrixXd _d; + MatrixXi _i; + return draw_mesh(V,F,N,_i,C,_d,_i,_d,0,_i,0); +} + +IGL_INLINE void igl::opengl2::draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC) +{ + using namespace Eigen; + MatrixXd _d; + MatrixXi _i; + return draw_mesh(V,F,N,_i,C,TC,_i,_d,0,_i,0); +} + +IGL_INLINE void igl::opengl2::draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC, + const Eigen::MatrixXd & W, + const GLuint W_index, + const Eigen::MatrixXi & WI, + const GLuint WI_index) +{ + using namespace Eigen; + return draw_mesh(V,F,N,MatrixXi(),C,TC,MatrixXi(),W,W_index,WI,WI_index); +} + +IGL_INLINE void igl::opengl2::draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXi & NF, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC, + const Eigen::MatrixXi & TF, + const Eigen::MatrixXd & W, + const GLuint W_index, + const Eigen::MatrixXi & WI, + const GLuint WI_index) +{ + using namespace std; + using namespace Eigen; + const int rF = F.rows(); + const int cF = F.cols(); + const int cC = C.cols(); + const int rC = C.rows(); + const int cW = W.cols(); + const int rW = W.rows(); + const int rV = V.rows(); + const int rTC = TC.rows(); + const int rTF = TF.rows(); + const int rNF = NF.rows(); + const int rN = N.rows(); + + if(F.size() > 0) + { + assert(F.maxCoeff() < V.rows()); + assert(V.cols() == 3); + assert(rC == rV || rC == rF || rC == rF*3 || rC==1 || C.size() == 0); + assert(C.cols() >= 3 || C.size() == 0); + assert(N.cols() == 3 || N.size() == 0); + assert(TC.cols() == 2 || TC.size() == 0); + assert(cF == 3 || cF == 4); + assert(TF.size() == 0 || TF.cols() == F.cols()); + assert(NF.size() == 0 || NF.cols() == NF.cols()); + } + if(W.size()>0) + { + assert(W.rows() == V.rows()); + assert(WI.rows() == V.rows()); + assert(W.cols() == WI.cols()); + } + + switch(F.cols()) + { + default: + case 3: + glBegin(GL_TRIANGLES); + break; + case 4: + glBegin(GL_QUADS); + break; + } + // loop over faces + for(int i = 0; i color; + if(rC == 1) + { + color = C.row(0); + }else if(rC == rV) + { + color = C.row(F(i,j)); + }else if(rC == rF*cF) + { + color = C.row(i*cF+j); + }else if(rC == rF) + { + color = C.row(i); + }else + { + assert(C.size() == 0); + } + + int n = -1; + if(rNF != 0) + { + n = NF(i,j); // indexed normals + } else if(rN == 1) + { + n = 0; // uniform normals + }else if(rN == rF) + { + n = i; // face normals + }else if(rN == rV) + { + n = F(i,j); // vertex normals + }else if(rN == rF*cF) + { + n = i*cF + j; // corner normals + }else + { + assert(N.size() == 0); + } + + { + if(rW>0 && W_index !=0 && WI_index != 0) + { + int weights_left = cW; + while(weights_left != 0) + { + int pass_size = std::min(4,weights_left); + int weights_already_passed = cW-weights_left; + // Get attribute location of next 4 weights + int pass_W_index = W_index + weights_already_passed/4; + int pass_WI_index = WI_index + weights_already_passed/4; + switch(pass_size) + { + case 1: + glVertexAttrib1d( + pass_W_index, + W(F(i,j),0+weights_already_passed)); + glVertexAttrib1d( + pass_WI_index, + WI(F(i,j),0+weights_already_passed)); + break; + case 2: + glVertexAttrib2d( + pass_W_index, + W(F(i,j),0+weights_already_passed), + W(F(i,j),1+weights_already_passed)); + glVertexAttrib2d( + pass_WI_index, + WI(F(i,j),0+weights_already_passed), + WI(F(i,j),1+weights_already_passed)); + break; + case 3: + glVertexAttrib3d( + pass_W_index, + W(F(i,j),0+weights_already_passed), + W(F(i,j),1+weights_already_passed), + W(F(i,j),2+weights_already_passed)); + glVertexAttrib3d( + pass_WI_index, + WI(F(i,j),0+weights_already_passed), + WI(F(i,j),1+weights_already_passed), + WI(F(i,j),2+weights_already_passed)); + break; + default: + glVertexAttrib4d( + pass_W_index, + W(F(i,j),0+weights_already_passed), + W(F(i,j),1+weights_already_passed), + W(F(i,j),2+weights_already_passed), + W(F(i,j),3+weights_already_passed)); + glVertexAttrib4d( + pass_WI_index, + WI(F(i,j),0+weights_already_passed), + WI(F(i,j),1+weights_already_passed), + WI(F(i,j),2+weights_already_passed), + WI(F(i,j),3+weights_already_passed)); + break; + } + weights_left -= pass_size; + } + } + if(tc != -1) + { + glTexCoord2d(TC(tc,0),TC(tc,1)); + } + if(rC>0) + { + switch(cC) + { + case 3: + glColor3dv(color.data()); + break; + case 4: + glColor4dv(color.data()); + break; + default: + break; + } + } + if(n != -1) + { + glNormal3d(N(n,0),N(n,1),N(n,2)); + } + glVertex3d(V(F(i,j),0),V(F(i,j),1),V(F(i,j),2)); + } + } + } + glEnd(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif + diff --git a/src/igl/opengl2/draw_mesh.h b/src/igl/opengl2/draw_mesh.h new file mode 100644 index 000000000..ea1559c20 --- /dev/null +++ b/src/igl/opengl2/draw_mesh.h @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_MESH_H +#define IGL_OPENGL2_DRAW_MESH_H +#include "../igl_inline.h" +#include "gl.h" +#include + + +namespace igl +{ + namespace opengl2 + { + + // Draw ../opengl/OpenGL_ commands needed to display a mesh with normals + // + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3|4 eigen Matrix of face (triangle/quad) indices + // N #V|#F by 3 eigen Matrix of 3D normals + IGL_INLINE void draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N); + + // Draw ../opengl/OpenGL_ commands needed to display a mesh with normals and per-vertex + // colors + // + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3|4 eigen Matrix of face (triangle/quad) indices + // N #V|#F by 3 eigen Matrix of 3D normals + // C #V|#F|1 by 3 eigen Matrix of RGB colors + IGL_INLINE void draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C); + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3|4 eigen Matrix of face (triangle/quad) indices + // N #V|#F by 3 eigen Matrix of 3D normals + // C #V|#F|1 by 3 eigen Matrix of RGB colors + // TC #V|#F|1 by 3 eigen Matrix of Texture Coordinates + IGL_INLINE void draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC); + + // Draw ../opengl/OpenGL_ commands needed to display a mesh with normals, per-vertex + // colors and LBS weights + // + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3|4 eigen Matrix of face (triangle/quad) indices + // N #V by 3 eigen Matrix of mesh vertex 3D normals + // C #V by 3 eigen Matrix of mesh vertex RGB colors + // TC #V by 3 eigen Matrix of mesh vertex UC coorindates between 0 and 1 + // W #V by #H eigen Matrix of per mesh vertex, per handle weights + // W_index Specifies the index of the "weight" vertex attribute: see + // glBindAttribLocation, if W_index is 0 then weights are ignored + // WI #V by #H eigen Matrix of per mesh vertex, per handle weight ids + // WI_index Specifies the index of the "weight" vertex attribute: see + // glBindAttribLocation, if WI_index is 0 then weight indices are ignored + IGL_INLINE void draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC, + const Eigen::MatrixXd & W, + const GLuint W_index, + const Eigen::MatrixXi & WI, + const GLuint WI_index); + + // Draw ../opengl/OpenGL_ commands needed to display a mesh with normals, per-vertex + // colors and LBS weights + // + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3|4 eigen Matrix of face (triangle/quad) indices + // N #V by 3 eigen Matrix of mesh vertex 3D normals + // NF #F by 3 eigen Matrix of face (triangle/quad) normal indices, <0 + // means no normal + // C #V by 3 eigen Matrix of mesh vertex RGB colors + // TC #V by 3 eigen Matrix of mesh vertex UC coorindates between 0 and 1 + // TF #F by 3 eigen Matrix of face (triangle/quad) texture indices, <0 + // means no texture + // W #V by #H eigen Matrix of per mesh vertex, per handle weights + // W_index Specifies the index of the "weight" vertex attribute: see + // glBindAttribLocation, if W_index is 0 then weights are ignored + // WI #V by #H eigen Matrix of per mesh vertex, per handle weight ids + // WI_index Specifies the index of the "weight" vertex attribute: see + // glBindAttribLocation, if WI_index is 0 then weight indices are ignored + IGL_INLINE void draw_mesh( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXd & N, + const Eigen::MatrixXi & NF, + const Eigen::MatrixXd & C, + const Eigen::MatrixXd & TC, + const Eigen::MatrixXi & TF, + const Eigen::MatrixXd & W, + const GLuint W_index, + const Eigen::MatrixXi & WI, + const GLuint WI_index); + + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "draw_mesh.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/draw_point.cpp b/src/igl/opengl2/draw_point.cpp new file mode 100644 index 000000000..910fcc716 --- /dev/null +++ b/src/igl/opengl2/draw_point.cpp @@ -0,0 +1,100 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "draw_point.h" + +// Implementation +#include "gl.h" + +#include +#include + +IGL_INLINE void igl::opengl2::draw_point( + const double x, + const double y, + const double z, + const double requested_r, + const bool selected) +{ + // Push GL settings + glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING_BIT); + + float f; + glGetFloatv(GL_POINT_SIZE_MAX,&f); + // THIS IS OVERZEALOUS on Mac OS X: OpenGL reports a smaller point size than + // possible. + //assert(requested_r<=0.5*f); + double r = (requested_r<0.5*f?requested_r:0.5*f); + + //glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + // get current color + float color[4]; + glGetFloatv(GL_CURRENT_COLOR,color); + + double outline_size = (r>7 ? sqrt(r/7.0) : 1.0); + + // White outline + glColor4f(1,1,1,color[3]); + glPointSize(2*r); + glBegin(GL_POINTS); + glVertex3d(x,y,z); + glEnd(); + // Black outline + glColor4f(0,0,0,color[3]); + glPointSize(2*r-2*outline_size); + glBegin(GL_POINTS); + glVertex3d(x,y,z); + glEnd(); + + // Foreground + glColor4fv(color); + glPointSize(2*r-4*outline_size); + glBegin(GL_POINTS); + glVertex3d(x,y,z); + glEnd(); + // Selection inner circle + if(selected) + { + glColor4f(0,0,0,color[3]); + double selected_size = 2*r-7*outline_size; + selected_size = (selected_size>3?selected_size:3); + glPointSize(selected_size); + glBegin(GL_POINTS); + glVertex3d(x,y,z); + glEnd(); + } + + // reset color + glColor4fv(color); + + // Pop GL settings + glPopAttrib(); +} + +template +IGL_INLINE void igl::opengl2::draw_point( + const Eigen::PlainObjectBase & P, + const double requested_r, + const bool selected) +{ + switch(P.size()) + { + case 2: + return draw_point(P(0),P(1),0,requested_r,selected); + default: + return draw_point(P(0),P(1),P(2),requested_r,selected); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::opengl2::draw_point >(Eigen::PlainObjectBase > const&, double, bool); +template void igl::opengl2::draw_point >(Eigen::PlainObjectBase > const&, double, bool); +#endif + diff --git a/src/igl/opengl2/draw_point.h b/src/igl/opengl2/draw_point.h new file mode 100644 index 000000000..2c7cc1c7d --- /dev/null +++ b/src/igl/opengl2/draw_point.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_POINT_H +#define IGL_OPENGL2_DRAW_POINT_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace opengl2 + { + + //double POINT_COLOR[3] = {239./255.,213./255.,46./255.}; + // Draw a nice looking, colored dot at a given point in 3d. + // + // Note: expects that GL_CURRENT_COLOR is set with the desired foreground color + // + // Inputs: + // x x-coordinate of point, modelview coordinates + // y y-coordinate of point, modelview coordinates + // z z-coordinate of point, modelview coordinates + // requested_r outer-most radius of dot {7}, measured in screen space pixels + // selected fills inner circle with black {false} + // Asserts that requested_r does not exceed 0.5*GL_POINT_SIZE_MAX + IGL_INLINE void draw_point( + const double x, + const double y, + const double z, + const double requested_r = 7, + const bool selected = false); + template + IGL_INLINE void draw_point( + const Eigen::PlainObjectBase & P, + const double requested_r = 7, + const bool selected = false); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "draw_point.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/draw_rectangular_marquee.cpp b/src/igl/opengl2/draw_rectangular_marquee.cpp new file mode 100644 index 000000000..73f97c844 --- /dev/null +++ b/src/igl/opengl2/draw_rectangular_marquee.cpp @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + + +#include "draw_rectangular_marquee.h" +#include "gl.h" +#include "glu.h" +#include "../material_colors.h" + +IGL_INLINE void igl::opengl2::draw_rectangular_marquee( + const int from_x, + const int from_y, + const int to_x, + const int to_y) +{ + using namespace std; + int l; + glGetIntegerv(GL_LIGHTING,&l); + int s; + glGetIntegerv(GL_LINE_STIPPLE,&s); + double lw; + glGetDoublev(GL_LINE_WIDTH,&lw); + glDisable(GL_LIGHTING); + // Screen space for this viewport + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT,viewport); + const int width = viewport[2]; + const int height = viewport[3]; + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + gluOrtho2D(0,width,0,height); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glEnable(GL_LINE_STIPPLE); + glLineStipple(3,0xAAAA); + glLineWidth(1); + glColor4f(0.2,0.2,0.2,1); + glBegin(GL_LINE_STRIP); + glVertex2d(from_x,from_y); + glVertex2d(to_x,from_y); + glVertex2d(to_x,to_y); + glVertex2d(from_x,to_y); + glVertex2d(from_x,from_y); + glEnd(); + + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glLineWidth(lw); + (s ? glEnable(GL_LINE_STIPPLE):glDisable(GL_LINE_STIPPLE)); + (l ? glEnable(GL_LIGHTING):glDisable(GL_LIGHTING)); +} + diff --git a/src/igl/opengl2/draw_rectangular_marquee.h b/src/igl/opengl2/draw_rectangular_marquee.h new file mode 100644 index 000000000..bbb2e3f18 --- /dev/null +++ b/src/igl/opengl2/draw_rectangular_marquee.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_RECTANGULAR_MARQUEE_H +#define IGL_OPENGL2_DRAW_RECTANGULAR_MARQUEE_H +#include "../igl_inline.h" +namespace igl +{ + namespace opengl2 + { + // Draw a rectangular marquee (selection box) in screen space. This sets up + // screen space using current viewport. + // + // Inputs: + // from_x x coordinate of from point + // from_y y coordinate of from point + // to_x x coordinate of to point + // to_y y coordinate of to point + IGL_INLINE void draw_rectangular_marquee( + const int from_x, + const int from_y, + const int to_x, + const int to_y); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "draw_rectangular_marquee.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/draw_skeleton_3d.cpp b/src/igl/opengl2/draw_skeleton_3d.cpp new file mode 100644 index 000000000..a0ca6139a --- /dev/null +++ b/src/igl/opengl2/draw_skeleton_3d.cpp @@ -0,0 +1,166 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "draw_skeleton_3d.h" +#include "../PI.h" +#include "../material_colors.h" +#include "gl.h" +#include +#include + + +template < + typename DerivedC, + typename DerivedBE, + typename DerivedT, + typename Derivedcolor> +IGL_INLINE void igl::opengl2::draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & color, + const double half_bbd) +{ + // Note: Maya's skeleton *does* scale with the mesh suggesting a scale + // parameter. Further, its joint balls are not rotated with the bones. + using namespace Eigen; + using namespace std; + if(color.size() == 0) + { + return draw_skeleton_3d(C,BE,T,MAYA_SEA_GREEN,half_bbd); + } + assert(color.cols() == 4 || color.size() == 4); + if(T.size() == 0) + { + typedef Eigen::Matrix Mat; + Mat I = Mat::Identity(C.cols()+1,C.cols()); + Mat T = I.replicate(BE.rows(),1); + // insane base case + if(T.size() == 0) + { + return; + } + return draw_skeleton_3d(C,BE,T,color,half_bbd); + } + assert(T.rows() == BE.rows()*(C.cols()+1)); + assert(T.cols() == C.cols()); + // push old settings + glPushAttrib(GL_LIGHTING_BIT); + glPushAttrib(GL_LINE_BIT); + glDisable(GL_LIGHTING); + glLineWidth(1.0); + + auto draw_sphere = [](const double r) + { + auto draw_circle = []() + { + glBegin(GL_LINE_STRIP); + for(double th = 0;th<2.0*igl::PI;th+=(2.0*igl::PI/30.0)) + { + glVertex3d(cos(th),sin(th),0.0); + } + glVertex3d(cos(0),sin(0),0.0); + glEnd(); + }; + glPushMatrix(); + glScaled(r,r,r); + draw_circle(); + glRotated(90.0,1.0,0.0,0.0); + draw_circle(); + glRotated(90.0,0.0,1.0,0.0); + draw_circle(); + glPopMatrix(); + }; + auto draw_pyramid = []() + { + glBegin(GL_LINE_STRIP); + glVertex3d(0, 1,-1); + glVertex3d(0,-1,-1); + glVertex3d(0,-1, 1); + glVertex3d(0, 1, 1); + glVertex3d(0, 1,-1); + glEnd(); + glBegin(GL_LINES); + glVertex3d(0, 1,-1); + glVertex3d(1,0,0); + glVertex3d(0,-1,-1); + glVertex3d(1,0,0); + glVertex3d(0,-1, 1); + glVertex3d(1,0,0); + glVertex3d(0, 1, 1); + glVertex3d(1,0,0); + glEnd(); + }; + + // Loop over bones + for(int e = 0;e < BE.rows();e++) + { + // Draw a sphere + auto s = C.row(BE(e,0)); + auto d = C.row(BE(e,1)); + auto b = (d-s).transpose().eval(); + double r = 0.02*half_bbd; + Matrix4d Te = Matrix4d::Identity(); + Te.block(0,0,3,4) = T.block(e*4,0,4,3).transpose(); + Quaterniond q; + q.setFromTwoVectors(Vector3d(1,0,0),b); + glPushMatrix(); + glMultMatrixd(Te.data()); + glTranslated(s(0),s(1),s(2)); + if(color.size() == 4) + { + glColor4d( color(0), color(1), color(2), color(3)); + }else + { + glColor4d( color(e,0), color(e,1), color(e,2), color(e,3)); + } + draw_sphere(r); + const double len = b.norm()-2.*r; + if(len>=0) + { + auto u = b.normalized()*r; + glPushMatrix(); + glTranslated(u(0),u(1),u(2)); + glMultMatrixd(Affine3d(q).matrix().data()); + glScaled(b.norm()-2.*r,r,r); + draw_pyramid(); + glPopMatrix(); + } + glTranslated(b(0),b(1),b(2)); + draw_sphere(r); + glPopMatrix(); + } + // Reset settings + glPopAttrib(); + glPopAttrib(); +} + +template +IGL_INLINE void igl::opengl2::draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T) +{ + return draw_skeleton_3d(C,BE,T,MAYA_SEA_GREEN); +} + +template +IGL_INLINE void igl::opengl2::draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE) +{ + return draw_skeleton_3d(C,BE,Eigen::MatrixXd(),MAYA_SEA_GREEN); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::opengl2::draw_skeleton_3d, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template void igl::opengl2::draw_skeleton_3d, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template void igl::opengl2::draw_skeleton_3d, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double); +template void igl::opengl2::draw_skeleton_3d, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double); +#endif diff --git a/src/igl/opengl2/draw_skeleton_3d.h b/src/igl/opengl2/draw_skeleton_3d.h new file mode 100644 index 000000000..7bbb59717 --- /dev/null +++ b/src/igl/opengl2/draw_skeleton_3d.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_SKELETON_3D_H +#define IGL_OPENGL2_DRAW_SKELETON_3D_H +#include "../igl_inline.h" +#include "../material_colors.h" +#include +namespace igl +{ + namespace opengl2 + { + + // Draw a skeleton + // + // Inputs: + // C #C by dim List of joint rest positions + // BE #BE by 2 list of bone edge indices into C + // T #BE*(dim+1) by dim matrix of stacked transposed bone transformations + // color #BE|1 by 4 list of color + // half_bbd half bounding box diagonal to determine scaling {1.0} + template < + typename DerivedC, + typename DerivedBE, + typename DerivedT, + typename Derivedcolor> + IGL_INLINE void draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & color, + const double half_bbd=0.5); + // Default color + template + IGL_INLINE void draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T); + template + IGL_INLINE void draw_skeleton_3d( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "draw_skeleton_3d.cpp" +#endif +#endif diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.cpp b/src/igl/opengl2/draw_skeleton_vector_graphics.cpp new file mode 100644 index 000000000..e45706120 --- /dev/null +++ b/src/igl/opengl2/draw_skeleton_vector_graphics.cpp @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "draw_skeleton_vector_graphics.h" +#include "draw_point.h" +#include "gl.h" +#include "../material_colors.h" + +IGL_INLINE void igl::opengl2::draw_skeleton_vector_graphics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE) +{ + return draw_skeleton_vector_graphics(C,BE,BBW_POINT_COLOR,BBW_LINE_COLOR); +} + +IGL_INLINE void igl::opengl2::draw_skeleton_vector_graphics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const float * point_color, + const float * line_color) +{ + using namespace Eigen; + + int old_lighting=0; + double old_line_width=1; + glGetIntegerv(GL_LIGHTING,&old_lighting); + glGetDoublev(GL_LINE_WIDTH,&old_line_width); + int cm; + glGetIntegerv(GL_COLOR_MATERIAL,&cm); + glDisable(GL_LIGHTING); + glDisable(GL_LINE_STIPPLE); + //glEnable(GL_POLYGON_OFFSET_FILL); + glEnable(GL_COLOR_MATERIAL); + glLineWidth(10.0); + glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE); + float mat_ambient[4] = {0.1,0.1,0.1,1.0}; + float mat_specular[4] = {0.0,0.0,0.0,1.0}; + float mat_shininess = 1; + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular); + glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess); + + for(int i = 0;i<3;i++) + { + switch(i) + { + case 0: glColor3fv(WHITE); glLineWidth(10); break; + case 1: glColor3fv(BLACK); glLineWidth( 6); break; + case 2: glColor3fv(line_color); glLineWidth( 4); break; + } + // Loop over bone edges + glBegin(GL_LINES); + for(int e = 0;e +IGL_INLINE void igl::opengl2::draw_skeleton_vector_graphics( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T) +{ + return draw_skeleton_vector_graphics(C,BE,T,BBW_POINT_COLOR,BBW_LINE_COLOR); +} + +template +IGL_INLINE void igl::opengl2::draw_skeleton_vector_graphics( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T, + const float * point_color, + const float * line_color) +{ + DerivedC CT; + DerivedBE BET; + const int dim = T.cols(); + assert(dim == C.cols()); + CT.resize(2*BE.rows(),C.cols()); + BET.resize(BE.rows(),2); + for(int e = 0;e, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.h b/src/igl/opengl2/draw_skeleton_vector_graphics.h new file mode 100644 index 000000000..8091cbb78 --- /dev/null +++ b/src/igl/opengl2/draw_skeleton_vector_graphics.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_DRAW_SKELETON_VECTOR_GRAPHICS_H +#define IGL_OPENGL2_DRAW_SKELETON_VECTOR_GRAPHICS_H +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace opengl2 + { + // Draw a skeleton with a 2D vector graphcis style à la BBW, STBS, Monotonic, + // FAST papers. + // + // Inputs: + // C #C by dim list of joint positions + // BE #BE by 2 list of bone edge indices into C + // point_color color of points + // line_color color of lines + IGL_INLINE void draw_skeleton_vector_graphics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE, + const float * point_color, + const float * line_color); + // Use default colors (originally from BBW paper) + IGL_INLINE void draw_skeleton_vector_graphics( + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & BE); + // T #BE*(dim+1) by dim matrix of stacked transposed bone transformations + template + IGL_INLINE void draw_skeleton_vector_graphics( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T, + const float * point_color, + const float * line_color); + template + IGL_INLINE void draw_skeleton_vector_graphics( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & BE, + const Eigen::PlainObjectBase & T); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "draw_skeleton_vector_graphics.cpp" +#endif +#endif diff --git a/src/igl/opengl2/flare_textures.h b/src/igl/opengl2/flare_textures.h new file mode 100644 index 000000000..0455195eb --- /dev/null +++ b/src/igl/opengl2/flare_textures.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_FLARE_TEXTURES_H +#define IGL_OPENGL2_FLARE_TEXTURES_H +#include +namespace igl +{ + namespace opengl2 + { + + const uint8_t FLARE_TEXTURE_0[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,12,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,12,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,12,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,20,20,20,20,20,23,23,23,23,23,23,23,23,23,23,23,23,23,23,20,20,20,20,20,20,20,20,15,15,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,15,20,20,20,20,20,20,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,23,20,20,20,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,20,20,23,23,28,28,28,28,28,28,28,31,31,31,31,31,31,31,31,31,31,31,31,31,31,28,28,28,28,28,28,28,28,23,23,20,20,20,20,15,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,20,23,28,28,28,28,28,28,31,31,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,31,31,31,28,28,28,28,28,23,23,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,15,20,20,20,23,23,28,28,28,28,31,31,36,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,39,39,39,39,36,36,36,36,36,36,36,36,31,28,28,28,28,28,23,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,28,28,31,31,36,36,36,36,36,39,39,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,39,39,36,36,36,36,36,36,31,28,28,28,28,23,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,28,28,31,36,36,36,36,39,39,44,44,44,44,44,44,44,44,47,47,47,47,47,47,47,47,47,47,47,47,47,44,44,44,44,44,44,44,39,39,36,36,36,36,31,31,28,28,28,23,23,20,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,20,23,28,28,28,31,31,36,36,36,36,39,44,44,44,44,44,47,47,47,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,47,47,44,44,44,44,44,39,39,36,36,36,36,31,28,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,28,28,28,31,36,36,36,39,44,44,44,44,44,47,47,52,52,52,52,52,52,52,55,55,55,55,55,55,55,55,55,55,55,55,55,52,52,52,52,52,52,52,47,47,44,44,44,44,39,36,36,36,36,31,28,28,28,23,20,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,52,52,55,55,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,55,55,52,52,52,52,52,47,47,44,44,44,39,39,36,36,36,31,28,28,28,23,20,20,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,55,55,60,60,60,60,60,60,60,63,63,63,63,63,63,63,63,63,63,63,63,60,60,60,60,60,60,60,60,55,52,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,23,20,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,15,20,20,23,28,28,28,36,36,36,39,44,44,44,47,47,52,52,52,55,55,60,60,60,60,60,63,63,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,63,63,63,60,60,60,60,60,55,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,20,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,20,20,23,28,28,31,36,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,63,63,68,68,68,68,68,68,68,71,71,71,71,71,71,71,71,71,71,71,71,68,68,68,68,68,68,68,63,63,60,60,60,60,55,52,52,52,52,47,44,44,44,39,36,36,36,28,28,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,20,28,28,28,31,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,63,68,68,68,68,68,71,71,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,71,71,68,68,68,68,68,63,63,60,60,60,55,55,52,52,52,47,44,44,39,36,36,36,31,28,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,31,36,36,36,39,44,44,47,52,52,52,55,60,60,60,63,63,68,68,68,68,71,76,76,76,76,76,76,76,79,79,79,79,79,79,79,79,79,79,79,79,76,76,76,76,76,76,76,71,71,68,68,68,68,63,60,60,60,55,52,52,52,47,44,44,44,39,36,36,31,28,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,31,36,36,39,44,44,44,47,52,52,55,60,60,60,63,63,68,68,68,71,71,76,76,76,76,79,79,79,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,79,79,76,76,76,76,76,71,68,68,68,68,63,60,60,60,55,52,52,52,47,44,44,39,36,36,31,28,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,31,36,36,39,44,44,47,52,52,52,55,60,60,60,63,68,68,68,71,76,76,76,76,79,79,84,84,84,84,84,84,84,87,87,87,87,87,87,87,87,87,87,87,84,84,84,84,84,84,84,79,76,76,76,76,71,71,68,68,68,63,60,60,60,55,52,52,47,44,44,44,36,36,36,28,28,28,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,31,36,36,39,44,44,47,52,52,55,60,60,60,63,68,68,68,71,76,76,76,76,79,84,84,84,84,84,87,87,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,87,87,84,84,84,84,84,79,76,76,76,71,71,68,68,68,60,60,60,55,52,52,52,44,44,44,36,36,36,28,28,28,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,31,36,36,39,44,44,47,52,52,55,60,60,63,68,68,68,71,76,76,76,79,79,84,84,84,84,87,92,92,92,92,92,92,92,95,95,95,95,95,95,95,95,95,95,95,92,92,92,92,92,92,87,87,84,84,84,84,79,76,76,76,71,68,68,68,63,60,60,60,52,52,52,47,44,44,39,36,36,28,28,28,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,31,36,36,39,44,44,47,52,52,55,60,60,63,68,68,68,71,76,76,79,79,84,84,84,87,92,92,92,92,92,95,95,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,95,95,92,92,92,92,92,87,84,84,84,84,79,76,76,76,71,68,68,68,60,60,60,55,52,52,47,44,44,39,36,36,28,28,23,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,28,28,31,36,36,39,44,44,52,52,52,60,60,60,63,68,68,71,76,76,76,79,84,84,84,87,92,92,92,92,95,100,100,100,100,100,100,100,103,103,103,103,103,103,103,103,103,103,100,100,100,100,100,100,100,95,95,92,92,92,87,87,84,84,84,79,76,76,71,68,68,68,63,60,60,55,52,52,47,44,44,39,36,36,28,28,23,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,28,28,31,36,36,39,44,44,52,52,52,60,60,60,68,68,68,71,76,76,79,84,84,84,87,92,92,92,95,95,100,100,100,100,103,103,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,103,103,100,100,100,100,100,95,92,92,92,87,84,84,84,79,76,76,76,71,68,68,63,60,60,55,52,52,47,44,44,39,36,36,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,23,28,31,36,36,39,44,44,52,52,52,60,60,63,68,68,68,76,76,76,79,84,84,87,92,92,92,95,95,100,100,100,103,103,108,108,108,108,108,108,111,111,111,111,111,111,111,111,111,111,108,108,108,108,108,108,108,103,100,100,100,100,95,92,92,92,87,84,84,84,79,76,76,71,68,68,63,60,60,55,52,52,47,44,44,36,36,31,28,28,23,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,36,36,39,44,44,47,52,52,60,60,63,68,68,71,76,76,79,84,84,84,87,92,92,92,95,100,100,100,103,108,108,108,108,108,111,111,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,111,111,108,108,108,108,103,103,100,100,100,95,92,92,92,87,84,84,79,76,76,71,68,68,63,60,60,55,52,52,47,44,44,36,36,31,28,28,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,31,36,39,44,44,47,52,52,60,60,63,68,68,71,76,76,79,84,84,87,92,92,92,95,100,100,100,103,108,108,108,108,111,116,116,116,116,116,116,119,119,119,119,119,119,119,119,119,119,116,116,116,116,116,116,111,111,108,108,108,103,103,100,100,100,95,92,92,87,84,84,79,76,76,71,68,68,63,60,60,55,52,52,44,44,39,36,36,31,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,31,36,36,44,44,47,52,52,60,60,63,68,68,71,76,76,79,84,84,87,92,92,92,100,100,100,103,108,108,108,111,111,116,116,116,116,119,119,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,119,119,116,116,116,116,111,108,108,108,103,100,100,100,95,92,92,87,84,84,84,76,76,76,68,68,63,60,60,55,52,52,44,44,39,36,36,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,36,36,39,44,47,52,52,55,60,60,68,68,71,76,76,79,84,84,87,92,92,95,100,100,100,108,108,108,111,116,116,116,116,119,119,124,124,124,124,124,124,127,127,127,127,127,127,127,127,127,124,124,124,124,124,124,119,116,116,116,116,111,108,108,108,103,100,100,100,92,92,92,84,84,84,76,76,76,68,68,63,60,60,55,52,47,44,44,39,36,31,28,28,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,36,36,39,44,44,52,52,55,60,60,68,68,71,76,76,79,84,84,87,92,92,95,100,100,103,108,108,108,111,116,116,116,119,124,124,124,124,127,127,132,132,132,132,132,132,132,132,132,132,132,132,132,132,127,127,124,124,124,124,119,119,116,116,116,111,108,108,103,100,100,100,95,92,92,87,84,84,76,76,71,68,68,63,60,60,52,52,47,44,44,36,36,31,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,28,28,31,36,36,44,44,47,52,55,60,60,63,68,68,76,76,79,84,84,87,92,92,95,100,100,103,108,108,111,116,116,116,119,124,124,124,124,127,132,132,132,132,132,132,135,135,135,135,135,135,135,135,135,132,132,132,132,132,132,127,124,124,124,119,119,116,116,111,108,108,108,103,100,100,95,92,92,87,84,84,76,76,71,68,68,63,60,55,52,52,44,44,39,36,36,28,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,36,36,39,44,47,52,52,60,60,63,68,68,76,76,79,84,84,87,92,92,100,100,100,108,108,108,111,116,116,119,124,124,124,127,127,132,132,132,132,135,140,140,140,140,140,140,140,140,140,140,140,140,140,140,135,135,132,132,132,132,127,124,124,124,119,116,116,116,111,108,108,103,100,100,95,92,92,87,84,84,76,76,71,68,68,60,60,55,52,52,44,44,39,36,31,28,28,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,31,36,39,44,44,52,52,55,60,63,68,68,71,76,79,84,84,87,92,92,100,100,100,108,108,108,116,116,116,119,124,124,124,127,132,132,132,135,140,140,140,140,140,140,143,143,143,143,143,143,143,143,140,140,140,140,140,140,135,135,132,132,132,127,124,124,124,119,116,116,111,108,108,103,100,100,95,92,92,84,84,79,76,76,71,68,63,60,60,52,52,47,44,44,36,36,28,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,36,36,44,44,47,52,55,60,60,68,68,71,76,76,84,84,87,92,92,95,100,100,108,108,111,116,116,116,124,124,124,127,132,132,132,135,140,140,140,140,143,143,148,148,148,148,148,148,148,148,148,148,148,148,148,143,143,140,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,103,100,100,95,92,92,84,84,79,76,76,68,68,63,60,55,52,52,44,44,39,36,31,28,28,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,28,28,31,36,39,44,44,52,52,60,60,63,68,68,76,76,79,84,87,92,92,95,100,100,108,108,111,116,116,119,124,124,124,132,132,132,135,140,140,140,143,143,148,148,148,148,148,151,151,151,151,151,151,151,151,148,148,148,148,148,148,143,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,103,100,100,92,92,87,84,84,79,76,71,68,68,60,60,55,52,47,44,44,36,36,31,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,31,36,36,44,44,47,52,55,60,60,68,68,71,76,79,84,84,92,92,95,100,100,108,108,111,116,116,119,124,124,127,132,132,132,140,140,140,143,148,148,148,148,151,151,156,156,156,156,156,156,156,156,156,156,156,156,151,151,148,148,148,148,143,140,140,140,135,132,132,127,124,124,119,116,116,111,108,108,103,100,100,92,92,87,84,84,76,76,71,68,63,60,60,52,52,47,44,39,36,31,28,28,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,28,28,31,36,39,44,47,52,52,60,60,63,68,71,76,76,84,84,87,92,92,100,100,103,108,108,116,116,119,124,124,127,132,132,135,140,140,140,143,148,148,148,151,156,156,156,156,156,156,159,159,159,159,159,159,159,156,156,156,156,156,151,151,148,148,148,143,140,140,135,132,132,132,124,124,119,116,116,111,108,108,100,100,95,92,92,84,84,79,76,76,68,68,60,60,55,52,47,44,44,36,36,28,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,28,36,36,44,44,47,52,55,60,63,68,68,76,76,79,84,87,92,92,100,100,103,108,108,116,116,119,124,124,127,132,132,135,140,140,143,148,148,148,151,156,156,156,156,159,164,164,164,164,164,164,164,164,164,164,164,164,159,159,156,156,156,156,151,148,148,143,140,140,140,132,132,132,124,124,119,116,116,111,108,108,100,100,95,92,87,84,84,76,76,71,68,63,60,60,52,52,47,44,39,36,31,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,39,44,44,52,52,60,60,63,68,71,76,79,84,84,92,92,95,100,100,108,108,111,116,116,124,124,127,132,132,135,140,140,143,148,148,151,156,156,156,159,159,164,164,164,164,164,167,167,167,167,167,167,167,164,164,164,164,164,159,156,156,156,151,148,148,148,143,140,140,135,132,132,124,124,119,116,116,108,108,103,100,100,92,92,87,84,79,76,76,68,68,63,60,55,52,47,44,44,36,36,28,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,44,44,47,52,55,60,63,68,68,76,76,79,84,87,92,92,100,100,108,108,111,116,116,124,124,127,132,132,135,140,140,143,148,148,151,156,156,159,164,164,164,164,167,167,172,172,172,172,172,172,172,172,172,172,172,167,164,164,164,164,159,156,156,156,151,148,148,143,140,140,135,132,132,124,124,119,116,116,108,108,103,100,95,92,92,84,84,79,76,71,68,63,60,60,52,52,44,44,39,36,31,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,36,44,44,52,52,60,60,63,68,71,76,79,84,84,92,92,95,100,103,108,108,116,116,119,124,124,132,132,135,140,140,143,148,148,156,156,156,159,164,164,164,167,172,172,172,172,172,175,175,175,175,175,175,175,172,172,172,172,172,167,164,164,164,159,156,156,151,148,148,143,140,140,132,132,127,124,124,116,116,111,108,108,100,100,95,92,87,84,79,76,76,68,68,60,60,55,52,47,44,39,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,31,36,39,44,47,52,55,60,60,68,68,76,76,79,84,87,92,95,100,100,108,108,111,116,119,124,124,132,132,135,140,140,143,148,148,156,156,156,164,164,164,167,172,172,172,175,175,180,180,180,180,180,180,180,180,180,180,180,175,172,172,172,167,164,164,164,159,156,156,151,148,148,143,140,140,132,132,127,124,124,116,116,108,108,103,100,95,92,92,84,84,79,76,71,68,63,60,60,52,52,44,44,36,36,28,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,44,44,52,52,55,60,63,68,71,76,76,84,84,92,92,95,100,103,108,111,116,116,124,124,127,132,132,140,140,143,148,148,156,156,156,164,164,164,172,172,172,175,180,180,180,180,180,183,183,183,183,183,183,180,180,180,180,180,175,172,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,132,124,124,119,116,111,108,108,100,100,95,92,87,84,79,76,76,68,68,60,60,52,52,47,44,39,36,31,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,44,52,52,60,60,68,68,71,76,79,84,87,92,95,100,100,108,108,116,116,119,124,124,132,132,140,140,143,148,148,156,156,156,164,164,167,172,172,172,175,180,180,180,183,188,188,188,188,188,188,188,188,188,188,183,183,180,180,180,175,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,127,124,124,116,116,111,108,103,100,95,92,92,84,84,76,76,71,68,63,60,55,52,47,44,39,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,39,44,47,52,55,60,63,68,68,76,76,84,84,92,92,95,100,103,108,111,116,116,124,124,127,132,135,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,180,183,188,188,188,188,191,191,191,191,191,191,188,188,188,188,188,183,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,140,132,132,127,124,119,116,111,108,108,100,100,92,92,87,84,79,76,71,68,63,60,60,52,52,44,44,36,36,28,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,47,52,55,60,63,68,71,76,79,84,84,92,92,100,100,108,108,111,116,119,124,127,132,132,140,140,143,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,188,191,191,196,196,196,196,196,196,196,196,196,191,188,188,188,183,180,180,180,172,172,167,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,111,108,103,100,95,92,87,84,84,76,76,68,68,60,60,52,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,44,44,52,52,60,60,68,68,76,76,79,84,87,92,95,100,103,108,108,116,116,124,124,127,132,135,140,143,148,148,156,156,159,164,164,172,172,175,180,180,183,188,188,191,196,196,196,196,196,199,199,199,199,199,196,196,196,196,191,188,188,188,180,180,180,172,172,167,164,164,156,156,151,148,143,140,140,132,132,127,124,119,116,111,108,108,100,100,92,92,84,84,76,76,71,68,63,60,55,52,47,44,39,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,52,60,60,68,68,76,76,84,84,92,92,100,100,103,108,111,116,119,124,124,132,132,140,140,148,148,151,156,159,164,164,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,204,204,204,204,204,204,199,199,196,196,196,191,188,188,180,180,180,172,172,167,164,159,156,156,148,148,143,140,135,132,127,124,124,116,116,108,108,100,100,95,92,87,84,79,76,71,68,63,60,55,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,84,92,92,100,100,108,108,116,116,119,124,127,132,135,140,143,148,148,156,156,164,164,167,172,172,180,180,183,188,188,191,196,196,199,204,204,204,204,207,207,207,207,207,204,204,204,204,196,196,196,191,188,188,180,180,175,172,172,164,164,159,156,151,148,143,140,140,132,132,124,124,119,116,111,108,103,100,95,92,87,84,79,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,108,116,116,124,124,132,132,140,140,143,148,151,156,159,164,164,172,172,175,180,183,188,188,191,196,196,199,204,204,207,212,212,212,212,212,212,212,212,207,204,204,204,199,196,196,191,188,188,180,180,175,172,167,164,164,156,156,148,148,140,140,135,132,127,124,119,116,111,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,52,52,60,60,68,68,76,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,132,140,140,148,148,156,156,159,164,167,172,175,180,180,188,188,191,196,196,204,204,204,207,212,212,212,215,215,215,215,212,212,212,212,207,204,204,199,196,196,188,188,183,180,180,172,172,164,164,159,156,151,148,143,140,135,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,71,68,63,60,55,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,52,52,60,60,68,68,76,76,84,84,92,92,100,100,108,108,111,116,119,124,127,132,135,140,143,148,151,156,156,164,164,172,172,180,180,183,188,191,196,196,199,204,204,212,212,212,215,220,220,220,220,220,220,220,215,212,212,207,204,204,199,196,191,188,188,180,180,175,172,167,164,159,156,156,148,148,140,140,132,132,124,124,116,116,108,108,103,100,95,92,87,84,79,76,71,68,63,60,55,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,7,12,15,20,23,28,31,36,36,44,44,52,52,60,60,68,68,76,76,84,84,92,92,100,100,108,108,116,116,124,124,132,132,140,140,143,148,151,156,159,164,167,172,175,180,180,188,188,196,196,199,204,204,212,212,215,220,220,220,220,223,223,223,220,220,220,215,212,212,207,204,204,196,196,191,188,183,180,175,172,172,164,164,156,156,148,148,140,140,135,132,127,124,119,116,111,108,103,100,95,92,87,84,79,76,71,68,63,60,55,52,47,44,39,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,84,92,92,100,100,108,108,116,116,124,124,132,132,140,140,148,148,156,156,164,164,167,172,175,180,183,188,191,196,196,204,204,207,212,215,220,220,223,228,228,228,228,228,228,223,220,220,215,212,212,207,204,199,196,196,188,188,180,180,172,172,164,164,159,156,151,148,143,140,135,132,127,124,119,116,111,108,103,100,95,92,87,84,79,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,124,132,132,140,140,148,148,156,156,164,164,172,172,180,180,188,188,191,196,199,204,207,212,212,220,220,223,228,228,228,231,231,231,228,228,228,220,220,215,212,212,204,204,196,196,188,188,183,180,175,172,167,164,159,156,151,148,143,140,135,132,127,124,119,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,156,164,164,172,172,180,180,188,188,196,196,204,204,212,212,215,220,223,228,228,231,236,236,236,236,236,228,228,223,220,220,212,212,207,204,199,196,191,188,183,180,175,172,167,164,159,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,196,204,204,212,212,220,220,228,228,231,236,236,239,239,236,236,236,228,228,223,220,215,212,207,204,199,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,215,220,220,228,228,236,236,239,244,244,244,239,236,231,228,223,220,220,212,212,204,204,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,215,220,223,228,231,236,239,244,244,247,244,244,236,236,228,228,220,220,212,212,204,204,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,215,220,223,228,231,236,239,244,247,252,244,244,236,236,228,228,220,220,212,212,204,204,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,215,220,223,228,231,236,236,244,244,244,244,239,236,236,228,228,220,220,212,212,204,204,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,212,220,220,228,228,236,236,239,244,244,239,236,236,231,228,223,220,215,212,207,204,204,196,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,151,156,159,164,167,172,175,180,180,188,188,196,196,204,204,212,212,220,220,223,228,228,236,236,236,236,236,236,231,228,228,220,220,215,212,207,204,199,196,191,188,183,180,175,172,172,164,164,156,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,140,148,148,156,156,164,164,172,172,180,180,188,188,196,196,199,204,207,212,215,220,220,228,228,228,231,236,236,236,231,228,228,223,220,220,212,212,204,204,199,196,191,188,183,180,175,172,167,164,159,156,151,148,143,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,95,100,100,108,108,116,116,124,124,132,132,140,140,148,148,156,156,164,164,172,172,180,180,183,188,191,196,199,204,204,212,212,215,220,220,223,228,228,228,228,228,228,228,223,220,220,212,212,207,204,204,196,196,188,188,180,180,175,172,167,164,159,156,151,148,143,140,135,132,127,124,119,116,111,108,103,100,100,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,60,68,68,76,76,84,84,92,92,100,100,108,108,116,116,124,124,132,132,140,140,148,148,151,156,159,164,167,172,175,180,183,188,188,196,196,204,204,207,212,212,215,220,220,223,223,228,228,228,223,220,220,220,215,212,212,204,204,199,196,191,188,188,180,180,172,172,164,164,156,156,151,148,143,140,135,132,127,124,119,116,111,108,103,100,95,92,87,84,79,76,71,68,63,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,44,44,52,52,60,60,68,68,76,76,84,84,92,92,100,100,108,108,116,116,124,124,127,132,135,140,143,148,151,156,159,164,164,172,172,180,180,188,188,191,196,196,204,204,207,212,212,215,220,220,220,220,220,220,220,220,220,212,212,212,204,204,199,196,196,188,188,183,180,175,172,167,164,164,156,156,148,148,140,140,132,132,124,124,119,116,111,108,103,100,95,92,87,84,79,76,71,68,63,60,55,52,47,44,39,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,52,52,60,60,68,68,76,76,84,84,92,92,95,100,103,108,111,116,119,124,127,132,135,140,143,148,148,156,156,164,164,172,172,175,180,183,188,188,196,196,199,204,204,207,212,212,212,215,220,220,220,220,215,215,212,212,212,204,204,204,196,196,191,188,188,180,180,172,172,167,164,159,156,151,148,143,140,140,132,132,124,124,116,116,108,108,100,100,92,92,87,84,79,76,71,68,63,60,55,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,52,52,60,60,63,68,71,76,79,84,87,92,95,100,103,108,111,116,119,124,124,132,132,140,140,148,148,151,156,159,164,167,172,172,180,180,183,188,191,196,196,199,204,204,207,212,212,212,212,212,212,212,212,212,212,207,204,204,204,196,196,191,188,188,180,180,175,172,172,164,164,156,156,151,148,143,140,135,132,127,124,119,116,116,108,108,100,100,92,92,84,84,76,76,68,68,60,60,55,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,39,44,47,52,55,60,63,68,71,76,79,84,87,92,92,100,100,108,108,116,116,124,124,132,132,135,140,143,148,151,156,156,164,164,167,172,175,180,180,188,188,191,196,196,199,204,204,204,207,207,212,212,212,212,207,207,204,204,204,199,196,196,191,188,188,183,180,180,172,172,167,164,159,156,156,148,148,140,140,132,132,127,124,119,116,111,108,103,100,95,92,92,84,84,76,76,68,68,60,60,52,52,44,44,36,36,28,28,23,20,15,12,7,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,55,60,63,68,68,76,76,84,84,92,92,100,100,108,108,111,116,119,124,127,132,135,140,140,148,148,156,156,159,164,164,172,172,175,180,180,188,188,191,196,196,196,199,204,204,204,204,204,204,204,204,204,204,204,199,196,196,191,188,188,183,180,180,175,172,167,164,164,156,156,151,148,143,140,135,132,132,124,124,116,116,108,108,103,100,95,92,87,84,79,76,71,68,68,60,60,52,52,44,44,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,36,44,44,52,52,60,60,68,68,76,76,84,84,87,92,95,100,103,108,111,116,116,124,124,132,132,140,140,143,148,151,156,156,164,164,167,172,172,180,180,180,188,188,188,191,196,196,196,199,199,204,204,204,204,204,199,199,196,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,151,148,148,140,140,135,132,127,124,119,116,116,108,108,100,100,92,92,84,84,79,76,71,68,63,60,55,52,47,44,39,36,36,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,44,44,52,52,60,60,63,68,71,76,79,84,87,92,95,100,100,108,108,116,116,119,124,127,132,135,140,140,148,148,151,156,159,164,164,167,172,172,180,180,180,188,188,188,191,196,196,196,196,196,196,196,196,196,196,196,196,191,188,188,188,183,180,180,175,172,172,167,164,159,156,156,148,148,143,140,140,132,132,124,124,119,116,111,108,103,100,95,92,92,84,84,76,76,68,68,60,60,55,52,47,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,39,44,47,52,55,60,63,68,71,76,76,84,84,92,92,100,100,103,108,111,116,119,124,124,132,132,135,140,143,148,148,156,156,159,164,164,167,172,172,180,180,180,183,188,188,188,188,191,196,196,196,196,196,196,191,191,188,188,188,188,180,180,180,175,172,172,167,164,164,156,156,151,148,148,140,140,135,132,127,124,124,116,116,108,108,100,100,95,92,87,84,79,76,76,68,68,60,60,52,52,44,44,36,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,31,36,39,44,47,52,52,60,60,68,68,76,76,84,84,87,92,95,100,103,108,108,116,116,119,124,127,132,132,140,140,143,148,151,156,156,159,164,164,167,172,172,175,180,180,180,183,188,188,188,188,188,188,188,188,188,188,188,188,188,183,180,180,180,175,172,172,167,164,164,156,156,151,148,148,143,140,135,132,132,124,124,119,116,111,108,103,100,100,92,92,84,84,79,76,71,68,63,60,55,52,52,44,44,36,36,28,28,20,20,12,12,7,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,28,36,36,44,44,52,52,60,60,63,68,71,76,79,84,84,92,92,100,100,103,108,111,116,119,124,124,132,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,175,180,180,180,180,183,183,188,188,188,188,188,188,183,183,180,180,180,180,175,172,172,172,167,164,164,159,156,156,148,148,143,140,140,132,132,127,124,119,116,116,108,108,103,100,95,92,87,84,84,76,76,68,68,60,60,55,52,47,44,39,36,31,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,36,36,39,44,47,52,55,60,63,68,68,76,76,84,84,87,92,95,100,103,108,108,116,116,119,124,127,132,132,135,140,143,148,148,151,156,156,159,164,164,167,172,172,172,175,175,180,180,180,180,180,180,180,180,180,180,180,180,180,175,172,172,172,167,164,164,164,156,156,156,148,148,143,140,140,135,132,127,124,124,116,116,111,108,103,100,100,92,92,87,84,79,76,71,68,68,60,60,52,52,44,44,39,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,39,44,47,52,52,60,60,68,68,71,76,79,84,87,92,92,100,100,103,108,111,116,116,124,124,127,132,132,140,140,143,148,148,151,156,156,159,164,164,164,167,172,172,172,172,175,175,180,180,180,180,180,180,175,175,175,172,172,172,172,167,164,164,159,156,156,156,148,148,143,140,140,135,132,132,124,124,119,116,116,108,108,100,100,95,92,87,84,84,76,76,68,68,63,60,55,52,47,44,44,36,36,28,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,28,36,36,44,44,52,52,55,60,63,68,71,76,76,84,84,87,92,95,100,100,108,108,111,116,119,124,124,127,132,132,140,140,143,148,148,151,156,156,156,159,164,164,164,167,172,172,172,172,172,172,172,172,172,172,172,172,172,172,167,167,164,164,164,159,156,156,151,148,148,143,140,140,135,132,132,127,124,119,116,116,111,108,103,100,100,92,92,87,84,79,76,71,68,68,60,60,52,52,47,44,39,36,31,28,28,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,28,28,31,36,39,44,47,52,52,60,60,68,68,71,76,79,84,84,92,92,100,100,103,108,108,116,116,119,124,124,132,132,135,140,140,143,148,148,148,156,156,156,159,164,164,164,164,164,167,167,172,172,172,172,172,172,172,167,167,164,164,164,164,159,156,156,156,151,148,148,143,140,140,135,132,132,127,124,124,116,116,111,108,108,100,100,95,92,87,84,84,76,76,71,68,63,60,55,52,52,44,44,36,36,31,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,31,36,36,44,44,52,52,55,60,63,68,71,76,76,84,84,87,92,95,100,100,103,108,111,116,116,119,124,124,132,132,135,140,140,140,148,148,148,151,156,156,156,159,159,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,159,156,156,156,151,148,148,148,143,140,140,135,132,132,127,124,124,119,116,111,108,108,103,100,95,92,92,84,84,79,76,71,68,68,60,60,55,52,47,44,39,36,36,28,28,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,39,44,47,52,55,60,60,68,68,71,76,79,84,84,92,92,95,100,100,108,108,111,116,116,119,124,124,132,132,132,140,140,140,143,148,148,148,151,156,156,156,156,159,159,159,164,164,164,164,164,164,164,159,159,156,156,156,156,156,151,148,148,148,143,140,140,135,132,132,127,124,124,119,116,116,108,108,103,100,100,92,92,87,84,84,76,76,68,68,63,60,55,52,52,44,44,36,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,39,44,44,52,52,55,60,63,68,68,76,76,79,84,87,92,92,100,100,103,108,108,111,116,116,119,124,124,132,132,132,135,140,140,143,143,148,148,148,151,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,151,151,148,148,148,143,140,140,140,135,132,132,127,124,124,119,116,116,108,108,103,100,100,95,92,87,84,84,79,76,71,68,68,60,60,55,52,47,44,39,36,36,28,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,36,36,39,44,47,52,52,60,60,68,68,71,76,76,84,84,87,92,92,100,100,103,108,108,111,116,116,119,124,124,127,132,132,135,140,140,140,143,143,148,148,148,148,151,151,156,156,156,156,156,156,156,156,151,151,151,148,148,148,148,143,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,100,100,95,92,92,84,84,79,76,76,68,68,63,60,55,52,52,44,44,39,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,36,44,44,52,52,55,60,60,68,68,71,76,79,84,84,87,92,95,100,100,103,108,108,111,116,116,119,124,124,127,132,132,132,135,140,140,140,140,143,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,143,143,140,140,140,140,135,132,132,127,124,124,124,119,116,116,111,108,108,100,100,95,92,92,87,84,84,76,76,71,68,63,60,60,52,52,47,44,39,36,36,28,28,23,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,23,28,28,36,36,39,44,47,52,52,60,60,63,68,68,76,76,79,84,84,92,92,95,100,100,103,108,108,111,116,116,119,124,124,124,127,132,132,132,135,140,140,140,140,140,143,143,148,148,148,148,148,148,148,148,143,143,143,140,140,140,140,135,135,132,132,132,127,124,124,119,116,116,116,108,108,108,100,100,95,92,92,87,84,84,76,76,71,68,68,60,60,55,52,52,44,44,36,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,31,36,36,44,44,47,52,55,60,60,68,68,71,76,76,79,84,84,92,92,95,100,100,103,108,108,111,116,116,116,124,124,124,127,132,132,132,132,135,135,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,135,132,132,132,132,127,124,124,124,119,116,116,111,108,108,108,100,100,100,92,92,87,84,84,79,76,76,68,68,63,60,55,52,52,44,44,39,36,36,28,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,31,36,39,44,44,52,52,55,60,60,68,68,71,76,76,84,84,87,92,92,95,100,100,103,108,108,108,116,116,116,119,124,124,124,127,127,132,132,132,132,135,135,135,140,140,140,140,140,140,140,140,140,135,135,132,132,132,132,132,127,124,124,124,119,116,116,116,111,108,108,103,100,100,95,92,92,87,84,84,79,76,76,68,68,63,60,60,52,52,47,44,44,36,36,31,28,23,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,23,28,28,36,36,39,44,47,52,52,60,60,63,68,68,71,76,76,84,84,87,92,92,95,100,100,100,108,108,108,111,116,116,116,119,124,124,124,124,127,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,127,127,124,124,124,124,119,116,116,116,111,108,108,103,100,100,95,92,92,87,84,84,79,76,76,71,68,68,60,60,55,52,52,44,44,39,36,31,28,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,31,36,36,44,44,47,52,52,60,60,63,68,68,71,76,76,84,84,87,92,92,92,100,100,100,103,108,108,108,111,116,116,116,119,124,124,124,124,124,127,127,127,132,132,132,132,132,132,132,132,132,127,127,124,124,124,124,124,119,119,116,116,116,111,108,108,108,103,100,100,95,92,92,87,84,84,79,76,76,71,68,68,60,60,55,52,52,44,44,39,36,36,28,28,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,36,36,39,44,44,52,52,55,60,60,63,68,68,76,76,76,84,84,84,92,92,92,95,100,100,103,108,108,108,111,111,116,116,116,119,119,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,119,119,116,116,116,116,111,108,108,108,103,100,100,100,95,92,92,87,84,84,79,76,76,71,68,68,60,60,55,52,52,47,44,44,36,36,31,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,36,36,39,44,44,52,52,55,60,60,63,68,68,76,76,76,84,84,84,87,92,92,95,100,100,100,103,108,108,108,111,111,116,116,116,116,116,119,119,119,124,124,124,124,124,124,124,124,124,119,119,119,116,116,116,116,116,111,108,108,108,103,100,100,100,95,92,92,92,87,84,84,79,76,76,71,68,68,63,60,60,52,52,47,44,44,36,36,31,28,28,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,20,28,28,31,36,36,44,44,47,52,52,55,60,60,63,68,68,71,76,76,79,84,84,87,92,92,92,95,100,100,100,103,108,108,108,108,111,111,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,111,108,108,108,108,103,103,100,100,100,95,92,92,87,84,84,84,79,76,76,71,68,68,63,60,60,52,52,47,44,44,39,36,36,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,28,28,31,36,36,44,44,47,52,52,55,60,60,63,68,68,71,76,76,79,84,84,84,87,92,92,95,95,100,100,100,103,108,108,108,108,108,108,111,111,116,116,116,116,116,116,116,116,116,116,111,111,111,108,108,108,108,108,103,100,100,100,100,95,92,92,92,87,84,84,84,76,76,76,71,68,68,63,60,60,52,52,47,44,44,39,36,36,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,31,36,36,44,44,47,52,52,55,60,60,63,68,68,71,76,76,79,84,84,84,87,92,92,92,95,95,100,100,100,100,103,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,103,103,100,100,100,100,95,92,92,92,87,84,84,84,79,76,76,76,68,68,68,60,60,60,52,52,52,44,44,39,36,36,31,28,28,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,20,20,23,28,28,36,36,39,44,44,47,52,52,55,60,60,63,68,68,71,76,76,76,79,84,84,84,87,92,92,92,92,95,100,100,100,100,100,103,103,103,108,108,108,108,108,108,108,108,108,108,103,103,103,100,100,100,100,100,95,95,92,92,92,87,87,84,84,84,79,76,76,71,68,68,68,60,60,60,52,52,52,44,44,39,36,36,31,28,28,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,36,36,39,44,44,47,52,52,55,60,60,63,68,68,68,71,76,76,76,79,84,84,84,87,92,92,92,92,95,95,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,95,95,92,92,92,92,87,87,84,84,84,79,76,76,76,71,68,68,63,60,60,55,52,52,47,44,44,39,36,36,31,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,23,28,28,36,36,39,44,44,47,52,52,55,60,60,60,63,68,68,71,76,76,76,79,79,84,84,84,87,87,92,92,92,92,92,95,95,95,100,100,100,100,100,100,100,100,100,100,100,95,95,92,92,92,92,92,92,87,84,84,84,84,79,76,76,76,71,68,68,68,63,60,60,55,52,52,47,44,44,39,36,36,31,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,28,28,28,36,36,39,44,44,44,52,52,52,55,60,60,63,68,68,68,71,76,76,76,79,79,84,84,84,84,87,87,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,87,84,84,84,84,84,79,76,76,76,71,68,68,68,63,60,60,60,55,52,52,47,44,44,39,36,36,31,28,28,23,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,28,28,28,36,36,36,44,44,44,47,52,52,55,60,60,60,63,68,68,68,71,76,76,76,76,79,84,84,84,84,84,84,87,87,87,92,92,92,92,92,92,92,92,92,92,92,87,87,87,84,84,84,84,84,79,79,76,76,76,71,71,68,68,68,63,60,60,55,52,52,52,47,44,44,39,36,36,31,28,28,23,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,28,28,28,36,36,36,39,44,44,47,52,52,52,55,60,60,60,63,68,68,68,71,76,76,76,76,76,79,79,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,79,79,76,76,76,76,71,68,68,68,68,63,60,60,60,55,52,52,52,44,44,44,39,36,36,31,28,28,23,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,31,36,36,39,44,44,44,52,52,52,55,60,60,60,63,63,68,68,68,71,71,76,76,76,76,76,76,79,79,79,84,84,84,84,84,84,84,84,84,84,84,79,79,79,76,76,76,76,76,76,71,68,68,68,68,63,60,60,60,55,52,52,52,47,44,44,44,36,36,36,31,28,28,23,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,31,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,63,68,68,68,68,71,71,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,71,71,68,68,68,68,68,63,60,60,60,55,55,52,52,52,44,44,44,39,36,36,36,28,28,28,23,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,31,36,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,63,63,68,68,68,68,68,71,71,71,76,76,76,76,76,76,76,76,76,76,76,76,71,71,71,68,68,68,68,68,68,63,60,60,60,60,55,55,52,52,52,47,44,44,44,39,36,36,31,28,28,28,20,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,28,31,36,36,39,44,44,44,47,47,52,52,52,55,60,60,60,60,60,63,63,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,63,60,60,60,60,60,55,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,23,20,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,20,23,28,28,31,36,36,36,39,44,44,44,47,47,52,52,52,52,55,60,60,60,60,60,60,63,63,63,68,68,68,68,68,68,68,68,68,68,68,68,63,63,63,60,60,60,60,60,60,55,55,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,52,55,55,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,55,55,52,52,52,52,47,47,44,44,44,39,39,36,36,36,31,28,28,23,20,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,47,52,52,52,52,52,52,55,55,55,60,60,60,60,60,60,60,60,60,60,60,60,60,55,55,55,52,52,52,52,52,52,47,44,44,44,44,39,36,36,36,36,31,28,28,28,23,20,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,20,20,20,23,28,28,28,31,31,36,36,36,39,39,44,44,44,44,47,47,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,47,47,44,44,44,44,44,39,36,36,36,36,31,28,28,28,23,20,20,20,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,28,28,31,36,36,36,36,39,39,44,44,44,44,44,44,47,47,47,52,52,52,52,52,52,52,52,52,52,52,52,52,47,47,47,44,44,44,44,44,44,39,39,36,36,36,36,31,28,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,28,28,31,36,36,36,36,36,39,39,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,39,39,36,36,36,36,36,31,31,28,28,28,23,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,28,28,28,31,36,36,36,36,36,36,39,39,39,39,44,44,44,44,44,44,44,44,44,44,44,44,44,39,39,39,36,36,36,36,36,36,31,31,28,28,28,28,23,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,23,28,28,28,28,28,31,31,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,31,31,28,28,28,28,28,23,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,20,23,23,28,28,28,28,28,28,31,31,31,36,36,36,36,36,36,36,36,36,36,36,36,36,36,31,31,31,28,28,28,28,28,28,28,23,20,20,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,15,20,20,20,20,20,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,20,20,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,20,20,20,23,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,23,23,20,20,20,20,20,20,15,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,12,12,15,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,15,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t FLARE_TEXTURE_1[16384] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,12,15,15,15,15,15,15,15,15,15,15,15,15,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,15,15,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,15,15,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,12,15,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,15,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,15,15,20,20,20,20,20,20,20,20,20,20,20,23,23,23,23,23,20,20,20,20,20,20,20,20,20,20,20,15,15,12,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,15,15,20,20,20,20,20,20,20,23,23,23,23,28,28,28,28,28,28,28,28,28,23,23,23,23,20,20,20,20,20,20,20,15,15,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,15,15,20,20,20,20,20,20,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,23,20,20,20,20,20,20,15,15,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,15,20,20,20,20,20,23,23,28,28,28,28,28,28,28,28,31,31,31,31,31,31,31,31,28,28,28,28,28,28,28,28,23,23,20,20,20,20,20,15,15,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,15,20,20,20,20,23,23,28,28,28,28,28,28,31,31,36,36,36,36,36,36,36,36,36,36,36,36,31,31,31,28,28,28,28,28,28,23,20,20,20,20,20,15,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,20,20,20,20,20,23,28,28,28,28,28,31,31,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,31,31,28,28,28,28,23,23,20,20,20,20,15,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,20,23,28,28,28,28,28,31,36,36,36,36,36,39,39,39,44,44,44,44,44,44,44,44,44,39,39,36,36,36,36,36,36,31,28,28,28,28,23,20,20,20,20,20,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,20,23,28,28,28,28,31,36,36,36,36,39,39,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,39,36,36,36,36,36,31,28,28,28,28,23,20,20,20,20,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,23,23,28,28,28,31,36,36,36,36,39,44,44,44,44,44,47,47,52,52,52,52,52,52,52,52,47,47,47,44,44,44,44,39,39,36,36,36,31,28,28,28,28,23,20,20,20,20,15,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,15,20,20,20,23,28,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,47,47,44,44,44,39,39,36,36,36,31,28,28,28,23,20,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,15,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,55,55,60,60,60,60,60,60,60,60,60,55,55,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,23,20,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,60,63,63,63,63,63,63,60,60,60,60,60,55,55,52,52,52,47,44,44,44,39,36,36,31,28,28,28,23,20,20,20,15,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,23,28,28,28,31,36,36,36,39,44,44,47,52,52,52,55,60,60,60,63,68,68,68,68,68,68,68,68,68,68,68,68,63,60,60,60,60,55,52,52,47,44,44,44,39,36,36,31,28,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,15,20,20,20,23,28,28,31,36,36,36,44,44,44,47,52,52,55,60,60,60,63,68,68,68,71,71,76,76,76,76,76,76,71,71,68,68,68,68,63,60,60,60,52,52,52,47,44,44,39,36,36,31,28,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,23,28,28,31,36,36,36,44,44,44,52,52,52,60,60,60,63,68,68,71,76,76,76,76,76,79,79,79,79,76,76,76,76,76,71,68,68,68,63,60,60,55,52,52,47,44,44,39,36,36,31,28,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,28,36,36,36,44,44,44,52,52,55,60,60,63,68,68,71,76,76,76,79,84,84,84,84,84,84,84,84,84,84,84,79,76,76,76,68,68,68,60,60,55,52,52,47,44,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,28,28,28,31,36,36,39,44,44,52,52,55,60,60,68,68,68,76,76,79,84,84,84,87,87,92,92,92,92,92,92,92,87,84,84,84,79,76,76,71,68,68,63,60,60,52,52,47,44,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,31,36,36,39,44,44,52,52,55,60,60,68,68,71,76,76,84,84,84,92,92,92,95,95,100,100,100,100,95,95,92,92,92,87,84,84,79,76,76,68,68,63,60,60,52,52,47,44,44,36,36,36,28,28,28,23,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,23,28,28,28,36,36,39,44,44,47,52,55,60,60,68,68,71,76,79,84,84,92,92,95,100,100,100,100,103,103,103,103,103,100,100,100,95,92,92,87,84,84,76,76,71,68,63,60,60,52,52,47,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,47,52,52,60,60,68,68,76,76,79,84,87,92,92,100,100,103,108,108,108,108,111,111,108,108,108,108,103,100,100,95,92,92,84,84,76,76,71,68,63,60,55,52,52,44,44,39,36,36,28,28,28,23,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,28,36,36,39,44,44,52,52,60,60,68,68,71,76,84,84,87,92,95,100,103,108,108,111,116,116,116,116,116,116,116,116,116,111,108,108,100,100,92,92,87,84,76,76,68,68,63,60,55,52,47,44,44,36,36,31,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,47,52,55,60,63,68,71,76,79,84,92,92,100,100,108,108,116,116,119,124,124,124,124,124,124,124,124,119,116,116,111,108,103,100,95,92,87,84,76,76,68,68,60,60,52,52,44,44,39,36,36,28,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,28,28,28,36,36,39,44,44,52,52,60,60,68,68,76,79,84,87,92,100,100,108,111,116,119,124,124,127,132,132,132,132,132,132,132,127,124,124,116,116,108,103,100,95,92,84,84,76,76,68,63,60,55,52,47,44,44,36,36,31,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,36,44,44,47,52,55,60,63,68,76,76,84,87,92,100,100,108,111,116,124,124,132,132,135,140,140,140,140,140,140,140,132,132,127,124,119,116,108,108,100,95,92,84,79,76,71,68,60,60,52,52,44,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,31,36,39,44,44,52,52,60,60,68,71,76,84,84,92,95,100,108,111,116,124,127,132,135,140,143,148,148,148,148,148,148,148,140,140,132,132,124,119,116,108,103,100,92,87,84,76,76,68,63,60,55,52,47,44,44,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,28,36,36,39,44,47,52,55,60,63,68,76,79,84,92,92,100,108,111,116,124,127,132,140,143,148,151,156,156,156,156,156,156,156,148,148,140,135,132,124,119,116,108,103,100,92,84,84,76,71,68,60,60,52,52,44,44,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,60,68,71,76,84,84,92,100,103,108,116,124,127,132,140,143,148,156,159,164,164,167,167,164,164,164,156,151,148,140,135,132,124,116,111,108,100,95,92,84,76,76,68,63,60,55,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,39,44,44,52,52,60,63,68,76,76,84,92,95,100,108,116,119,124,132,140,143,151,156,164,167,172,172,175,175,175,172,172,164,159,156,148,140,135,127,124,116,108,103,100,92,84,84,76,68,68,60,55,52,47,44,39,36,36,31,28,28,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,36,36,39,44,47,52,55,60,68,68,76,79,84,92,100,103,108,116,124,132,135,143,148,156,164,172,175,180,183,188,188,183,180,180,172,164,159,156,148,140,132,124,119,116,108,100,92,87,84,76,71,68,60,60,52,52,44,44,36,36,31,28,28,23,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,36,36,39,44,47,52,55,60,68,71,76,84,87,92,100,108,111,119,124,132,140,148,156,164,172,175,180,188,191,196,196,196,188,188,180,172,164,156,151,143,135,132,124,116,108,100,95,92,84,79,76,68,63,60,52,52,44,44,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,31,36,36,44,44,52,52,60,60,68,71,76,84,87,95,100,108,116,124,127,135,143,151,159,167,175,180,188,196,204,204,204,204,199,196,188,180,172,164,156,148,140,132,124,116,111,103,100,92,84,79,76,68,63,60,55,52,47,44,39,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,31,36,36,44,44,52,52,60,63,68,76,76,84,92,95,100,108,116,124,132,140,148,156,164,172,180,188,196,204,212,215,220,212,207,199,191,183,175,167,156,148,140,132,127,119,111,108,100,92,87,84,76,68,68,60,55,52,47,44,39,36,36,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,79,84,92,100,103,108,116,124,132,140,148,156,164,172,183,191,204,212,220,228,228,223,215,204,196,188,180,172,164,151,143,135,127,124,116,108,100,92,87,84,76,71,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,79,84,92,100,103,111,116,124,132,140,148,156,167,175,188,196,204,215,228,236,236,231,220,212,199,188,180,172,164,156,148,140,132,124,116,108,100,92,87,84,76,71,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,79,84,92,100,103,111,116,124,132,140,148,156,167,175,188,196,204,220,228,236,244,236,220,212,204,191,180,172,164,156,148,140,132,124,116,108,100,92,87,84,76,71,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,79,84,92,100,103,108,116,124,132,140,148,156,164,175,183,196,204,212,223,231,236,228,220,212,199,188,180,172,164,156,148,135,132,124,116,108,100,92,87,84,76,71,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,76,84,92,95,103,108,116,124,132,140,148,156,164,172,180,188,199,207,215,220,220,220,212,204,196,188,180,167,159,151,143,135,127,119,116,108,100,92,87,84,76,68,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,31,36,36,44,44,52,52,60,60,68,71,76,84,92,95,100,108,116,124,132,140,148,156,164,172,180,188,196,199,204,212,212,212,204,196,188,180,172,164,156,148,140,132,124,116,111,108,100,92,84,79,76,68,68,60,55,52,47,44,39,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,28,36,36,44,44,47,52,60,60,68,71,76,84,87,92,100,108,116,119,127,132,140,148,156,164,172,180,188,191,196,199,204,199,196,188,183,175,167,164,156,148,140,132,124,116,108,103,100,92,84,79,76,68,63,60,55,52,44,44,39,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,36,36,39,44,47,52,55,60,68,68,76,84,84,92,100,103,111,116,124,132,140,148,151,159,164,172,180,183,188,188,191,188,188,180,175,172,164,156,148,140,132,127,124,116,108,100,95,92,84,76,76,68,63,60,52,52,44,44,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,20,20,20,23,28,28,31,36,39,44,47,52,55,60,63,68,76,79,84,92,95,100,108,116,124,127,132,140,148,156,159,164,172,175,180,180,180,180,180,172,167,164,156,148,143,140,132,124,116,111,108,100,92,87,84,76,71,68,60,60,52,52,44,44,36,36,31,28,28,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,36,44,44,52,52,60,60,68,71,76,84,87,92,100,108,111,116,124,132,135,140,148,156,156,164,167,172,172,172,172,167,164,164,156,148,148,140,132,124,119,116,108,100,95,92,84,79,76,68,68,60,55,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,36,44,44,47,52,55,60,68,68,76,79,84,92,95,100,108,116,119,124,132,135,140,148,151,156,156,164,164,164,164,159,156,156,148,143,140,132,127,124,116,108,103,100,92,87,84,76,71,68,63,60,52,52,44,44,39,36,31,28,28,23,20,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,36,36,39,44,47,52,55,60,63,68,76,76,84,87,92,100,103,108,116,119,124,132,135,140,143,148,148,151,156,156,156,151,148,148,140,140,132,127,124,116,111,108,100,95,92,84,79,76,68,68,60,60,52,52,44,44,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,52,52,60,60,68,68,76,79,84,92,92,100,103,108,116,119,124,127,132,135,140,140,143,148,148,148,143,140,140,132,132,124,124,116,111,108,100,100,92,87,84,76,71,68,63,60,55,52,47,44,39,36,36,28,28,28,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,31,36,36,39,44,47,52,55,60,63,68,71,76,84,84,92,95,100,108,108,116,116,124,124,132,132,132,135,140,140,135,135,132,132,127,124,119,116,111,108,100,100,92,87,84,79,76,68,68,60,60,52,52,44,44,39,36,31,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,36,36,39,44,44,52,52,60,60,68,68,76,76,84,87,92,95,100,103,108,111,116,119,124,124,127,127,132,132,132,127,124,124,124,116,116,108,108,100,100,92,92,84,79,76,71,68,63,60,55,52,47,44,39,36,36,31,28,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,31,36,36,39,44,47,52,52,60,60,68,68,76,76,84,87,92,95,100,103,108,108,116,116,116,119,124,124,124,124,119,116,116,116,111,108,103,100,100,92,92,84,79,76,71,68,63,60,55,52,52,44,44,39,36,36,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,28,28,28,36,36,39,44,44,47,52,55,60,63,68,71,76,76,84,84,92,92,100,100,103,108,108,111,111,116,116,116,116,116,111,108,108,108,100,100,95,92,87,84,79,76,76,68,68,60,60,52,52,47,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,31,36,36,39,44,44,52,52,55,60,63,68,71,76,76,84,84,87,92,95,100,100,100,103,108,108,108,108,108,108,108,103,100,100,95,92,92,87,84,79,76,76,68,68,60,60,55,52,47,44,44,36,36,31,28,28,28,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,47,52,52,60,60,63,68,68,76,76,79,84,84,92,92,92,95,100,100,100,100,100,100,100,100,100,95,92,92,87,84,84,79,76,71,68,68,60,60,55,52,52,44,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,23,28,28,28,36,36,39,44,44,47,52,52,60,60,63,68,68,76,76,76,84,84,84,87,92,92,92,92,92,92,92,92,92,92,92,87,84,84,79,76,76,71,68,68,60,60,55,52,52,44,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,31,36,36,39,44,44,47,52,52,60,60,63,68,68,71,76,76,76,84,84,84,84,87,87,87,87,87,87,84,84,84,84,79,76,76,71,68,68,63,60,60,55,52,52,44,44,44,36,36,31,28,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,15,20,20,20,28,28,28,31,36,36,39,44,44,47,52,52,55,60,60,63,68,68,71,76,76,76,79,79,84,84,84,84,84,84,79,79,76,76,76,71,68,68,68,63,60,60,55,52,52,44,44,44,36,36,36,28,28,28,23,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,28,31,36,36,39,44,44,47,52,52,55,60,60,60,63,68,68,68,71,76,76,76,76,76,76,76,76,76,76,76,71,68,68,68,63,60,60,55,52,52,52,44,44,44,36,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,23,28,28,28,31,36,36,39,44,44,47,52,52,52,55,60,60,60,63,68,68,68,68,68,71,71,71,71,68,68,68,68,68,68,63,60,60,60,55,52,52,47,44,44,44,36,36,36,31,28,28,23,20,20,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,15,20,20,20,23,28,28,28,31,36,36,39,44,44,44,47,52,52,52,55,60,60,60,60,63,63,68,68,68,68,68,68,68,63,63,60,60,60,60,55,52,52,52,47,44,44,39,36,36,36,31,28,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,47,52,52,52,55,55,60,60,60,60,60,60,60,60,60,60,60,60,60,55,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,20,23,28,28,28,31,36,36,36,39,44,44,44,44,47,52,52,52,52,52,55,55,55,55,55,55,55,55,55,52,52,52,52,52,47,44,44,44,39,36,36,36,31,28,28,28,28,23,20,20,20,15,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,20,23,28,28,28,31,36,36,36,36,39,44,44,44,44,47,47,52,52,52,52,52,52,52,52,52,52,52,52,52,47,44,44,44,44,39,39,36,36,36,31,28,28,28,23,23,20,20,20,15,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,15,20,20,20,20,23,28,28,28,28,31,36,36,36,36,39,44,44,44,44,44,44,47,47,47,47,47,47,47,47,44,44,44,44,44,44,39,39,36,36,36,31,31,28,28,28,23,20,20,20,20,15,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,20,20,20,20,23,23,28,28,28,28,31,36,36,36,36,36,39,39,44,44,44,44,44,44,44,44,44,44,44,44,44,39,39,36,36,36,36,36,31,28,28,28,28,23,20,20,20,20,15,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,15,20,20,20,20,23,28,28,28,28,28,31,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,36,36,36,36,36,36,31,31,28,28,28,28,23,23,20,20,20,20,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,15,20,20,20,20,23,23,28,28,28,28,28,31,31,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,31,31,28,28,28,28,28,28,23,20,20,20,20,20,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,15,20,20,20,20,20,23,23,28,28,28,28,28,28,31,31,31,31,36,36,36,36,36,36,31,31,31,31,28,28,28,28,28,28,23,23,20,20,20,20,20,15,15,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,15,15,20,20,20,20,20,23,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,20,20,20,20,20,20,15,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,15,15,20,20,20,20,20,20,23,23,23,28,28,28,28,28,28,28,28,28,28,28,28,28,28,23,23,23,20,20,20,20,20,20,20,15,15,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,15,15,20,20,20,20,20,20,20,20,23,23,23,23,23,23,23,23,23,23,23,23,20,20,20,20,20,20,20,20,20,15,15,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,15,12,12,12,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,12,12,12,15,15,15,15,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,15,15,15,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,15,15,15,15,15,15,20,20,20,20,20,15,15,15,15,15,15,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,15,15,15,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,12,12,12,12,12,12,12,12,12,12,12,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t FLARE_TEXTURE_2[16384] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,20,23,28,28,28,28,28,28,28,28,28,28,23,23,20,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,28,28,31,36,36,36,36,39,39,44,44,44,44,44,44,39,39,39,36,36,36,36,28,28,23,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,23,28,31,36,36,39,44,44,44,47,47,52,52,52,52,52,52,52,52,52,52,52,52,47,47,44,44,44,44,36,36,36,28,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,28,36,36,39,44,44,47,52,52,52,55,55,60,60,60,60,60,60,60,60,60,60,60,60,60,60,55,55,52,52,52,52,47,44,44,39,36,31,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,36,36,44,44,47,52,52,55,60,60,60,60,63,63,68,68,68,68,68,68,68,68,68,68,68,68,68,68,63,63,60,60,60,60,55,52,52,52,44,44,39,36,31,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,31,36,44,44,47,52,52,60,60,60,63,68,68,68,68,68,71,71,76,76,76,76,76,76,76,76,76,76,76,76,71,71,68,68,68,68,68,63,60,60,60,55,52,52,47,44,39,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,39,44,47,52,52,60,60,60,63,68,68,68,71,76,76,76,76,76,76,79,79,79,79,84,84,84,84,84,79,79,79,79,76,76,76,76,76,71,71,68,68,68,63,60,60,55,52,52,44,44,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,39,44,52,52,55,60,60,63,68,68,71,76,76,76,76,79,79,84,84,84,84,84,84,84,84,84,87,87,87,84,84,84,84,84,84,84,84,84,79,79,76,76,76,71,68,68,68,63,60,60,52,52,47,44,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,39,44,52,52,60,60,63,68,68,71,76,76,76,79,84,84,84,84,84,87,87,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,87,87,84,84,84,84,79,76,76,76,71,68,68,68,60,60,55,52,47,44,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,31,36,44,52,52,60,60,63,68,68,76,76,76,79,84,84,84,84,87,92,92,92,92,92,92,95,95,95,100,100,100,100,100,100,100,100,95,95,95,95,92,92,92,92,92,92,87,84,84,84,79,76,76,76,71,68,68,60,60,55,52,47,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,44,47,52,60,60,63,68,71,76,76,76,84,84,84,87,92,92,92,92,92,95,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,95,95,92,92,92,92,87,84,84,84,79,76,76,71,68,68,63,60,55,52,44,39,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,39,44,52,55,60,63,68,71,76,76,79,84,84,84,87,92,92,92,95,100,100,100,100,100,100,103,103,108,108,108,108,108,108,108,108,108,108,108,108,108,108,103,103,103,100,100,100,100,100,95,92,92,92,92,87,84,84,79,76,76,71,68,68,60,60,52,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,44,52,52,60,63,68,68,76,76,79,84,84,87,92,92,92,95,100,100,100,100,103,103,108,108,108,108,108,108,108,108,111,111,111,111,111,111,111,111,111,108,108,108,108,108,108,108,108,103,100,100,100,100,95,92,92,92,87,84,84,84,76,76,71,68,68,60,55,52,44,39,36,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,39,44,52,55,60,68,68,76,76,79,84,84,87,92,92,92,95,100,100,100,103,108,108,108,108,108,111,111,111,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,111,111,108,108,108,108,108,103,100,100,100,100,95,92,92,87,84,84,79,76,76,71,68,63,60,52,52,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,23,36,44,47,52,60,63,68,71,76,76,84,84,87,92,92,92,100,100,100,103,103,108,108,108,108,111,116,116,116,116,116,116,116,116,119,119,119,119,119,119,119,119,119,119,119,116,116,116,116,116,116,116,111,111,108,108,108,108,103,100,100,100,95,92,92,87,84,84,79,76,76,68,68,60,55,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,44,52,55,60,68,68,76,76,79,84,84,92,92,92,100,100,100,103,108,108,108,108,111,116,116,116,116,116,119,119,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,119,119,116,116,116,116,116,111,111,108,108,108,103,100,100,100,95,92,92,87,84,84,76,76,71,68,63,60,52,47,39,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,44,52,60,60,68,71,76,76,84,84,87,92,92,95,100,100,103,108,108,108,111,116,116,116,116,119,119,124,124,124,124,124,124,124,124,127,127,127,127,127,127,127,127,127,127,127,124,124,124,124,124,124,124,119,119,116,116,116,116,111,108,108,108,103,100,100,100,95,92,92,84,84,79,76,76,68,63,60,52,47,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,44,52,60,63,68,71,76,79,84,84,92,92,95,100,100,103,108,108,108,111,116,116,116,116,119,124,124,124,124,124,127,127,127,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,127,127,124,124,124,124,124,119,119,116,116,116,111,111,108,108,103,100,100,100,92,92,87,84,84,76,76,68,68,60,55,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,39,44,52,60,63,68,76,76,84,84,87,92,92,100,100,100,108,108,108,111,116,116,116,119,124,124,124,124,124,127,132,132,132,132,132,132,132,132,132,135,135,135,135,135,135,135,135,135,132,132,132,132,132,132,132,132,127,127,124,124,124,124,119,116,116,116,111,108,108,108,103,100,100,95,92,92,84,84,79,76,71,68,60,55,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,39,47,52,60,63,68,76,76,84,84,92,92,95,100,100,103,108,108,111,116,116,116,119,124,124,124,124,127,132,132,132,132,132,132,135,135,135,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,135,135,135,132,132,132,132,132,127,127,124,124,124,119,116,116,116,111,108,108,108,100,100,100,92,92,87,84,79,76,71,68,60,55,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,47,52,60,68,68,76,76,84,84,92,92,95,100,100,108,108,108,111,116,116,119,124,124,124,124,127,132,132,132,132,135,135,140,140,140,140,140,140,140,140,140,140,140,143,143,140,140,140,140,140,140,140,140,140,140,140,135,135,132,132,132,132,132,127,124,124,124,119,116,116,116,111,108,108,103,100,100,95,92,87,84,84,76,71,68,60,55,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,44,52,60,68,68,76,79,84,87,92,92,100,100,103,108,108,111,116,116,116,119,124,124,124,127,132,132,132,132,135,140,140,140,140,140,140,143,143,143,148,148,148,148,148,148,148,148,148,148,148,148,143,143,143,140,140,140,140,140,140,135,135,132,132,132,132,127,124,124,124,119,116,116,111,108,108,108,100,100,95,92,92,84,84,76,71,68,60,55,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,36,44,52,60,68,68,76,79,84,87,92,92,100,100,103,108,108,111,116,116,119,124,124,124,127,132,132,132,135,135,140,140,140,140,143,143,143,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,143,143,140,140,140,140,140,135,132,132,132,132,127,124,124,124,116,116,116,111,108,108,100,100,95,92,92,84,84,76,71,68,60,55,52,44,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,60,63,68,76,79,84,87,92,95,100,100,108,108,108,116,116,116,124,124,124,127,132,132,132,135,135,140,140,140,140,143,148,148,148,148,148,148,148,151,151,151,151,151,151,156,156,156,151,151,151,151,151,148,148,148,148,148,148,148,143,143,140,140,140,140,135,132,132,132,127,124,124,124,119,116,116,111,108,108,103,100,100,92,92,84,84,76,71,68,60,55,52,39,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,63,68,76,79,84,87,92,95,100,100,108,108,111,116,116,119,124,124,124,127,132,132,132,135,140,140,140,143,143,148,148,148,148,148,151,151,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,151,151,148,148,148,148,148,148,143,140,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,103,100,100,92,92,84,84,76,71,68,60,52,47,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,44,52,60,63,68,76,76,84,87,92,95,100,100,108,108,111,116,116,119,124,124,127,132,132,132,135,140,140,140,143,143,148,148,148,148,151,151,156,156,156,156,156,156,156,156,156,159,159,159,159,159,159,159,156,156,156,156,156,156,156,156,156,151,148,148,148,148,148,143,140,140,140,140,135,132,132,127,124,124,124,116,116,116,108,108,103,100,100,92,92,84,84,76,71,68,60,52,44,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,47,55,60,68,76,76,84,87,92,95,100,100,108,108,111,116,116,119,124,124,127,132,132,135,140,140,140,140,143,148,148,148,148,151,156,156,156,156,156,156,159,159,159,164,164,164,164,164,164,164,164,164,164,164,164,159,159,159,156,156,156,156,156,156,151,151,148,148,148,148,143,140,140,140,135,132,132,132,127,124,124,119,116,116,108,108,103,100,100,92,92,84,79,76,68,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,60,68,71,76,84,84,92,92,100,100,108,108,111,116,116,124,124,124,127,132,132,135,140,140,140,143,148,148,148,148,151,156,156,156,156,156,159,159,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,159,159,156,156,156,156,156,151,148,148,148,143,143,140,140,140,132,132,132,127,124,124,119,116,116,108,108,103,100,95,92,87,84,79,76,68,63,60,52,44,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,71,76,84,84,92,92,100,100,108,108,111,116,116,124,124,124,132,132,132,135,140,140,143,148,148,148,148,151,156,156,156,156,159,159,164,164,164,164,164,164,164,167,167,167,167,167,167,167,167,167,167,167,167,164,164,164,164,164,164,164,164,159,156,156,156,156,151,151,148,148,148,143,140,140,140,135,132,132,127,124,124,119,116,116,108,108,103,100,95,92,87,84,76,76,68,60,55,47,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,52,55,63,68,76,79,84,92,92,100,100,108,108,111,116,116,124,124,124,132,132,132,140,140,140,143,148,148,148,151,156,156,156,156,159,159,164,164,164,164,164,167,167,167,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,167,167,164,164,164,164,164,164,159,156,156,156,156,151,148,148,148,143,140,140,140,135,132,132,127,124,124,119,116,116,108,108,103,100,95,92,87,84,76,71,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,60,68,76,76,84,87,92,95,100,103,108,111,116,116,124,124,124,132,132,132,140,140,140,143,148,148,148,151,156,156,156,159,159,164,164,164,164,167,167,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,167,167,164,164,164,164,164,159,156,156,156,156,151,148,148,148,143,140,140,135,132,132,127,124,124,119,116,116,108,108,100,100,92,92,84,84,76,68,63,60,52,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,44,52,60,68,71,76,84,84,92,95,100,103,108,108,116,116,119,124,124,132,132,135,140,140,140,143,148,148,151,156,156,156,156,159,164,164,164,164,167,167,172,172,172,172,172,172,175,175,175,175,175,180,180,180,180,180,175,175,175,175,172,172,172,172,172,172,172,167,164,164,164,164,164,159,156,156,156,151,148,148,148,143,140,140,135,132,132,127,124,124,119,116,111,108,108,100,100,92,92,84,79,76,68,60,55,47,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,47,55,63,68,76,79,84,92,92,100,100,108,108,116,116,119,124,124,132,132,132,140,140,140,143,148,148,151,156,156,156,159,164,164,164,164,167,167,172,172,172,172,172,175,175,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,175,175,175,172,172,172,172,172,167,164,164,164,164,159,156,156,156,151,148,148,148,143,140,140,135,132,132,127,124,124,116,116,111,108,103,100,95,92,87,84,76,71,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,76,84,87,92,100,100,108,108,111,116,119,124,124,127,132,132,140,140,140,148,148,148,151,156,156,156,159,164,164,164,167,167,172,172,172,172,175,175,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,175,175,172,172,172,172,172,167,164,164,164,164,159,156,156,156,148,148,148,143,140,140,135,132,132,127,124,124,116,116,111,108,103,100,95,92,84,84,76,68,63,55,52,39,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,39,52,60,63,68,76,84,84,92,95,100,103,108,111,116,116,124,124,127,132,132,140,140,140,143,148,148,151,156,156,156,159,164,164,164,167,172,172,172,172,175,175,180,180,180,180,180,180,183,183,183,183,188,188,188,188,188,188,188,183,183,183,180,180,180,180,180,180,180,175,172,172,172,172,167,167,164,164,164,159,156,156,156,148,148,148,143,140,140,135,132,132,124,124,119,116,116,108,108,100,100,92,92,84,79,76,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,79,84,92,92,100,100,108,108,116,116,124,124,127,132,132,135,140,140,143,148,148,151,156,156,156,159,164,164,164,167,172,172,172,175,175,180,180,180,180,180,183,183,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,183,183,183,180,180,180,180,180,175,172,172,172,172,167,164,164,164,159,156,156,156,148,148,148,143,140,140,135,132,132,124,124,119,116,111,108,103,100,95,92,87,84,76,68,63,60,52,39,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,39,52,60,63,71,76,84,87,92,95,100,108,108,111,116,119,124,124,132,132,135,140,140,143,148,148,151,156,156,156,164,164,164,167,172,172,172,172,175,180,180,180,180,180,183,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,183,183,180,180,180,180,175,175,172,172,172,167,164,164,164,159,156,156,156,148,148,148,140,140,140,132,132,127,124,124,116,116,111,108,103,100,92,92,84,79,76,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,79,84,92,92,100,103,108,111,116,116,124,124,127,132,135,140,140,143,148,148,151,156,156,156,164,164,164,167,172,172,172,175,175,180,180,180,180,183,188,188,188,188,188,188,191,191,191,191,196,196,196,196,196,196,191,191,191,191,188,188,188,188,188,188,183,183,180,180,180,180,175,172,172,172,167,164,164,164,159,156,156,151,148,148,148,140,140,135,132,132,127,124,119,116,116,108,108,100,100,92,87,84,76,71,63,60,52,39,28,0,0,0,0,0,0,0,0,0,0,0,0,28,39,52,60,63,71,76,84,87,92,100,100,108,108,116,116,119,124,127,132,132,140,140,143,148,148,151,156,156,156,159,164,164,167,172,172,172,175,180,180,180,180,183,188,188,188,188,188,191,191,191,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,191,191,188,188,188,188,188,183,180,180,180,180,175,172,172,172,167,164,164,164,159,156,156,151,148,148,143,140,140,135,132,132,124,124,119,116,111,108,103,100,92,92,84,79,76,68,60,52,44,36,15,0,0,0,0,0,0,0,0,0,0,12,31,44,52,60,68,76,79,84,92,92,100,103,108,111,116,119,124,124,132,132,135,140,140,148,148,148,156,156,156,159,164,164,167,172,172,172,175,180,180,180,180,183,188,188,188,188,191,191,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,191,191,188,188,188,188,188,183,180,180,180,175,172,172,172,167,164,164,164,159,156,156,151,148,148,143,140,140,132,132,127,124,124,116,116,108,108,100,100,92,87,84,76,68,63,55,47,36,23,0,0,0,0,0,0,0,0,0,0,23,36,47,55,63,68,76,84,87,92,100,100,108,108,116,116,124,124,127,132,132,140,140,143,148,148,151,156,156,159,164,164,167,172,172,172,175,180,180,180,183,188,188,188,188,191,191,196,196,196,196,196,196,199,199,199,199,199,204,204,199,199,199,199,199,196,196,196,196,196,196,196,191,188,188,188,188,183,180,180,180,175,172,172,172,167,164,164,164,156,156,156,151,148,148,140,140,135,132,132,124,124,119,116,111,108,103,100,92,92,84,79,76,68,60,52,44,31,12,0,0,0,0,0,0,0,0,0,28,44,52,60,68,76,76,84,92,92,100,103,108,111,116,119,124,124,132,132,135,140,140,148,148,151,156,156,159,164,164,164,172,172,172,175,180,180,180,183,188,188,188,188,191,196,196,196,196,196,199,199,199,204,204,204,204,204,204,204,204,204,204,204,204,204,199,199,196,196,196,196,196,191,191,188,188,188,183,180,180,180,175,172,172,172,167,164,164,159,156,156,151,148,148,143,140,140,135,132,127,124,124,116,116,108,108,100,95,92,87,84,76,68,63,55,44,36,20,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,84,84,92,95,100,108,108,116,116,124,124,127,132,135,140,140,143,148,148,156,156,156,164,164,164,167,172,172,175,180,180,180,183,188,188,188,188,191,196,196,196,196,199,199,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,199,199,196,196,196,196,196,191,188,188,188,183,180,180,180,175,172,172,172,167,164,164,159,156,156,151,148,148,143,140,135,132,132,124,124,119,116,111,108,103,100,92,92,84,76,71,68,60,52,39,28,0,0,0,0,0,0,0,0,23,36,47,60,63,71,76,84,87,92,100,103,108,111,116,119,124,124,132,132,135,140,143,148,148,151,156,156,159,164,164,167,172,172,175,180,180,180,183,188,188,188,191,191,196,196,196,196,199,204,204,204,204,204,204,204,207,207,207,207,207,207,207,207,207,204,204,204,204,204,204,199,199,196,196,196,196,191,188,188,188,183,180,180,180,175,172,172,172,164,164,164,159,156,156,148,148,143,140,140,135,132,127,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,44,31,12,0,0,0,0,0,0,7,28,44,52,60,68,76,79,84,92,95,100,103,108,116,116,124,124,127,132,135,140,140,143,148,148,156,156,159,164,164,167,172,172,172,175,180,180,183,188,188,188,191,196,196,196,196,199,204,204,204,204,204,204,207,207,212,212,212,212,212,212,212,212,212,212,207,207,207,204,204,204,204,204,199,196,196,196,196,191,188,188,188,183,180,180,180,175,172,172,167,164,164,159,156,156,151,148,148,143,140,135,132,132,124,124,119,116,111,108,100,100,92,87,84,76,68,63,55,47,36,20,0,0,0,0,0,0,20,36,44,52,60,68,76,84,87,92,100,100,108,108,116,116,124,124,132,132,135,140,143,148,148,151,156,156,159,164,164,167,172,172,175,180,180,180,188,188,188,191,196,196,196,196,199,204,204,204,204,207,207,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,207,207,204,204,204,204,199,199,196,196,196,191,188,188,188,183,180,180,180,172,172,172,167,164,164,159,156,156,148,148,143,140,140,135,132,127,124,119,116,111,108,103,100,92,92,84,76,71,68,60,52,39,28,0,0,0,0,0,0,23,36,47,60,63,71,76,84,92,92,100,103,108,111,116,119,124,127,132,132,140,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,180,183,188,188,188,191,196,196,196,199,204,204,204,204,207,212,212,212,212,212,212,212,212,215,215,215,215,215,212,212,212,212,212,212,212,207,207,204,204,204,204,199,196,196,196,191,188,188,188,183,180,180,175,172,172,167,164,164,159,156,156,151,148,148,143,140,135,132,132,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,44,31,12,0,0,0,0,0,28,39,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,175,180,180,183,188,188,188,191,196,196,196,199,204,204,204,204,207,212,212,212,212,212,215,215,215,220,220,220,220,220,220,215,215,215,212,212,212,212,212,212,207,204,204,204,204,199,196,196,196,191,188,188,183,180,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,140,132,132,127,124,119,116,111,108,100,100,92,87,84,76,68,63,55,44,36,20,0,0,0,0,12,31,44,52,60,68,76,84,84,92,100,100,108,108,116,119,124,124,132,132,140,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,180,188,188,188,191,196,196,196,199,204,204,204,207,207,212,212,212,212,215,215,220,220,220,220,220,220,220,220,220,220,220,220,220,215,212,212,212,212,212,207,204,204,204,199,199,196,196,191,188,188,188,183,180,180,175,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,127,124,124,116,111,108,103,100,92,92,84,76,71,68,60,47,36,23,0,0,0,0,20,36,44,55,63,68,76,84,87,92,100,103,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,204,207,207,212,212,212,215,215,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,215,215,212,212,212,212,207,204,204,204,199,196,196,196,191,188,188,188,180,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,39,28,0,0,0,0,20,36,47,60,68,71,76,84,92,92,100,103,108,116,116,124,124,132,132,135,140,143,148,148,151,156,156,164,164,167,172,172,175,180,180,180,188,188,188,191,196,196,196,204,204,204,204,207,212,212,212,215,215,220,220,220,220,220,223,223,223,223,223,223,223,223,220,220,220,220,220,220,215,212,212,212,212,207,204,204,204,199,196,196,196,191,188,188,183,180,180,175,172,172,167,164,164,159,156,156,151,148,143,140,140,132,132,127,124,119,116,111,108,100,100,92,84,84,76,68,60,52,44,31,12,0,0,0,28,39,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,140,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,204,207,212,212,212,215,220,220,220,220,220,223,223,228,228,228,228,228,228,228,228,228,223,223,220,220,220,220,215,212,212,212,212,207,204,204,204,199,196,196,191,188,188,188,180,180,180,175,172,172,164,164,164,156,156,151,148,148,140,140,135,132,127,124,119,116,111,108,103,100,92,87,84,76,68,60,52,44,36,15,0,0,0,28,44,52,60,68,76,79,84,92,100,100,108,111,116,119,124,127,132,132,140,140,143,148,151,156,156,159,164,164,172,172,172,180,180,180,188,188,188,191,196,196,199,204,204,204,207,212,212,212,215,220,220,220,220,223,223,228,228,228,228,228,228,228,228,228,228,228,228,228,223,220,220,220,220,215,212,212,212,207,204,204,204,199,196,196,196,191,188,188,183,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,108,103,100,92,92,84,76,71,63,55,47,36,20,0,0,12,31,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,207,212,212,212,215,215,220,220,220,223,228,228,228,228,228,228,228,231,231,228,228,228,228,228,228,228,223,220,220,220,220,215,212,212,212,207,204,204,204,199,196,196,191,188,188,183,180,180,180,172,172,167,164,164,159,156,156,148,148,143,140,140,132,132,124,124,116,116,108,108,100,95,92,84,79,71,68,60,52,36,23,0,0,15,36,44,55,63,68,76,84,87,92,100,103,108,111,116,124,124,127,132,135,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,204,207,212,212,212,215,220,220,220,223,228,228,228,228,228,231,231,231,236,236,236,231,231,231,228,228,228,228,223,220,220,220,220,215,212,212,212,207,204,204,199,196,196,196,188,188,188,183,180,180,175,172,172,164,164,159,156,156,151,148,143,140,140,132,132,127,124,119,116,108,108,100,95,92,84,79,76,68,60,52,39,28,0,0,20,36,47,55,63,71,76,84,92,92,100,103,108,116,116,124,124,132,132,135,140,143,148,148,156,156,159,164,164,167,172,172,180,180,180,188,188,188,191,196,196,199,204,204,204,212,212,212,215,220,220,220,223,228,228,228,228,231,231,236,236,236,236,236,236,236,236,236,231,228,228,228,228,223,220,220,220,215,212,212,212,207,204,204,204,196,196,196,191,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,148,140,140,135,132,127,124,119,116,111,108,100,100,92,84,79,76,68,60,52,44,28,0,0,20,36,47,60,68,71,76,84,92,95,100,108,108,116,116,124,124,132,132,140,140,143,148,151,156,156,159,164,164,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,207,212,212,212,215,220,220,220,223,228,228,228,231,231,236,236,236,236,236,236,236,236,236,236,236,231,228,228,228,228,223,220,220,220,215,212,212,207,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,164,159,156,151,148,148,140,140,135,132,127,124,119,116,111,108,100,100,92,87,84,76,68,60,52,44,28,7,0,23,36,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,204,207,212,212,215,220,220,220,223,228,228,228,231,231,236,236,236,236,239,239,239,239,236,236,236,236,236,231,228,228,228,223,220,220,220,215,212,212,212,207,204,204,199,196,196,191,188,188,188,180,180,180,172,172,167,164,164,159,156,156,148,148,143,140,135,132,127,124,119,116,111,108,103,100,92,87,84,76,68,60,52,44,31,12,0,28,39,52,60,68,76,79,84,92,95,100,108,108,116,119,124,127,132,132,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,204,212,212,212,215,220,220,220,223,228,228,228,231,236,236,236,236,239,244,244,244,244,239,239,236,236,236,236,231,228,228,228,223,220,220,220,212,212,212,207,204,204,199,196,196,196,188,188,188,183,180,180,172,172,172,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,108,103,100,92,87,84,76,68,63,52,44,36,12,0,28,39,52,60,68,76,79,84,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,207,212,212,212,215,220,220,223,228,228,228,231,236,236,236,236,239,244,244,244,244,244,244,244,239,236,236,236,231,228,228,228,223,220,220,220,215,212,212,207,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,148,148,143,140,140,132,132,124,124,116,116,108,103,100,92,92,84,76,68,63,55,44,36,15,0,28,44,52,60,68,76,79,84,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,159,164,164,167,172,172,175,180,180,188,188,188,196,196,196,199,204,204,207,212,212,212,220,220,220,223,228,228,228,231,236,236,236,239,244,244,244,244,244,244,244,244,239,239,236,236,236,231,228,228,223,220,220,220,215,212,212,212,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,103,100,92,92,84,76,71,63,55,44,36,20,0,28,44,52,60,68,76,84,84,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,159,164,164,167,172,172,180,180,180,188,188,188,196,196,196,199,204,204,207,212,212,215,220,220,220,223,228,228,228,231,236,236,239,244,244,244,244,247,247,244,244,244,244,239,236,236,236,231,228,228,228,223,220,220,215,212,212,212,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,103,100,95,92,84,76,71,63,55,47,36,20,0,28,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,135,140,143,148,148,156,156,159,164,164,167,172,172,180,180,180,188,188,188,196,196,196,204,204,204,207,212,212,215,220,220,220,223,228,228,231,236,236,236,239,244,244,244,247,252,252,247,244,244,244,239,236,236,236,231,228,228,228,223,220,220,215,212,212,212,207,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,143,140,140,132,132,124,124,116,116,108,108,100,95,92,84,76,71,63,55,47,36,20,0,28,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,135,140,143,148,148,156,156,159,164,164,167,172,172,180,180,180,188,188,188,196,196,196,204,204,204,207,212,212,215,220,220,220,223,228,228,231,236,236,236,239,244,244,244,247,252,252,247,244,244,244,239,236,236,236,231,228,228,228,223,220,220,215,212,212,212,207,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,143,140,140,132,132,124,124,116,116,108,108,100,95,92,84,76,71,63,55,47,36,20,0,28,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,156,156,159,164,164,167,172,172,180,180,180,188,188,188,196,196,196,199,204,204,207,212,212,215,220,220,220,223,228,228,228,236,236,236,239,244,244,244,244,247,247,247,244,244,244,239,236,236,236,231,228,228,228,223,220,220,215,212,212,212,207,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,103,100,95,92,84,76,71,63,55,47,36,20,0,28,44,52,60,68,76,84,84,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,159,164,164,167,172,172,180,180,180,188,188,188,196,196,196,199,204,204,207,212,212,215,220,220,220,223,228,228,228,231,236,236,236,239,244,244,244,244,244,244,244,244,244,239,236,236,236,231,228,228,228,220,220,220,215,212,212,212,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,103,100,92,92,84,76,71,63,55,44,36,20,0,28,39,52,60,68,76,79,84,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,159,164,164,167,172,172,175,180,180,188,188,188,191,196,196,199,204,204,207,212,212,212,215,220,220,223,228,228,228,231,236,236,236,239,244,244,244,244,244,244,244,244,239,236,236,236,231,228,228,228,223,220,220,220,215,212,212,207,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,103,100,92,92,84,76,71,63,55,44,36,20,0,28,39,52,60,68,76,79,84,92,95,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,207,212,212,212,215,220,220,220,228,228,228,231,236,236,236,236,239,239,244,244,244,244,244,239,236,236,236,236,231,228,228,228,223,220,220,220,215,212,212,207,204,204,204,196,196,196,191,188,188,183,180,180,175,172,172,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,108,103,100,92,87,84,76,68,63,55,44,36,15,0,23,39,52,60,68,76,79,84,92,95,100,108,108,116,119,124,127,132,132,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,204,207,212,212,215,220,220,220,223,228,228,228,231,236,236,236,236,239,239,239,239,239,239,236,236,236,236,231,228,228,228,228,223,220,220,215,212,212,212,207,204,204,199,196,196,196,188,188,188,180,180,180,172,172,167,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,111,108,103,100,92,87,84,76,68,60,52,44,31,12,0,23,36,47,60,68,71,79,84,92,95,100,108,108,116,116,124,124,132,132,140,140,143,148,151,156,156,159,164,164,172,172,175,180,180,183,188,188,191,196,196,196,204,204,204,207,212,212,212,220,220,220,223,228,228,228,228,231,236,236,236,236,236,236,236,236,236,236,236,236,231,231,228,228,228,223,220,220,220,215,212,212,212,207,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,135,132,127,124,119,116,111,108,103,100,92,87,84,76,68,60,52,44,31,12,0,20,36,47,55,63,71,76,84,92,95,100,103,108,116,116,124,124,132,132,140,140,143,148,148,156,156,159,164,164,172,172,172,180,180,180,188,188,188,196,196,196,199,204,204,207,212,212,212,215,220,220,220,223,228,228,228,228,231,236,236,236,236,236,236,236,236,236,236,231,231,228,228,228,223,220,220,220,220,215,212,212,207,204,204,204,199,196,196,191,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,148,140,140,135,132,127,124,119,116,111,108,100,100,92,87,84,76,68,60,52,44,28,0,0,20,36,44,55,63,68,76,84,92,92,100,103,108,116,116,124,124,132,132,135,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,191,196,196,199,204,204,204,207,212,212,212,215,220,220,220,223,228,228,228,228,231,231,236,236,236,236,236,236,231,231,228,228,228,228,228,223,220,220,220,215,212,212,212,207,204,204,199,196,196,196,191,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,148,140,140,132,132,127,124,119,116,108,108,100,95,92,84,79,76,68,60,52,44,28,0,0,12,36,44,52,60,68,76,84,87,92,100,103,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,167,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,207,212,212,212,215,220,220,220,220,223,228,228,228,228,228,231,231,231,231,231,231,228,228,228,228,228,228,223,220,220,220,215,212,212,212,207,204,204,204,199,196,196,191,188,188,188,180,180,180,172,172,172,164,164,159,156,156,151,148,143,140,140,132,132,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,39,28,0,0,7,28,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,164,172,172,175,180,180,183,188,188,188,191,196,196,199,204,204,204,207,212,212,212,215,220,220,220,220,223,228,228,228,228,228,228,228,228,228,228,228,228,228,228,223,223,220,220,220,220,215,212,212,212,207,204,204,204,196,196,196,191,188,188,183,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,135,132,132,124,124,116,116,108,103,100,95,92,84,76,71,68,60,47,36,20,0,0,0,28,44,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,140,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,191,196,196,196,199,204,204,207,212,212,212,212,215,220,220,220,220,223,223,228,228,228,228,228,228,228,228,228,228,228,223,220,220,220,220,220,215,212,212,212,207,204,204,204,199,196,196,196,188,188,188,183,180,180,175,172,172,167,164,164,156,156,151,148,148,140,140,135,132,127,124,119,116,111,108,103,100,92,87,84,76,68,63,55,44,36,20,0,0,0,23,36,52,60,68,71,79,84,92,95,100,108,108,116,116,124,124,132,132,135,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,188,196,196,196,199,204,204,204,207,212,212,212,212,215,220,220,220,220,220,223,223,223,228,228,228,228,228,223,223,223,220,220,220,220,220,215,212,212,212,207,204,204,204,199,196,196,196,191,188,188,183,180,180,180,172,172,172,164,164,159,156,156,151,148,148,140,140,132,132,127,124,119,116,111,108,100,100,92,87,84,76,68,60,52,44,31,12,0,0,0,20,36,47,55,63,71,76,84,92,92,100,103,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,164,164,164,172,172,172,180,180,180,183,188,188,191,196,196,196,199,204,204,204,207,212,212,212,212,215,220,220,220,220,220,220,220,223,223,223,223,220,220,220,220,220,220,220,215,215,212,212,212,207,207,204,204,204,199,196,196,191,188,188,188,183,180,180,175,172,172,167,164,164,159,156,156,148,148,143,140,140,132,132,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,44,28,0,0,0,0,15,36,44,52,60,68,76,84,87,92,100,100,108,111,116,119,124,127,132,132,140,140,143,148,148,156,156,159,164,164,167,172,172,175,180,180,183,188,188,188,191,196,196,196,199,204,204,204,207,212,212,212,212,215,215,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,215,212,212,212,212,207,207,204,204,204,199,196,196,196,191,188,188,183,180,180,180,172,172,172,164,164,164,156,156,151,148,148,143,140,135,132,132,124,124,116,116,108,103,100,95,92,84,79,71,68,60,52,39,28,0,0,0,0,0,28,44,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,135,140,143,148,148,151,156,156,164,164,164,172,172,172,180,180,180,183,188,188,191,196,196,196,199,204,204,204,204,207,212,212,212,212,212,215,215,220,220,220,220,220,220,220,220,220,220,215,215,215,212,212,212,212,207,207,204,204,204,199,196,196,196,191,188,188,188,183,180,180,175,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,127,124,119,116,111,108,103,100,92,87,84,76,71,63,55,47,36,20,0,0,0,0,0,28,39,52,60,68,71,76,84,92,92,100,103,108,111,116,119,124,127,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,175,180,180,180,188,188,188,191,196,196,196,199,204,204,204,204,207,207,212,212,212,212,212,212,215,215,215,215,215,215,215,215,215,212,212,212,212,212,212,207,204,204,204,204,199,196,196,196,191,188,188,188,183,180,180,180,172,172,172,164,164,164,156,156,151,148,148,143,140,140,132,132,124,124,119,116,108,108,100,100,92,87,84,76,68,60,52,44,36,15,0,0,0,0,0,20,36,44,55,63,68,76,84,87,92,100,100,108,111,116,119,124,127,132,132,140,140,143,148,148,156,156,156,164,164,164,172,172,172,175,180,180,183,188,188,188,191,196,196,196,199,199,204,204,204,204,207,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,207,207,204,204,204,204,199,196,196,196,191,191,188,188,183,180,180,180,175,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,127,124,124,116,116,108,108,100,95,92,84,79,76,68,60,52,44,28,0,0,0,0,0,0,12,31,44,52,60,68,76,79,84,92,95,100,108,108,116,116,124,124,132,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,175,180,180,180,183,188,188,188,191,196,196,196,199,199,204,204,204,204,207,207,207,212,212,212,212,212,212,212,212,212,212,212,212,212,207,207,204,204,204,204,204,199,196,196,196,196,191,188,188,188,183,180,180,175,172,172,172,164,164,164,156,156,156,148,148,143,140,140,132,132,127,124,119,116,111,108,103,100,92,92,84,76,71,63,60,47,36,23,0,0,0,0,0,0,0,28,39,52,60,68,71,76,84,92,92,100,103,108,111,116,119,124,127,132,132,140,140,143,148,148,151,156,156,164,164,164,167,172,172,175,180,180,180,183,188,188,188,191,196,196,196,196,199,204,204,204,204,204,204,207,207,207,212,212,212,212,212,212,207,207,207,207,204,204,204,204,204,199,199,196,196,196,191,191,188,188,188,183,180,180,180,172,172,172,167,164,164,159,156,156,151,148,148,140,140,135,132,132,124,124,116,116,108,108,100,100,92,87,84,76,68,60,52,44,36,20,0,0,0,0,0,0,0,20,36,47,55,63,68,76,84,87,92,100,100,108,108,116,116,124,124,132,132,135,140,140,148,148,151,156,156,159,164,164,167,172,172,172,175,180,180,180,183,188,188,188,191,196,196,196,196,199,199,204,204,204,204,204,204,204,204,204,207,207,207,204,204,204,204,204,204,204,204,204,199,196,196,196,196,191,191,188,188,188,183,180,180,180,175,172,172,167,164,164,159,156,156,151,148,148,143,140,140,132,132,127,124,119,116,111,108,103,100,95,92,84,79,76,68,60,52,44,28,7,0,0,0,0,0,0,0,12,31,44,52,60,68,76,79,84,92,95,100,103,108,111,116,119,124,127,132,132,140,140,143,148,148,151,156,156,159,164,164,167,172,172,172,175,180,180,180,183,188,188,188,191,191,196,196,196,196,199,199,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,199,199,196,196,196,196,196,191,188,188,188,188,183,180,180,180,175,172,172,167,164,164,164,159,156,156,151,148,148,140,140,135,132,132,124,124,116,116,108,108,100,100,92,87,84,76,71,63,60,47,36,23,0,0,0,0,0,0,0,0,0,28,39,52,60,68,71,76,84,87,92,100,100,108,111,116,116,124,124,132,132,135,140,140,143,148,148,156,156,156,164,164,164,167,172,172,172,175,180,180,180,183,188,188,188,188,191,196,196,196,196,196,196,199,199,199,204,204,204,204,204,204,204,204,204,199,199,199,196,196,196,196,196,191,191,188,188,188,188,183,180,180,180,175,172,172,172,167,164,164,159,156,156,151,148,148,143,140,140,132,132,127,124,119,116,111,108,103,100,95,92,84,79,76,68,60,52,44,36,15,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,79,84,92,95,100,103,108,111,116,119,124,127,132,132,140,140,143,148,148,151,156,156,159,164,164,164,167,172,172,172,175,180,180,180,183,188,188,188,188,191,191,196,196,196,196,196,196,196,199,199,199,199,199,199,199,199,196,196,196,196,196,196,196,196,191,188,188,188,188,183,183,180,180,180,175,172,172,172,167,164,164,159,156,156,151,148,148,143,140,140,135,132,132,124,124,116,116,108,108,100,100,92,87,84,76,71,68,60,52,39,28,0,0,0,0,0,0,0,0,0,0,7,28,44,52,60,68,71,76,84,87,92,100,100,108,108,116,116,124,124,127,132,135,140,140,143,148,148,151,156,156,159,164,164,164,167,172,172,172,175,180,180,180,183,183,188,188,188,188,191,191,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,191,191,188,188,188,188,188,183,180,180,180,180,175,172,172,172,167,164,164,159,156,156,156,148,148,148,140,140,135,132,132,127,124,119,116,111,108,103,100,95,92,84,84,76,68,60,55,44,36,20,0,0,0,0,0,0,0,0,0,0,0,20,36,47,55,60,68,76,84,84,92,95,100,103,108,111,116,119,124,124,132,132,135,140,140,143,148,148,151,156,156,159,164,164,164,167,172,172,172,175,180,180,180,180,183,188,188,188,188,188,188,191,191,191,196,196,196,196,196,196,196,196,196,196,196,191,191,191,188,188,188,188,188,183,183,180,180,180,180,175,172,172,172,167,164,164,164,156,156,156,151,148,148,143,140,140,132,132,127,124,124,116,116,108,108,100,100,92,87,84,76,71,68,60,52,44,28,12,0,0,0,0,0,0,0,0,0,0,0,12,31,44,52,60,68,71,76,84,87,92,100,100,108,108,116,116,124,124,127,132,132,140,140,140,148,148,148,156,156,156,159,164,164,164,167,172,172,172,175,180,180,180,180,180,183,188,188,188,188,188,188,188,188,191,191,191,191,191,191,191,191,191,188,188,188,188,188,188,188,183,183,180,180,180,180,175,172,172,172,172,167,164,164,164,156,156,156,151,148,148,143,140,140,135,132,132,124,124,119,116,111,108,103,100,95,92,84,84,76,68,63,55,47,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,47,55,63,68,76,79,84,92,95,100,103,108,111,116,116,124,124,127,132,132,140,140,143,148,148,148,156,156,156,159,164,164,164,167,172,172,172,172,175,180,180,180,180,180,183,183,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,183,183,180,180,180,180,180,175,172,172,172,167,167,164,164,164,156,156,156,151,148,148,143,140,140,135,132,132,127,124,119,116,116,108,108,100,100,92,87,84,76,71,68,60,52,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,52,60,68,71,76,84,87,92,100,100,108,108,111,116,119,124,124,132,132,135,140,140,143,148,148,148,156,156,156,159,164,164,164,167,172,172,172,172,175,175,180,180,180,180,180,183,183,183,188,188,188,188,188,188,188,188,188,188,188,188,183,183,183,180,180,180,180,180,180,175,172,172,172,172,167,164,164,164,159,156,156,156,151,148,148,143,140,140,135,132,132,127,124,124,116,116,111,108,103,100,95,92,84,79,76,68,60,55,47,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,47,55,60,68,76,79,84,92,92,100,100,108,108,116,116,119,124,127,132,132,135,140,140,143,148,148,148,156,156,156,159,164,164,164,164,167,172,172,172,172,175,175,180,180,180,180,180,180,180,183,183,183,183,183,183,183,183,183,183,180,180,180,180,180,180,180,180,175,172,172,172,172,172,167,164,164,164,159,156,156,156,151,148,148,148,140,140,140,132,132,127,124,124,119,116,111,108,103,100,95,92,87,84,76,71,68,60,52,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,60,68,71,76,84,84,92,95,100,103,108,111,116,116,124,124,127,132,132,135,140,140,143,148,148,148,151,156,156,156,159,164,164,164,167,167,172,172,172,172,175,175,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,175,175,172,172,172,172,172,167,164,164,164,164,159,156,156,156,151,148,148,148,140,140,140,132,132,132,124,124,119,116,116,108,108,100,100,92,92,84,79,76,68,60,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,76,76,84,87,92,100,100,108,108,111,116,116,124,124,127,132,132,135,140,140,143,148,148,148,151,156,156,156,159,164,164,164,164,167,167,172,172,172,172,172,175,175,175,180,180,180,180,180,180,180,180,180,180,180,180,175,175,175,172,172,172,172,172,172,167,164,164,164,164,159,159,156,156,156,151,148,148,143,140,140,140,132,132,132,124,124,119,116,116,108,108,103,100,95,92,84,84,76,68,63,60,52,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,39,52,55,63,68,76,79,84,92,92,100,100,108,108,111,116,119,124,124,127,132,132,135,140,140,143,148,148,148,151,156,156,156,159,159,164,164,164,164,167,167,172,172,172,172,172,172,172,172,175,175,175,175,175,175,175,175,175,172,172,172,172,172,172,172,172,167,164,164,164,164,164,159,156,156,156,151,148,148,148,143,140,140,140,132,132,132,124,124,119,116,116,111,108,103,100,95,92,87,84,76,71,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,68,71,76,84,84,92,95,100,100,108,108,116,116,119,124,124,127,132,132,135,140,140,140,148,148,148,151,151,156,156,156,159,159,164,164,164,164,164,167,167,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,167,167,164,164,164,164,164,159,156,156,156,156,151,148,148,148,143,140,140,140,132,132,132,124,124,124,116,116,111,108,103,100,100,92,87,84,79,76,68,60,55,47,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,60,68,76,76,84,87,92,95,100,103,108,108,116,116,119,124,124,127,132,132,135,140,140,140,143,148,148,148,151,156,156,156,156,159,159,164,164,164,164,164,164,167,167,167,172,172,172,172,172,172,172,172,172,172,167,167,167,167,164,164,164,164,164,164,159,156,156,156,156,151,151,148,148,148,143,140,140,135,132,132,132,124,124,124,116,116,111,108,108,100,100,92,92,84,79,76,68,63,60,52,44,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,52,55,63,68,76,79,84,87,92,95,100,103,108,108,116,116,119,124,124,127,132,132,135,140,140,140,143,148,148,148,148,151,156,156,156,156,159,159,164,164,164,164,164,164,164,164,164,164,164,167,167,167,164,164,164,164,164,164,164,164,164,164,159,159,156,156,156,156,156,151,148,148,148,143,140,140,140,135,132,132,132,124,124,124,116,116,111,108,108,100,100,92,92,84,84,76,71,68,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,63,68,76,79,84,92,92,100,100,103,108,108,116,116,119,124,124,127,132,132,132,135,140,140,140,143,148,148,148,148,151,156,156,156,156,156,159,159,159,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,159,159,156,156,156,156,156,151,151,148,148,148,148,143,140,140,140,135,132,132,127,124,124,119,116,116,111,108,108,100,100,95,92,87,84,76,71,68,60,52,47,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,60,68,71,76,84,84,92,92,100,100,103,108,108,116,116,119,124,124,124,132,132,132,135,140,140,140,143,143,148,148,148,148,151,156,156,156,156,156,156,156,159,159,159,159,159,159,164,164,159,159,159,159,159,159,156,156,156,156,156,156,156,151,151,148,148,148,148,143,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,100,100,95,92,87,84,76,76,68,60,55,52,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,36,47,52,60,68,71,76,84,84,92,92,100,100,103,108,108,116,116,116,124,124,124,127,132,132,132,135,140,140,140,143,143,148,148,148,148,151,151,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,151,151,148,148,148,148,148,143,140,140,140,140,135,132,132,132,127,124,124,119,116,116,111,108,108,100,100,95,92,87,84,79,76,68,63,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,47,55,60,68,71,76,84,84,92,92,100,100,103,108,108,111,116,116,119,124,124,127,132,132,132,135,135,140,140,140,143,143,148,148,148,148,148,148,151,151,151,156,156,156,156,156,156,156,156,156,156,156,156,156,151,151,151,148,148,148,148,148,148,143,140,140,140,140,135,132,132,132,127,124,124,124,119,116,116,111,108,108,100,100,95,92,87,84,79,76,68,63,60,52,44,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,44,52,55,60,68,71,76,84,84,92,92,95,100,103,108,108,111,116,116,119,124,124,124,127,132,132,132,135,135,140,140,140,140,143,143,148,148,148,148,148,148,148,148,148,151,151,151,151,151,151,151,148,148,148,148,148,148,148,148,148,143,143,140,140,140,140,135,132,132,132,132,127,124,124,119,116,116,116,108,108,103,100,100,95,92,87,84,79,76,68,68,60,52,44,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,55,60,68,71,76,84,84,92,92,95,100,100,108,108,111,116,116,116,119,124,124,124,127,132,132,132,135,135,140,140,140,140,140,143,143,143,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,148,143,143,140,140,140,140,140,140,135,132,132,132,132,127,124,124,124,119,116,116,111,108,108,103,100,100,92,92,87,84,79,76,68,68,60,52,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,55,60,68,71,76,84,84,87,92,95,100,100,103,108,108,111,116,116,119,124,124,124,124,127,132,132,132,132,135,135,140,140,140,140,140,140,140,143,143,143,143,143,143,143,143,143,143,143,143,143,140,140,140,140,140,140,140,135,135,132,132,132,132,127,124,124,124,119,116,116,116,111,108,108,103,100,100,92,92,87,84,79,76,68,68,60,52,47,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,55,60,68,71,76,79,84,87,92,92,100,100,103,108,108,111,116,116,116,119,124,124,124,124,127,132,132,132,132,132,135,135,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,135,135,132,132,132,132,132,127,127,124,124,124,119,116,116,116,111,108,108,103,100,100,95,92,92,84,84,76,76,68,68,60,52,47,39,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,55,60,68,71,76,79,84,87,92,92,95,100,100,103,108,108,111,116,116,116,119,124,124,124,124,127,127,132,132,132,132,132,132,135,135,135,135,140,140,140,140,140,140,140,140,135,135,135,135,132,132,132,132,132,132,132,127,124,124,124,124,119,116,116,116,111,108,108,108,103,100,100,95,92,87,84,84,76,76,68,63,60,52,47,39,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,55,60,68,68,76,76,84,84,92,92,95,100,100,103,108,108,108,111,116,116,116,119,119,124,124,124,124,127,127,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,127,127,124,124,124,124,124,119,116,116,116,111,108,108,108,103,100,100,95,92,92,87,84,79,76,71,68,63,60,52,44,39,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,44,52,52,60,68,68,76,76,84,84,87,92,92,95,100,100,103,108,108,108,111,116,116,116,116,119,124,124,124,124,124,124,127,127,127,127,132,132,132,132,132,132,132,132,132,132,132,127,127,127,124,124,124,124,124,124,119,119,116,116,116,111,108,108,108,103,100,100,100,95,92,92,84,84,79,76,71,68,60,60,52,44,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,39,47,52,60,63,68,71,76,79,84,84,92,92,92,100,100,100,103,108,108,108,111,111,116,116,116,116,119,119,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,119,119,116,116,116,116,116,111,108,108,108,103,100,100,100,95,92,92,87,84,84,76,76,68,68,60,55,52,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,39,44,52,60,60,68,68,76,76,84,84,87,92,92,92,100,100,100,103,108,108,108,108,111,111,116,116,116,116,116,119,119,119,119,124,124,124,124,124,124,124,124,124,124,124,119,119,119,116,116,116,116,116,116,111,108,108,108,108,103,100,100,100,95,92,92,87,84,84,79,76,71,68,63,60,52,52,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,44,52,55,60,63,68,71,76,79,84,84,87,92,92,92,95,100,100,100,103,108,108,108,108,111,111,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,111,111,108,108,108,108,108,103,100,100,100,95,92,92,87,84,84,79,76,76,68,68,60,60,52,47,39,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,44,47,52,60,60,68,68,76,76,79,84,84,87,92,92,92,95,100,100,100,100,103,108,108,108,108,108,108,111,111,111,116,116,116,116,116,116,116,116,116,116,111,111,111,108,108,108,108,108,108,103,103,100,100,100,100,95,92,92,87,84,84,84,76,76,71,68,63,60,55,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,36,44,52,55,60,63,68,68,76,76,79,84,84,87,92,92,92,92,95,100,100,100,100,103,103,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,108,103,103,100,100,100,100,100,95,92,92,92,87,84,84,84,76,76,71,68,68,60,60,52,47,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,44,47,52,55,60,63,68,71,76,76,79,84,84,84,87,92,92,92,92,95,100,100,100,100,100,100,100,103,103,103,103,103,108,108,103,103,103,103,103,103,100,100,100,100,100,100,95,95,92,92,92,92,87,84,84,79,76,76,71,68,68,60,60,52,52,44,36,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,36,44,52,52,60,60,63,68,68,76,76,76,79,84,84,84,87,92,92,92,92,92,95,95,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,95,95,95,92,92,92,92,87,87,84,84,84,79,76,76,71,68,68,63,60,55,52,44,44,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,44,52,52,60,60,63,68,68,71,76,76,76,79,84,84,84,84,87,92,92,92,92,92,92,92,92,92,95,95,95,95,92,92,92,92,92,92,92,92,92,87,87,84,84,84,84,79,76,76,76,71,68,68,60,60,55,52,47,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,39,44,52,52,55,60,63,68,68,68,71,76,76,76,79,84,84,84,84,84,84,87,87,87,92,92,92,92,92,92,92,92,87,87,87,87,84,84,84,84,84,79,79,76,76,76,71,68,68,63,60,60,55,52,47,44,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,39,44,47,52,55,60,60,63,68,68,68,71,76,76,76,76,79,79,79,84,84,84,84,84,84,84,84,84,84,84,84,84,84,84,79,79,76,76,76,76,71,71,68,68,63,60,60,60,52,52,44,44,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,36,44,44,52,52,55,60,60,63,68,68,68,68,71,71,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,71,68,68,68,68,63,60,60,60,52,52,47,44,39,36,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,23,31,36,39,44,47,52,52,55,60,60,60,60,63,68,68,68,68,68,68,68,71,71,71,71,71,71,71,68,68,68,68,68,68,68,63,60,60,60,55,52,52,47,44,44,36,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,31,36,39,44,44,47,52,52,52,55,60,60,60,60,60,63,63,63,63,63,63,63,63,63,63,60,60,60,60,60,60,55,52,52,52,47,44,44,36,36,28,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,31,36,36,39,44,44,47,52,52,52,52,52,52,55,55,55,55,55,55,55,55,55,52,52,52,52,52,47,44,44,44,39,36,36,28,23,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,28,31,36,36,36,39,44,44,44,44,44,44,47,47,47,47,44,44,44,44,44,44,44,39,36,36,31,28,28,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,23,28,28,28,31,36,36,36,36,36,36,36,36,36,36,31,31,28,28,28,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,20,20,20,20,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t FLARE_TEXTURE_3[16384] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,36,39,44,52,55,60,60,60,60,60,60,60,52,52,44,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,44,60,68,76,84,92,95,100,108,108,108,111,111,111,108,108,103,100,92,84,79,71,60,52,44,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,71,84,95,108,116,124,132,140,148,148,156,156,159,164,164,164,159,156,151,148,140,135,127,119,111,100,92,76,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,60,76,92,108,119,132,143,156,164,172,180,188,196,199,204,207,212,212,212,212,212,204,204,196,191,188,180,172,159,148,140,124,116,100,84,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,55,76,92,108,124,140,156,167,180,191,204,212,220,228,236,244,252,252,244,244,244,244,244,244,247,252,247,244,236,228,220,207,196,188,172,164,148,132,116,100,84,63,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,100,119,140,156,172,188,204,215,228,244,252,244,231,223,215,212,204,199,196,196,191,191,196,196,196,204,207,212,220,228,236,244,244,236,220,212,196,180,164,148,132,108,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,39,60,84,108,124,148,164,183,204,220,236,252,239,228,212,204,191,183,172,167,159,156,148,148,143,140,140,143,148,148,151,156,164,172,180,188,196,207,220,236,247,244,228,212,196,175,156,135,116,95,76,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,60,84,108,132,151,172,191,212,228,247,236,220,204,191,180,164,156,143,132,124,116,111,108,100,100,92,92,92,92,95,100,103,108,116,124,132,140,148,159,172,188,199,212,228,244,239,220,204,180,164,140,116,92,71,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,52,76,100,124,148,172,196,220,236,244,228,207,191,172,159,143,132,116,108,95,84,76,68,60,55,52,47,44,44,44,44,44,52,52,60,68,71,79,92,100,111,124,140,151,164,180,199,220,236,247,228,204,183,164,140,116,92,63,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,68,92,119,148,172,196,220,239,244,220,199,180,164,143,127,111,95,84,68,60,44,36,28,20,12,0,0,0,0,0,0,0,0,0,0,12,15,23,31,44,52,63,76,92,103,119,135,156,172,188,212,228,252,228,204,180,156,132,108,79,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,79,108,135,164,188,212,236,244,220,196,175,156,132,116,100,79,63,52,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,44,55,71,92,108,124,143,164,188,207,228,252,228,204,175,148,124,92,68,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,92,124,148,180,204,228,247,220,196,172,151,132,108,87,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,79,100,119,140,164,188,212,236,244,220,191,164,135,108,76,47,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,39,71,100,132,164,188,220,244,228,204,180,156,132,108,84,63,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,76,95,119,140,164,191,220,244,231,204,175,148,116,87,55,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,108,140,172,204,231,244,215,188,164,135,111,84,63,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,52,76,100,124,148,175,204,228,244,215,188,156,124,92,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,116,148,180,212,244,231,204,172,148,119,92,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,31,55,79,108,132,159,188,220,247,228,196,164,132,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,87,124,156,188,220,252,220,191,164,132,108,76,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,63,92,119,148,180,207,236,236,204,172,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,124,156,191,228,244,212,183,151,124,92,63,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,76,108,140,167,196,228,244,207,175,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,92,124,159,196,228,244,207,175,143,116,84,52,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,68,100,127,159,191,228,244,212,180,143,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,87,124,159,196,228,236,204,172,140,108,76,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,92,124,156,188,220,247,212,180,140,108,68,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,44,84,124,156,196,228,236,204,167,132,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,116,151,188,220,247,212,175,140,103,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,116,156,191,228,236,204,167,132,100,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,47,79,116,148,183,220,247,212,172,135,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,71,108,148,188,228,244,204,167,132,95,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,116,148,188,223,244,204,167,132,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,100,140,180,220,244,207,172,132,100,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,44,79,116,151,188,228,236,199,164,124,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,132,172,212,252,212,175,140,100,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,84,119,156,196,236,231,191,151,111,71,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,124,164,204,244,220,183,143,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,87,124,164,204,244,220,180,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,68,108,148,188,231,231,191,151,116,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,55,92,132,172,212,252,212,172,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,52,92,135,180,220,244,204,164,124,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,60,100,140,180,223,239,196,156,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,119,164,204,244,215,172,132,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,71,116,156,196,236,228,183,140,100,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,100,148,188,228,228,188,148,108,63,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,84,124,167,212,252,212,164,124,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,124,172,212,247,204,164,119,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,100,140,183,228,236,191,148,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,108,148,196,236,220,180,135,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,156,199,244,215,172,127,84,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,132,172,220,244,196,156,111,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,132,175,220,239,196,151,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,108,151,196,239,220,172,132,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,108,151,196,244,220,172,127,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,124,172,220,244,196,151,108,63,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,84,132,172,220,239,196,148,103,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,55,100,148,191,236,220,175,132,84,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,108,151,196,244,215,172,124,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,119,164,212,244,199,156,108,63,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,84,132,180,223,236,188,143,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,183,228,228,180,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,111,156,204,252,207,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,247,207,164,116,68,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,231,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,236,191,143,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,167,212,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,220,172,127,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,103,151,196,244,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,252,204,159,111,63,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,87,135,180,228,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,71,119,167,215,239,191,143,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,119,167,215,239,191,143,95,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,143,191,244,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,215,167,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,228,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,244,191,143,95,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,119,167,220,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,231,183,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,207,247,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,223,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,199,247,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,92,140,188,236,215,167,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,191,244,212,164,116,68,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,148,196,244,212,159,111,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,135,188,236,220,172,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,199,252,204,156,108,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,79,132,180,228,223,175,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,108,156,204,252,199,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,175,228,228,180,132,79,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,207,244,196,148,100,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,71,124,172,220,231,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,244,196,143,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,119,172,220,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,244,191,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,167,220,236,188,135,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,244,191,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,167,220,236,188,140,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,244,196,143,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,167,220,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,244,196,148,95,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,119,172,220,236,183,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,247,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,76,124,172,223,231,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,103,151,204,252,204,151,103,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,127,180,228,228,180,127,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,247,207,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,231,220,172,124,76,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,191,244,212,164,116,68,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,92,140,188,236,215,167,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,135,188,236,220,172,124,71,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,100,148,196,244,212,159,111,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,79,127,180,228,228,180,132,79,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,55,108,156,204,252,204,156,103,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,71,119,172,220,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,68,116,164,212,244,196,143,95,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,159,207,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,236,183,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,207,159,111,63,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,87,135,183,231,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,247,199,151,103,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,79,127,175,223,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,212,164,119,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,95,143,188,236,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,228,180,135,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,111,159,204,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,244,199,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,175,223,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,239,220,172,124,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,55,100,148,196,239,215,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,175,220,236,188,143,100,52,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,167,212,244,196,151,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,156,204,247,212,164,119,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,52,100,140,188,231,228,180,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,135,180,228,228,188,140,95,52,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,116,164,207,252,204,159,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,164,204,252,207,164,119,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,100,140,188,228,228,183,140,92,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,95,140,183,228,228,188,140,100,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,119,164,207,252,207,164,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,116,164,204,252,212,164,124,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,100,148,188,231,228,183,140,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,92,140,180,228,236,191,148,108,63,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,84,127,172,212,247,204,159,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,156,204,244,220,175,132,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,111,156,196,236,220,180,135,92,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,132,175,220,244,204,159,119,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,100,140,180,223,236,196,156,111,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,148,191,231,228,188,148,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,47,87,127,167,212,252,212,172,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,79,124,164,204,244,220,180,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,76,116,156,196,236,228,183,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,135,175,215,247,207,167,127,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,68,108,148,188,228,236,196,156,116,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,148,188,228,236,196,159,124,84,47,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,103,140,180,220,244,207,167,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,116,156,196,236,228,191,156,116,79,44,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,100,135,172,212,252,215,175,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,47,87,124,164,204,244,228,188,151,116,79,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,100,132,172,204,244,220,183,148,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,132,172,207,244,220,188,148,116,79,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,100,132,167,204,239,228,188,151,116,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,63,100,140,175,212,247,220,183,148,116,84,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,68,100,132,167,204,236,228,196,156,119,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,140,180,212,247,220,188,151,119,87,55,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,39,68,103,135,172,204,236,231,196,159,124,84,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,143,180,212,247,223,188,156,124,92,60,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,47,76,108,140,172,204,239,228,196,159,124,92,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,175,212,244,228,196,164,132,100,71,44,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,87,116,148,180,212,244,228,196,159,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,172,204,236,236,204,172,140,116,84,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,68,100,127,156,188,220,252,220,188,156,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,68,103,135,167,199,231,244,212,180,156,124,100,71,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,84,111,140,167,196,228,244,215,183,151,119,84,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,68,100,132,164,191,220,252,223,196,167,140,116,92,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,52,76,100,127,156,180,212,236,236,207,175,148,116,84,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,92,124,151,180,212,239,236,212,183,156,132,108,84,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,52,76,100,119,148,172,196,223,252,228,196,167,140,108,76,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,111,140,172,196,228,252,228,199,175,151,132,108,84,68,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,55,76,100,116,140,164,188,212,236,236,212,183,156,127,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,71,100,132,156,183,212,236,244,220,196,172,151,132,111,92,76,60,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,47,68,84,100,124,140,164,188,207,231,247,220,196,172,140,116,84,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,87,116,140,164,191,215,239,244,220,196,180,156,140,124,103,87,71,60,44,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,28,39,52,68,79,95,111,132,148,167,188,207,228,252,228,204,180,156,127,100,76,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,71,100,124,148,172,196,220,239,244,223,204,188,167,151,135,119,108,92,84,68,60,52,44,36,31,28,20,20,20,20,20,20,23,28,36,39,47,55,68,76,87,100,116,127,143,159,175,196,212,231,252,228,207,183,159,135,111,84,60,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,55,79,108,127,151,172,196,215,236,252,231,212,196,180,167,156,140,132,119,108,100,92,84,79,76,71,68,68,68,68,68,76,76,84,92,100,108,116,124,135,148,164,175,188,204,223,239,244,228,204,183,164,140,116,92,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,84,108,127,148,172,188,207,228,244,244,228,215,204,188,180,167,156,148,140,135,132,124,124,119,116,116,116,119,124,127,132,140,148,156,164,172,183,196,212,223,236,252,236,215,196,180,159,140,116,95,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,103,124,143,164,180,196,212,228,239,252,236,228,220,207,199,191,188,180,175,172,172,167,167,167,172,172,180,180,188,196,204,212,220,231,244,244,236,220,204,188,172,151,132,116,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,60,76,95,116,132,148,164,180,191,204,215,228,236,247,247,244,236,228,228,220,220,220,220,220,220,223,228,231,236,244,252,244,236,220,212,196,188,172,156,140,124,108,87,68,47,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,52,68,84,100,116,132,143,156,167,180,188,196,204,212,220,223,228,231,236,236,236,236,236,231,228,220,215,212,204,196,183,172,164,148,140,124,108,92,76,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,68,84,95,108,119,132,140,148,156,164,172,175,180,180,188,188,188,188,183,180,180,172,167,159,156,143,135,124,116,100,92,76,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,47,60,71,84,92,100,108,116,119,124,132,132,135,135,140,135,132,132,127,124,116,111,103,95,84,76,68,52,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,52,60,68,71,76,79,84,84,87,87,84,84,84,76,76,68,60,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,28,28,36,36,36,36,36,36,31,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t FLARE_TEXTURE_4[16384] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,36,39,44,52,55,60,60,60,60,60,60,60,52,52,44,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,44,60,68,76,84,92,95,100,108,108,108,111,111,111,108,108,103,100,92,84,79,71,60,52,44,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,71,84,95,108,116,124,132,140,148,148,156,156,159,164,164,164,159,156,151,148,140,135,127,119,111,100,92,76,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,60,76,92,108,119,132,143,156,164,172,180,188,196,199,204,207,212,212,212,212,212,204,204,196,191,188,180,172,159,148,140,124,116,100,84,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,55,76,92,108,124,140,156,167,180,191,204,212,220,228,236,244,252,252,252,252,252,252,252,252,252,252,247,244,236,228,220,207,196,188,172,164,148,132,116,100,84,63,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,100,119,140,156,172,188,204,215,228,244,252,252,252,252,247,247,247,244,244,244,244,244,244,244,244,247,247,247,247,252,252,252,244,236,220,212,196,180,164,148,132,108,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,39,60,84,108,124,148,164,183,204,220,236,252,252,252,247,247,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,247,247,252,252,244,228,212,196,175,156,135,116,95,76,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,60,84,108,132,151,172,191,212,228,247,252,247,247,244,244,244,244,244,239,239,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,239,239,244,244,244,244,244,247,252,252,239,220,204,180,164,140,116,92,71,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,52,76,100,124,148,172,196,220,236,252,252,247,244,244,244,244,239,236,236,236,236,236,236,231,231,231,231,231,231,231,231,231,231,231,231,236,236,236,236,236,236,239,239,244,244,244,244,247,252,247,228,204,183,164,140,116,92,63,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,68,92,119,148,172,196,220,239,252,247,244,244,244,244,239,236,236,236,236,231,231,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,231,236,236,236,236,236,239,244,244,244,247,252,252,228,204,180,156,132,108,79,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,79,108,135,164,188,212,236,252,247,244,244,244,239,236,236,236,236,231,228,228,228,228,228,223,223,223,220,220,220,220,220,220,220,220,220,220,220,223,223,228,228,228,228,228,228,231,236,236,236,239,244,244,244,247,252,252,228,204,175,148,124,92,68,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,92,124,148,180,204,228,252,252,244,244,244,239,236,236,236,231,228,228,228,228,223,220,220,220,220,220,220,220,220,215,215,215,215,215,215,215,220,220,220,220,220,220,220,223,223,228,228,228,231,231,236,236,236,239,244,244,247,252,244,220,191,164,135,108,76,47,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,39,71,100,132,164,188,220,244,252,247,244,244,239,236,236,236,231,228,228,228,223,220,220,220,220,215,215,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,215,215,220,220,220,220,223,223,228,228,228,231,236,236,236,244,244,244,247,252,231,204,175,148,116,87,55,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,108,140,172,204,231,252,247,244,244,239,236,236,236,228,228,228,223,220,220,220,220,215,212,212,212,212,212,212,207,207,207,204,204,204,204,204,204,204,207,207,207,212,212,212,212,212,215,215,220,220,220,223,228,228,228,231,236,236,239,244,244,247,252,244,215,188,156,124,92,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,116,148,180,212,244,252,247,244,244,236,236,236,231,228,228,223,220,220,220,215,212,212,212,212,207,207,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,207,212,212,212,212,215,220,220,220,223,228,228,228,231,236,236,239,244,244,247,252,228,196,164,132,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,87,124,156,188,220,252,252,244,244,239,236,236,231,228,228,223,220,220,220,215,212,212,212,207,204,204,204,204,204,199,199,196,196,196,196,196,196,196,196,196,196,196,196,196,199,199,204,204,204,204,207,207,212,212,212,215,220,220,223,228,228,228,236,236,236,244,244,247,252,236,204,172,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,124,156,191,228,252,247,244,244,239,236,236,228,228,228,220,220,220,215,212,212,207,204,204,204,204,199,196,196,196,196,196,196,191,191,191,191,191,191,191,191,191,191,196,196,196,196,196,196,199,199,204,204,204,207,212,212,212,215,220,220,223,228,228,231,236,236,239,244,244,252,244,207,175,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,92,124,159,196,228,252,247,244,244,236,236,231,228,228,223,220,220,215,212,212,207,204,204,204,199,196,196,196,196,191,191,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,191,196,196,196,196,199,204,204,204,207,212,212,212,215,220,220,223,228,228,236,236,239,244,244,252,244,212,180,143,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,87,124,159,196,228,252,247,244,239,236,236,231,228,228,220,220,215,212,212,207,204,204,204,199,196,196,196,191,188,188,188,188,188,183,183,180,180,180,180,180,180,180,180,180,180,183,183,188,188,188,188,188,191,196,196,196,196,199,204,204,207,212,212,215,220,220,223,228,228,231,236,239,244,244,247,247,212,180,140,108,68,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,44,84,124,156,196,228,252,247,244,239,236,236,228,228,223,220,220,215,212,212,207,204,204,199,196,196,196,191,188,188,188,183,183,180,180,180,180,180,180,175,175,175,175,175,175,180,180,180,180,180,180,180,183,188,188,188,188,191,196,196,196,199,204,204,207,212,212,215,220,220,228,228,231,236,236,244,244,247,247,212,175,140,103,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,116,156,191,228,252,247,244,239,236,236,228,228,223,220,220,212,212,207,204,204,199,196,196,196,188,188,188,183,180,180,180,180,175,175,172,172,172,172,172,172,172,172,172,172,172,172,172,172,175,175,180,180,180,180,183,188,188,188,191,196,196,199,204,204,204,212,212,215,220,220,223,228,231,236,236,244,244,247,247,212,172,135,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,71,108,148,188,228,252,247,244,239,236,231,228,228,220,220,215,212,212,207,204,204,196,196,196,188,188,188,183,180,180,180,175,172,172,172,172,172,167,167,167,164,164,164,164,164,164,167,167,167,172,172,172,172,172,175,180,180,180,183,188,188,188,191,196,196,199,204,204,207,212,212,220,220,223,228,231,236,236,244,244,252,244,204,167,132,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,100,140,180,220,252,247,244,239,236,231,228,228,220,220,215,212,212,204,204,199,196,196,191,188,188,183,180,180,180,175,172,172,172,167,167,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,167,172,172,172,172,175,180,180,183,188,188,188,196,196,196,204,204,207,212,212,220,220,223,228,231,236,236,244,244,252,236,199,164,124,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,132,172,212,252,247,244,239,236,236,228,228,220,220,215,212,207,204,204,199,196,196,188,188,183,180,180,180,175,172,172,172,167,164,164,164,164,159,159,156,156,156,156,156,156,156,156,156,156,156,159,159,164,164,164,164,167,172,172,172,175,180,180,183,188,188,191,196,196,199,204,207,212,212,220,220,223,228,231,236,236,244,244,252,231,191,151,111,71,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,124,164,204,244,252,244,244,236,236,228,228,220,220,215,212,207,204,204,196,196,191,188,188,183,180,180,175,172,172,167,164,164,164,159,159,156,156,156,156,156,151,151,151,151,151,151,151,151,156,156,156,156,156,156,159,164,164,164,167,172,172,172,175,180,180,183,188,188,196,196,199,204,204,212,212,220,220,223,228,231,236,239,244,247,252,220,180,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,68,108,148,188,231,252,244,244,236,236,228,228,220,220,215,212,207,204,204,196,196,191,188,188,180,180,175,172,172,167,164,164,164,159,156,156,156,151,151,148,148,148,148,148,148,148,148,148,148,148,148,148,148,151,151,156,156,156,156,159,164,164,167,172,172,175,180,180,183,188,188,196,196,199,204,204,212,212,220,220,223,228,231,236,239,244,247,252,212,172,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,52,92,135,180,220,252,247,244,239,236,231,228,223,220,215,212,207,204,204,196,196,188,188,183,180,180,175,172,172,164,164,164,159,156,156,156,151,148,148,148,148,143,143,140,140,140,140,140,140,140,140,143,143,148,148,148,148,148,151,156,156,156,159,164,164,167,172,172,175,180,180,188,188,191,196,199,204,204,212,212,220,220,228,228,236,236,244,244,252,239,196,156,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,119,164,204,244,247,244,239,236,231,228,223,220,215,212,207,204,204,196,196,188,188,183,180,180,172,172,167,164,164,159,156,156,151,148,148,148,148,143,140,140,140,140,140,140,135,135,135,135,140,140,140,140,140,140,140,143,148,148,148,151,156,156,156,164,164,164,172,172,175,180,180,188,188,191,196,199,204,204,212,212,220,220,228,228,236,236,244,244,252,228,183,140,100,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,100,148,188,228,252,244,244,236,236,228,228,220,220,212,212,204,204,196,196,188,188,183,180,175,172,172,167,164,164,156,156,156,148,148,148,143,140,140,140,140,135,132,132,132,132,132,132,132,132,132,132,132,132,135,135,140,140,140,143,148,148,148,151,156,156,159,164,164,167,172,175,180,180,188,188,191,196,199,204,207,212,215,220,223,228,231,236,239,244,247,252,212,164,124,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,124,172,212,252,247,244,236,236,228,228,220,220,212,212,204,204,196,196,188,188,183,180,175,172,172,164,164,159,156,156,151,148,148,143,140,140,140,135,132,132,132,132,127,127,127,124,124,124,124,127,127,127,132,132,132,132,132,135,140,140,140,148,148,148,156,156,156,164,164,167,172,172,180,180,188,188,191,196,199,204,207,212,215,220,223,228,231,236,239,244,252,236,191,148,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,108,148,196,236,252,244,239,236,231,228,223,220,215,212,207,204,199,196,191,188,183,180,175,172,172,164,164,159,156,156,148,148,143,140,140,140,135,132,132,132,127,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,127,132,132,132,135,140,140,143,148,148,151,156,156,164,164,167,172,172,180,180,188,188,196,196,204,204,212,212,220,220,228,228,236,236,244,244,252,215,172,127,84,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,132,172,220,252,244,244,236,236,228,228,220,215,212,207,204,199,196,191,188,183,180,175,172,172,164,164,159,156,151,148,148,143,140,140,135,132,132,127,124,124,124,124,119,119,116,116,116,116,116,116,116,116,116,119,119,124,124,124,127,132,132,132,135,140,140,143,148,151,156,156,159,164,167,172,175,180,180,188,188,196,196,204,204,212,212,220,220,228,231,236,239,244,247,239,196,151,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,108,151,196,239,247,244,239,236,231,228,220,220,212,212,204,204,196,196,188,188,180,180,172,172,164,164,156,156,151,148,148,140,140,135,132,132,127,124,124,124,119,116,116,116,116,116,111,111,111,111,111,111,111,116,116,116,116,119,119,124,124,127,132,132,135,140,140,143,148,148,156,156,159,164,167,172,175,180,183,188,191,196,199,204,207,212,215,220,223,228,236,236,244,244,252,220,172,127,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,124,172,220,252,244,244,236,236,228,223,220,215,212,207,204,196,196,188,188,180,180,172,172,164,164,159,156,151,148,148,140,140,135,132,132,124,124,124,119,116,116,116,111,108,108,108,108,108,108,108,108,108,108,108,108,111,111,116,116,116,119,124,124,127,132,132,140,140,143,148,148,156,156,159,164,167,172,175,180,183,188,191,196,199,204,212,212,220,220,228,228,236,239,244,247,239,196,148,103,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,55,100,148,191,236,247,244,239,236,228,228,220,220,212,207,204,199,196,191,188,183,180,175,172,167,164,159,156,151,148,143,140,140,132,132,127,124,124,119,116,116,111,108,108,108,108,103,103,100,100,100,100,100,100,103,103,108,108,108,108,111,116,116,116,124,124,124,132,132,135,140,143,148,148,156,156,164,164,172,172,180,180,188,188,196,196,204,204,212,215,220,223,228,236,236,244,244,252,215,172,124,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,119,164,212,252,244,244,236,236,228,223,220,215,212,204,204,196,196,188,183,180,175,172,167,164,159,156,151,148,148,140,140,132,132,127,124,124,116,116,116,108,108,108,103,100,100,100,100,100,100,95,95,95,100,100,100,100,100,103,108,108,108,111,116,116,119,124,124,132,132,135,140,143,148,148,156,156,164,164,172,172,180,180,188,191,196,199,204,207,212,220,220,228,228,236,239,244,252,236,188,143,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,183,228,252,244,239,236,231,228,220,220,212,207,204,199,196,188,188,180,180,172,172,164,164,156,156,148,148,140,140,132,132,127,124,119,116,116,111,108,108,103,100,100,100,95,92,92,92,92,92,92,92,92,92,92,95,100,100,100,100,108,108,108,116,116,119,124,124,132,132,135,140,143,148,151,156,159,164,167,172,175,180,183,188,196,196,204,204,212,215,220,223,228,236,236,244,247,252,207,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,247,247,244,236,236,228,223,220,215,212,204,204,196,196,188,183,180,175,172,164,164,156,156,148,148,140,140,132,132,127,124,119,116,116,108,108,103,100,100,100,95,92,92,92,87,87,87,84,84,84,87,87,92,92,92,92,95,100,100,103,108,108,111,116,116,124,124,132,132,135,140,143,148,151,156,159,164,172,172,180,180,188,188,196,199,204,207,212,220,220,228,231,236,239,244,252,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,252,244,244,236,231,228,220,220,212,207,204,199,196,188,188,180,180,172,167,164,159,156,151,148,143,140,135,132,127,124,119,116,116,108,108,103,100,100,95,92,92,87,84,84,84,84,84,84,84,84,84,84,84,84,87,92,92,92,95,100,100,108,108,111,116,116,124,124,132,132,140,140,148,148,156,156,164,164,172,175,180,183,188,196,196,204,204,212,215,220,223,228,236,236,244,247,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,247,244,239,236,228,228,220,215,212,204,204,196,196,188,183,180,175,172,164,164,156,156,148,143,140,135,132,127,124,119,116,116,108,108,100,100,95,92,92,87,84,84,84,79,79,76,76,76,76,76,76,76,79,84,84,84,87,92,92,95,100,100,103,108,111,116,116,124,124,132,132,140,140,148,151,156,159,164,167,172,180,180,188,188,196,199,204,212,212,220,223,228,231,236,244,244,252,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,252,247,244,236,236,228,223,220,212,212,204,199,196,191,188,180,180,172,167,164,159,156,148,148,140,140,132,132,124,124,116,116,108,108,100,100,95,92,92,84,84,84,79,76,76,76,76,71,71,71,71,71,76,76,76,76,79,84,84,87,92,92,100,100,103,108,111,116,119,124,127,132,135,140,143,148,156,156,164,164,172,175,180,183,188,196,196,204,207,212,215,220,228,228,236,239,244,252,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,71,119,167,215,252,244,244,236,231,228,220,220,212,207,204,196,196,188,183,180,175,172,164,164,156,151,148,143,140,135,132,124,124,116,116,108,108,100,100,95,92,87,84,84,79,76,76,71,71,68,68,68,68,68,68,68,68,68,71,76,76,76,84,84,87,92,92,100,100,103,108,111,116,119,124,127,132,140,140,148,148,156,159,164,167,172,180,180,188,191,196,204,204,212,215,220,223,228,236,236,244,247,239,191,143,95,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,252,244,239,236,228,228,220,215,212,204,204,196,191,188,180,180,172,172,164,159,156,148,148,140,140,132,127,124,119,116,111,108,103,100,95,92,87,84,84,76,76,76,68,68,68,63,63,60,60,60,60,60,63,68,68,68,71,76,76,79,84,84,92,92,100,100,108,108,116,116,124,124,132,135,140,143,148,156,156,164,164,172,175,180,188,188,196,199,204,207,212,220,223,228,231,236,244,247,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,143,191,244,247,244,236,236,228,223,220,212,212,204,199,196,188,188,180,175,172,167,164,156,156,148,143,140,135,132,124,124,116,116,108,103,100,95,92,87,84,84,76,76,71,68,68,63,60,60,60,60,55,55,60,60,60,60,60,63,68,68,76,76,79,84,84,92,92,100,100,108,111,116,119,124,127,132,140,140,148,151,156,159,164,172,172,180,183,188,196,196,204,207,212,215,220,228,231,236,244,244,252,215,167,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,252,247,244,236,231,228,220,220,212,207,204,196,196,188,183,180,172,172,164,159,156,151,148,140,140,132,127,124,119,116,108,108,100,100,92,92,84,84,76,76,71,68,63,60,60,60,55,52,52,52,52,52,52,52,55,60,60,63,68,68,71,76,79,84,87,92,95,100,103,108,111,116,124,124,132,135,140,143,148,156,156,164,167,172,180,180,188,191,196,204,204,212,215,220,228,228,236,239,244,252,228,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,252,244,244,236,231,228,220,215,212,207,204,196,191,188,183,180,172,167,164,159,156,148,148,140,135,132,124,124,116,111,108,103,100,95,92,84,84,76,76,71,68,63,60,60,55,52,52,47,47,47,44,47,47,52,52,52,55,60,60,68,68,71,76,79,84,87,92,100,100,108,108,116,119,124,127,132,140,140,148,151,156,164,164,172,175,180,188,188,196,199,204,212,212,220,223,228,236,236,244,247,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,252,244,239,236,228,228,220,215,212,204,204,196,191,188,180,175,172,167,164,156,151,148,143,140,132,132,124,119,116,108,108,100,100,92,87,84,79,76,71,68,63,60,55,52,52,47,44,44,44,44,44,44,44,44,47,52,52,55,60,60,68,68,76,76,84,84,92,95,100,103,108,116,116,124,124,132,135,140,148,148,156,159,164,172,172,180,183,188,196,196,204,207,212,220,223,228,231,236,244,247,247,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,252,244,239,236,228,223,220,212,212,204,199,196,188,188,180,175,172,164,164,156,151,148,140,140,132,127,124,116,116,108,103,100,95,92,84,84,76,76,68,63,60,55,52,52,44,44,44,39,36,36,36,36,36,39,44,44,47,52,55,60,60,68,68,76,79,84,87,92,100,100,108,111,116,119,124,132,132,140,143,148,156,156,164,167,172,180,183,188,191,196,204,207,212,220,220,228,231,236,244,244,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,92,140,188,236,247,244,236,236,228,223,220,212,212,204,199,196,188,183,180,172,172,164,159,156,148,148,140,135,132,124,124,116,111,108,100,100,92,87,84,79,76,68,68,60,60,52,52,44,44,39,36,36,36,31,31,31,36,36,36,44,44,47,52,55,60,63,68,71,76,84,84,92,95,100,103,108,116,119,124,127,132,140,143,148,151,156,164,167,172,180,180,188,191,196,204,204,212,215,220,228,231,236,244,244,252,212,164,116,68,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,148,196,244,247,244,236,231,228,223,220,212,207,204,196,196,188,183,180,172,167,164,159,156,148,143,140,132,132,124,119,116,108,108,100,95,92,84,84,76,71,68,63,60,55,52,44,44,39,36,31,28,28,28,28,28,28,31,36,36,44,44,47,52,60,60,68,68,76,79,84,92,92,100,103,108,111,116,124,127,132,140,140,148,151,156,164,164,172,175,180,188,191,196,199,204,212,215,220,228,228,236,239,244,252,220,172,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,199,252,247,244,236,231,228,220,220,212,207,204,196,191,188,180,180,172,167,164,156,156,148,143,140,132,127,124,119,116,108,103,100,92,92,84,79,76,71,68,60,60,52,47,44,39,36,31,28,28,23,20,20,20,23,28,28,36,36,44,44,52,52,60,63,68,76,76,84,87,92,100,100,108,111,116,124,124,132,135,140,148,148,156,159,164,172,175,180,188,188,196,199,204,212,215,220,223,228,236,239,244,252,223,175,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,108,156,204,252,244,244,236,231,228,220,220,212,207,204,196,191,188,180,180,172,167,164,156,151,148,140,140,132,127,124,116,116,108,103,100,92,87,84,79,76,68,63,60,55,52,44,44,36,31,28,28,20,20,20,20,20,20,23,28,28,36,39,44,47,52,60,60,68,71,76,84,84,92,95,100,108,108,116,119,124,132,135,140,148,148,156,159,164,172,172,180,183,188,196,199,204,212,212,220,223,228,236,239,244,252,228,180,132,79,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,207,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,140,132,127,124,116,111,108,100,100,92,87,84,76,76,68,63,60,52,47,44,39,36,28,28,20,20,12,12,12,12,15,20,23,28,31,36,44,44,52,55,60,68,68,76,79,84,92,95,100,108,108,116,119,124,132,132,140,143,148,156,159,164,172,172,180,183,188,196,199,204,212,212,220,223,228,236,239,244,247,231,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,135,132,124,124,116,111,108,100,100,92,87,84,76,71,68,60,60,52,47,44,36,36,28,23,20,12,12,7,7,12,12,20,20,28,28,36,39,44,52,55,60,63,68,76,79,84,92,92,100,103,108,116,119,124,132,132,140,143,148,156,159,164,172,172,180,183,188,196,196,204,207,212,220,223,228,236,236,244,247,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,135,132,124,124,116,111,108,100,95,92,84,84,76,71,68,60,55,52,47,44,36,31,28,20,20,12,7,0,0,0,12,15,20,23,28,36,39,44,52,52,60,63,68,76,79,84,92,92,100,103,108,116,119,124,132,132,140,143,148,156,156,164,167,172,180,183,188,196,196,204,207,212,220,223,228,236,236,244,247,236,188,135,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,135,132,124,124,116,111,108,100,95,92,84,84,76,71,68,60,55,52,44,44,36,31,28,20,20,12,7,0,0,0,12,12,20,23,28,36,39,44,52,52,60,63,68,76,79,84,92,92,100,103,108,116,119,124,132,132,140,143,148,156,156,164,167,172,180,183,188,196,196,204,207,212,220,223,228,236,236,244,247,236,188,140,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,135,132,124,124,116,111,108,100,95,92,84,84,76,71,68,60,60,52,47,44,36,31,28,20,20,12,12,0,0,7,12,15,20,28,28,36,39,44,52,52,60,63,68,76,79,84,92,92,100,103,108,116,119,124,132,132,140,143,148,156,159,164,167,172,180,183,188,196,196,204,207,212,220,223,228,236,236,244,247,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,175,172,164,164,156,151,148,140,140,132,127,124,116,111,108,100,100,92,87,84,76,71,68,60,60,52,47,44,36,36,28,23,20,15,12,12,12,12,12,20,20,28,31,36,44,44,52,55,60,68,68,76,79,84,92,92,100,103,108,116,119,124,132,132,140,143,148,156,159,164,172,172,180,183,188,196,199,204,212,212,220,223,228,236,236,244,247,236,183,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,252,244,244,236,231,228,220,215,212,204,204,196,191,188,180,180,172,167,164,156,151,148,140,140,132,127,124,116,111,108,103,100,92,87,84,76,76,68,63,60,52,52,44,39,36,31,28,23,20,20,15,12,15,20,20,28,28,36,36,44,47,52,55,60,68,71,76,84,84,92,95,100,108,108,116,119,124,132,135,140,143,148,156,159,164,172,172,180,183,188,196,199,204,212,212,220,223,228,236,239,244,252,231,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,103,151,204,252,247,244,236,231,228,220,220,212,207,204,196,191,188,180,180,172,167,164,156,156,148,143,140,132,127,124,116,116,108,103,100,92,92,84,79,76,68,68,60,55,52,47,44,36,36,28,28,23,20,20,20,20,20,28,28,31,36,39,44,52,52,60,63,68,71,76,84,84,92,95,100,108,111,116,124,124,132,135,140,148,148,156,159,164,172,175,180,188,188,196,199,204,212,212,220,223,228,236,239,244,252,228,180,127,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,247,247,244,236,231,228,220,220,212,207,204,196,196,188,183,180,172,167,164,156,156,148,143,140,132,132,124,119,116,108,108,100,95,92,84,84,76,71,68,60,60,52,52,44,44,36,36,28,28,28,23,23,28,28,28,31,36,39,44,47,52,55,60,68,68,76,76,84,87,92,100,100,108,111,116,124,124,132,135,140,148,151,156,164,164,172,175,180,188,188,196,199,204,212,215,220,228,228,236,239,244,252,220,172,124,76,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,191,244,247,244,236,236,228,223,220,212,207,204,196,196,188,183,180,172,172,164,159,156,148,148,140,135,132,124,119,116,111,108,100,100,92,87,84,76,76,68,63,60,55,52,47,44,44,36,36,31,28,28,28,28,31,36,36,39,44,44,52,52,60,60,68,71,76,79,84,92,92,100,103,108,116,116,124,127,132,140,140,148,151,156,164,164,172,175,180,188,191,196,204,204,212,215,220,228,228,236,239,244,252,215,167,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,135,188,236,247,244,236,236,228,223,220,212,212,204,199,196,188,188,180,175,172,164,159,156,151,148,140,135,132,124,124,116,111,108,103,100,92,92,84,79,76,71,68,63,60,55,52,47,44,44,39,36,36,36,36,36,36,36,39,44,44,52,52,60,60,68,68,76,76,84,87,92,95,100,108,108,116,119,124,132,132,140,143,148,156,156,164,167,172,180,180,188,191,196,204,207,212,215,220,228,231,236,244,244,252,212,159,111,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,79,127,180,228,252,244,239,236,228,228,220,215,212,204,199,196,188,188,180,175,172,164,164,156,151,148,140,140,132,127,124,119,116,108,108,100,95,92,87,84,76,76,68,68,60,60,55,52,47,44,44,44,39,39,39,39,44,44,44,47,52,52,55,60,63,68,71,76,79,84,92,92,100,100,108,111,116,124,124,132,135,140,148,148,156,159,164,172,172,180,183,188,196,196,204,207,212,220,220,228,231,236,244,247,252,204,156,103,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,71,119,172,220,252,244,239,236,228,228,220,215,212,204,204,196,191,188,180,180,172,167,164,156,156,148,143,140,132,132,124,119,116,111,108,100,100,92,92,84,84,76,76,68,68,60,60,55,52,52,47,44,44,44,44,44,44,47,52,52,52,60,60,63,68,71,76,79,84,87,92,95,100,108,108,116,116,124,127,132,140,140,148,151,156,159,164,172,175,180,183,188,196,199,204,212,212,220,223,228,236,236,244,247,244,196,143,95,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,159,207,252,244,244,236,231,228,220,220,212,207,204,196,196,188,183,180,172,172,164,159,156,148,148,140,135,132,127,124,116,116,108,108,100,95,92,87,84,79,76,71,68,68,60,60,60,52,52,52,52,52,52,52,52,52,52,55,60,60,63,68,68,76,76,84,84,92,92,100,100,108,111,116,119,124,132,132,140,143,148,151,156,164,167,172,175,180,188,188,196,199,204,212,215,220,223,228,236,239,244,252,236,183,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,247,244,236,236,228,223,220,212,212,204,199,196,188,188,180,175,172,164,164,156,151,148,143,140,132,132,124,119,116,111,108,103,100,95,92,87,84,79,76,71,68,68,63,60,60,60,55,55,52,52,52,55,55,60,60,60,68,68,71,76,76,84,84,92,92,100,100,108,108,116,116,124,127,132,135,140,148,148,156,159,164,167,172,180,180,188,191,196,204,204,212,215,220,228,228,236,239,244,252,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,247,244,239,236,228,223,220,215,212,204,199,196,191,188,180,180,172,167,164,156,156,148,148,140,135,132,127,124,116,116,108,108,100,100,92,92,84,84,79,76,76,68,68,68,63,60,60,60,60,60,60,60,60,63,68,68,68,71,76,76,84,84,87,92,95,100,103,108,111,116,119,124,132,132,140,140,148,151,156,164,164,172,175,180,183,188,196,196,204,207,212,220,220,228,231,236,244,244,252,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,252,244,239,236,228,228,220,215,212,207,204,196,196,188,183,180,172,172,164,159,156,151,148,140,140,132,132,124,124,116,116,108,108,100,100,92,92,84,84,79,76,76,71,68,68,68,68,63,63,63,63,68,68,68,68,71,76,76,79,84,84,87,92,95,100,103,108,111,116,119,124,127,132,135,140,148,148,156,156,164,167,172,175,180,188,188,196,199,204,212,212,220,223,228,236,236,244,247,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,252,244,244,236,231,228,223,220,212,207,204,199,196,188,188,180,175,172,167,164,156,156,148,148,140,135,132,127,124,119,116,111,108,103,100,100,92,92,87,84,84,79,76,76,76,71,68,68,68,68,68,68,71,71,76,76,76,79,84,84,92,92,95,100,100,108,108,116,116,124,124,132,132,140,143,148,151,156,159,164,172,172,180,183,188,191,196,204,204,212,215,220,228,228,236,239,244,252,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,247,244,236,236,228,223,220,215,212,204,204,196,191,188,183,180,172,172,164,159,156,151,148,143,140,135,132,124,124,119,116,111,108,103,100,100,92,92,87,84,84,84,79,76,76,76,76,76,76,76,76,76,76,76,79,84,84,87,92,92,95,100,100,108,108,116,116,124,124,132,132,140,140,148,148,156,156,164,167,172,175,180,188,188,196,199,204,207,212,220,220,228,231,236,244,244,252,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,252,244,239,236,231,228,220,220,212,207,204,196,196,188,188,180,175,172,167,164,156,156,148,148,140,140,132,132,124,124,116,116,111,108,103,100,100,95,92,92,87,84,84,84,84,79,79,79,79,79,79,84,84,84,84,87,92,92,92,100,100,103,108,108,116,116,119,124,127,132,135,140,143,148,151,156,164,164,172,172,180,180,188,191,196,204,204,212,212,220,223,228,236,236,244,247,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,252,244,244,236,231,228,223,220,212,212,204,199,196,191,188,183,180,172,172,164,164,156,156,148,143,140,140,132,132,124,124,116,116,111,108,108,100,100,100,95,92,92,92,87,84,84,84,84,84,84,84,84,84,87,92,92,92,95,100,100,103,108,108,116,116,119,124,127,132,135,140,140,148,151,156,159,164,167,172,175,180,188,188,196,196,204,207,212,215,220,228,228,236,239,244,252,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,239,247,244,239,236,228,228,220,215,212,207,204,196,196,188,188,180,175,172,167,164,159,156,151,148,143,140,135,132,132,124,124,116,116,111,108,108,103,100,100,100,95,92,92,92,92,92,92,92,92,92,92,92,92,92,95,100,100,100,108,108,111,116,116,119,124,127,132,132,140,140,148,148,156,156,164,164,172,172,180,183,188,191,196,199,204,212,212,220,223,228,231,236,244,244,252,215,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,175,220,252,244,244,236,231,228,223,220,212,212,204,199,196,191,188,183,180,175,172,164,164,156,156,151,148,143,140,135,132,132,124,124,119,116,116,111,108,108,103,100,100,100,100,95,95,92,92,92,92,92,95,95,100,100,100,100,108,108,108,111,116,116,124,124,127,132,132,140,140,148,148,151,156,159,164,167,172,180,180,188,188,196,196,204,207,212,215,220,228,228,236,236,244,247,244,196,151,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,156,204,247,247,244,236,236,228,228,220,215,212,207,204,199,196,188,188,180,180,172,172,164,164,156,156,148,148,143,140,135,132,132,124,124,119,116,116,111,108,108,108,103,103,100,100,100,100,100,100,100,100,100,100,100,103,108,108,108,111,116,116,119,124,124,127,132,132,140,140,143,148,151,156,159,164,167,172,175,180,183,188,191,196,204,204,212,212,220,223,228,231,236,244,244,252,228,180,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,135,180,228,252,244,239,236,231,228,223,220,212,212,204,204,196,196,188,183,180,175,172,167,164,164,156,156,148,148,143,140,135,132,132,127,124,124,119,116,116,116,111,108,108,108,108,108,103,103,103,103,103,108,108,108,108,108,111,116,116,116,119,124,124,132,132,135,140,140,143,148,151,156,159,164,164,172,172,180,180,188,191,196,199,204,207,212,215,220,228,228,236,236,244,247,252,204,159,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,164,204,252,247,244,236,236,228,228,220,215,212,207,204,199,196,191,188,183,180,175,172,167,164,159,156,156,148,148,143,140,140,132,132,127,124,124,124,119,116,116,116,111,111,108,108,108,108,108,108,108,108,111,111,116,116,116,116,119,124,124,127,132,132,135,140,140,148,148,151,156,159,164,164,172,172,180,180,188,188,196,196,204,204,212,212,220,223,228,231,236,239,244,252,228,183,140,92,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,95,140,183,228,252,244,244,236,231,228,223,220,215,212,204,204,196,196,188,188,180,180,175,172,167,164,159,156,156,148,148,143,140,140,135,132,132,127,124,124,124,119,119,116,116,116,116,116,116,116,116,116,116,116,116,116,119,124,124,124,127,132,132,132,140,140,140,148,148,151,156,159,164,164,172,172,180,180,183,188,191,196,199,204,212,212,220,220,228,228,236,236,244,247,252,207,164,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,116,164,204,252,247,244,239,236,228,228,220,220,212,212,204,204,196,196,188,188,180,180,172,172,167,164,159,156,156,151,148,148,140,140,140,135,132,132,127,124,124,124,124,124,119,119,119,119,119,119,119,119,124,124,124,124,124,127,132,132,132,135,140,140,143,148,148,151,156,159,164,164,172,172,175,180,183,188,191,196,199,204,207,212,215,220,223,228,231,236,244,244,252,228,183,140,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,92,140,180,228,252,244,244,236,236,228,223,220,215,212,207,204,199,196,196,188,188,180,180,172,172,167,164,164,156,156,151,148,148,143,140,140,140,135,132,132,132,127,127,124,124,124,124,124,124,124,124,124,124,124,127,132,132,132,132,135,140,140,143,148,148,151,156,156,159,164,164,172,172,175,180,183,188,191,196,196,204,204,212,212,220,220,228,231,236,239,244,247,247,204,159,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,156,204,244,247,244,239,236,231,228,223,220,215,212,207,204,199,196,191,188,188,180,180,175,172,167,164,164,159,156,156,151,148,148,143,140,140,140,135,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,132,135,140,140,140,140,148,148,148,151,156,156,159,164,164,172,172,175,180,183,188,188,196,196,204,204,212,212,220,220,228,228,236,236,244,244,252,220,180,135,92,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,132,175,220,252,247,244,236,236,228,228,220,220,212,212,207,204,199,196,191,188,188,180,180,175,172,172,164,164,159,156,156,156,148,148,148,143,140,140,140,140,140,135,135,132,132,132,132,132,132,135,135,135,140,140,140,140,143,148,148,148,151,156,156,159,164,164,167,172,172,180,180,183,188,188,196,196,204,204,212,212,215,220,223,228,231,236,239,244,252,236,196,156,111,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,148,191,231,252,244,244,236,236,228,228,220,220,212,212,204,204,199,196,191,188,188,180,180,175,172,172,167,164,164,159,156,156,156,151,148,148,148,143,143,140,140,140,140,140,140,140,140,140,140,140,140,140,143,148,148,148,148,151,156,156,156,164,164,164,167,172,172,180,180,183,188,188,196,196,204,204,207,212,215,220,223,228,231,236,239,244,247,252,212,172,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,79,124,164,204,244,247,244,239,236,231,228,223,220,220,212,212,204,204,199,196,191,188,188,183,180,180,172,172,172,164,164,164,159,156,156,156,151,148,148,148,148,148,148,143,143,143,143,143,143,143,148,148,148,148,148,151,151,156,156,156,159,164,164,167,172,172,175,180,180,183,188,191,196,196,204,204,207,212,215,220,223,228,228,236,236,244,244,252,228,183,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,135,175,215,252,247,244,239,236,231,228,223,220,220,212,212,204,204,199,196,196,188,188,183,180,180,175,172,172,167,164,164,164,159,156,156,156,156,151,151,148,148,148,148,148,148,148,148,148,148,151,151,156,156,156,156,159,164,164,164,167,172,172,172,180,180,180,188,188,191,196,196,204,204,207,212,215,220,220,228,228,236,236,244,244,252,236,196,156,116,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,148,188,228,252,244,244,239,236,231,228,223,220,220,212,212,204,204,199,196,196,191,188,188,180,180,180,175,172,172,167,164,164,164,164,159,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,159,159,164,164,164,167,172,172,172,175,180,180,183,188,188,191,196,196,204,204,207,212,215,220,220,228,228,236,236,239,244,247,244,207,167,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,116,156,196,236,252,244,244,236,236,231,228,223,220,220,212,212,207,204,204,196,196,191,188,188,183,180,180,180,175,172,172,172,167,164,164,164,164,164,159,159,159,159,156,156,159,159,159,159,164,164,164,164,164,167,167,172,172,172,175,180,180,183,188,188,191,196,196,199,204,204,212,212,215,220,220,228,228,236,236,239,244,247,252,215,175,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,47,87,124,164,204,244,252,244,244,236,236,231,228,223,220,220,212,212,207,204,204,199,196,196,191,188,188,183,180,180,180,175,172,172,172,172,167,167,164,164,164,164,164,164,164,164,164,164,164,164,164,167,172,172,172,172,175,175,180,180,180,188,188,188,191,196,196,199,204,204,212,212,215,220,220,228,228,231,236,239,244,247,252,220,183,148,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,132,172,207,244,247,244,244,236,236,231,228,223,220,220,215,212,212,204,204,199,196,196,196,188,188,188,183,180,180,180,180,175,172,172,172,172,172,172,172,172,167,167,167,172,172,172,172,172,172,172,175,175,180,180,180,183,188,188,188,191,196,196,199,204,204,207,212,212,215,220,223,228,228,236,236,239,244,247,252,228,188,151,116,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,63,100,140,175,212,247,247,244,244,236,236,231,228,228,220,220,215,212,212,207,204,204,199,196,196,196,188,188,188,188,183,180,180,180,180,180,175,175,172,172,172,172,172,172,172,172,175,175,175,180,180,180,180,180,183,188,188,188,191,196,196,196,204,204,204,212,212,212,220,220,223,228,228,236,236,239,244,247,252,228,196,156,119,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,140,180,212,247,247,244,244,236,236,231,228,228,223,220,220,212,212,212,204,204,204,199,196,196,196,191,188,188,188,188,183,183,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,183,183,188,188,188,188,191,196,196,196,199,204,204,207,212,212,215,220,220,223,228,228,236,236,239,244,247,252,231,196,159,124,84,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,143,180,212,247,252,244,244,239,236,236,228,228,223,220,220,215,212,212,207,204,204,204,199,196,196,196,196,191,188,188,188,188,188,188,183,183,183,183,183,183,183,183,188,188,188,188,188,188,188,191,196,196,196,199,204,204,204,207,212,212,212,220,220,220,228,228,231,236,236,239,244,247,252,228,196,159,124,92,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,175,212,244,252,244,244,239,236,236,231,228,228,220,220,220,215,212,212,207,204,204,204,204,199,196,196,196,196,191,191,191,188,188,188,188,188,188,188,188,188,188,188,191,191,196,196,196,196,196,199,204,204,204,207,212,212,212,215,220,220,223,228,228,231,236,236,244,244,247,252,228,196,159,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,172,204,236,252,247,244,244,236,236,231,228,228,223,220,220,220,215,212,212,212,207,204,204,204,204,199,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,199,199,204,204,204,204,207,212,212,212,215,220,220,223,228,228,231,236,236,239,244,244,247,252,220,188,156,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,68,103,135,167,199,231,252,247,244,244,239,236,236,231,228,228,223,220,220,220,215,212,212,212,207,207,204,204,204,204,204,199,199,199,199,196,196,196,196,199,199,199,199,204,204,204,204,204,204,207,212,212,212,212,215,220,220,223,228,228,228,231,236,236,239,244,244,252,244,215,183,151,119,84,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,68,100,132,164,191,220,252,252,244,244,239,236,236,236,228,228,228,223,220,220,220,215,215,212,212,212,212,207,207,204,204,204,204,204,204,204,204,204,204,204,204,204,204,207,207,212,212,212,212,212,215,220,220,220,223,228,228,228,231,236,236,239,244,244,247,252,236,207,175,148,116,84,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,92,124,151,180,212,239,252,247,244,244,239,236,236,236,228,228,228,223,223,220,220,220,215,215,212,212,212,212,212,212,212,212,207,207,207,207,212,212,212,212,212,212,212,212,215,215,220,220,220,220,223,228,228,228,231,236,236,236,244,244,244,252,252,228,196,167,140,108,76,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,111,140,172,196,228,252,252,244,244,244,239,236,236,236,231,228,228,228,223,223,220,220,220,220,220,215,215,215,212,212,212,212,212,212,212,212,212,215,215,215,220,220,220,220,220,223,228,228,228,228,231,236,236,236,239,244,244,247,252,236,212,183,156,127,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,71,100,132,156,183,212,236,252,247,244,244,244,239,236,236,236,231,228,228,228,228,228,223,223,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,223,223,228,228,228,228,231,236,236,236,236,244,244,244,247,252,247,220,196,172,140,116,84,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,87,116,140,164,191,215,239,252,247,244,244,244,239,236,236,236,236,231,231,228,228,228,228,228,228,223,223,223,223,223,223,223,223,223,223,228,228,228,228,228,228,228,231,236,236,236,236,239,244,244,244,247,252,252,228,204,180,156,127,100,76,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,71,100,124,148,172,196,220,239,252,252,247,244,244,244,239,236,236,236,236,236,231,231,231,228,228,228,228,228,228,228,228,228,228,228,228,228,231,231,236,236,236,236,236,239,244,244,244,244,247,252,252,228,207,183,159,135,111,84,60,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,55,79,108,127,151,172,196,215,236,252,252,247,244,244,244,244,244,239,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,239,239,244,244,244,244,247,252,252,244,228,204,183,164,140,116,92,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,84,108,127,148,172,188,207,228,244,252,252,247,247,244,244,244,244,244,244,239,239,239,239,236,236,236,236,236,239,239,239,239,244,244,244,244,244,244,247,252,252,252,236,215,196,180,159,140,116,95,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,103,124,143,164,180,196,212,228,239,252,252,252,247,247,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,247,247,252,252,252,244,236,220,204,188,172,151,132,116,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,60,76,95,116,132,148,164,180,191,204,215,228,236,247,252,252,252,252,252,247,247,247,247,247,247,252,252,252,252,252,252,244,236,220,212,196,188,172,156,140,124,108,87,68,47,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,52,68,84,100,116,132,143,156,167,180,188,196,204,212,220,223,228,231,236,236,236,236,236,231,228,220,215,212,204,196,183,172,164,148,140,124,108,92,76,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,68,84,95,108,119,132,140,148,156,164,172,175,180,180,188,188,188,188,183,180,180,172,167,159,156,143,135,124,116,100,92,76,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,47,60,71,84,92,100,108,116,119,124,132,132,135,135,140,135,132,132,127,124,116,111,103,95,84,76,68,52,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,52,60,68,71,76,79,84,84,87,87,84,84,84,76,76,68,60,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,28,28,36,36,36,36,36,36,31,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t FLARE_TEXTURE_5[16384] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,36,39,44,52,55,60,60,60,60,60,60,60,52,52,44,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,44,60,68,76,84,92,95,100,108,108,108,111,111,111,108,108,103,100,92,84,79,71,60,52,44,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,71,84,95,108,116,124,132,140,148,148,156,156,159,164,164,164,159,156,151,148,140,135,127,119,111,100,92,76,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,60,76,92,108,119,132,143,156,164,172,180,188,196,199,204,207,212,212,212,212,212,204,204,196,191,188,180,172,159,148,140,124,116,100,84,68,52,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,55,76,92,108,124,140,156,167,180,191,204,212,220,228,236,244,252,252,252,252,252,252,252,252,252,252,247,244,236,228,220,207,196,188,172,164,148,132,116,100,84,63,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,100,119,140,156,172,188,204,215,228,244,252,247,244,244,244,239,236,236,236,236,236,236,236,236,236,236,236,239,244,244,247,252,244,236,220,212,196,180,164,148,132,108,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,39,60,84,108,124,148,164,183,204,220,236,252,247,244,239,236,236,231,228,228,228,223,220,220,220,220,220,220,220,220,223,223,228,228,228,236,236,239,244,244,252,244,228,212,196,175,156,135,116,95,76,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,60,84,108,132,151,172,191,212,228,247,247,244,236,236,228,228,223,220,220,215,212,212,212,212,207,207,207,207,207,207,207,212,212,212,212,220,220,220,228,228,231,236,239,244,252,239,220,204,180,164,140,116,92,71,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,52,76,100,124,148,172,196,220,236,252,244,239,236,228,228,220,220,212,212,207,204,204,199,199,196,196,196,196,196,196,196,196,196,196,196,199,204,204,204,212,212,215,220,223,228,231,236,244,247,247,228,204,183,164,140,116,92,63,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,68,92,119,148,172,196,220,239,247,244,236,231,228,220,215,212,207,204,204,196,196,191,188,188,188,188,183,183,180,180,180,180,180,183,183,188,188,188,191,196,196,199,204,204,212,212,220,223,228,236,239,244,252,228,204,180,156,132,108,79,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,79,108,135,164,188,212,236,252,244,236,228,223,220,212,207,204,199,196,191,188,188,183,180,180,175,172,172,172,172,172,172,172,172,172,172,172,172,175,180,180,180,183,188,188,196,196,204,204,212,215,220,228,231,236,244,252,228,204,175,148,124,92,68,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,92,124,148,180,204,228,252,244,236,228,223,220,212,204,204,196,191,188,183,180,180,172,172,172,164,164,164,164,159,159,159,156,156,159,159,159,164,164,164,164,167,172,172,175,180,180,188,188,196,196,204,207,212,220,228,231,239,244,244,220,191,164,135,108,76,47,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,39,71,100,132,164,188,220,244,244,236,228,223,220,212,204,199,196,188,183,180,175,172,172,164,164,159,156,156,156,151,151,148,148,148,148,148,148,148,148,148,151,156,156,156,159,164,164,167,172,172,180,180,188,191,196,204,207,212,220,228,236,244,252,231,204,175,148,116,87,55,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,108,140,172,204,231,252,244,236,228,220,212,204,199,196,188,183,180,172,172,164,164,156,156,151,148,148,148,143,140,140,140,140,140,140,140,140,140,140,140,140,140,143,148,148,151,156,156,159,164,167,172,175,180,188,188,196,204,207,215,220,228,236,244,244,215,188,156,124,92,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,116,148,180,212,244,244,236,228,220,212,207,199,196,188,180,180,172,167,164,156,156,151,148,148,140,140,140,135,132,132,132,132,127,127,127,127,127,127,132,132,132,132,132,135,140,140,143,148,148,156,156,164,164,172,172,180,188,191,196,204,212,220,228,236,244,252,228,196,164,132,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,87,124,156,188,220,252,244,236,228,220,212,204,196,188,183,180,172,164,164,156,151,148,148,140,140,135,132,132,127,124,124,124,124,119,119,119,116,116,116,119,119,119,124,124,124,124,132,132,132,135,140,143,148,148,156,159,164,172,172,180,188,196,199,204,212,220,228,236,247,236,204,172,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,124,156,191,228,252,239,231,223,215,207,199,191,188,180,172,167,164,156,151,148,143,140,135,132,127,124,124,119,116,116,116,116,111,111,108,108,108,108,108,108,111,111,116,116,116,116,119,124,124,127,132,132,140,140,148,148,156,159,164,172,175,180,188,196,204,212,220,228,236,244,244,207,175,140,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,92,124,159,196,228,247,239,228,220,212,204,196,188,180,175,172,164,156,151,148,140,140,132,132,124,124,119,116,116,111,108,108,108,108,103,100,100,100,100,100,100,100,100,103,103,108,108,108,111,116,116,116,124,124,127,132,135,140,148,148,156,159,164,172,180,188,196,199,207,215,228,236,244,244,212,180,143,108,71,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,87,124,159,196,228,247,236,228,220,212,204,196,188,180,172,164,159,156,148,143,140,132,132,124,124,116,116,111,108,108,103,100,100,100,100,95,92,92,92,92,92,92,92,92,95,95,100,100,100,100,108,108,108,116,116,119,124,127,132,135,140,148,151,156,164,172,175,183,188,196,204,212,223,236,244,247,212,180,140,108,68,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,44,84,124,156,196,228,247,236,228,220,212,199,191,183,180,172,164,156,148,148,140,132,132,124,119,116,116,108,108,103,100,100,95,92,92,92,92,87,87,84,84,84,84,84,84,84,87,87,92,92,92,95,100,100,100,103,108,111,116,116,124,127,132,140,140,148,156,159,164,172,180,188,196,204,212,220,231,244,247,212,175,140,103,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,116,156,191,228,247,236,228,220,207,199,191,183,175,167,164,156,148,140,135,132,124,124,116,111,108,108,100,100,95,92,92,87,84,84,84,84,79,79,79,76,76,76,76,79,79,79,84,84,84,84,87,92,92,92,100,100,103,108,108,116,119,124,127,132,140,148,151,156,164,172,180,188,196,204,212,220,231,244,247,212,172,135,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,71,108,148,188,228,247,236,228,220,207,199,188,180,172,164,159,151,148,140,132,127,124,116,116,108,108,100,100,92,92,87,84,84,84,79,76,76,76,76,76,71,71,71,71,71,71,71,76,76,76,76,76,79,84,84,87,92,92,95,100,103,108,111,116,119,124,132,135,140,148,156,164,172,180,188,196,204,212,220,231,244,244,204,167,132,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,100,140,180,220,252,239,228,220,207,199,188,180,172,164,156,151,143,140,132,124,119,116,108,108,100,100,92,92,87,84,84,79,76,76,76,71,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,71,76,76,76,79,84,84,92,92,95,100,103,108,116,116,124,127,132,140,148,156,164,172,180,188,196,204,212,223,236,244,236,199,164,124,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,132,172,212,252,239,228,220,212,199,188,180,172,164,156,148,143,135,132,124,116,116,108,103,100,92,92,87,84,79,76,76,71,68,68,68,68,63,60,60,60,60,60,60,60,60,60,60,60,60,63,63,68,68,68,71,76,76,79,84,84,92,92,95,100,108,108,116,124,127,132,140,148,156,164,172,180,188,196,204,212,223,236,244,231,191,151,111,71,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,79,124,164,204,244,244,231,220,212,199,191,180,172,164,156,148,140,135,127,124,116,111,108,100,95,92,87,84,79,76,76,71,68,68,63,60,60,60,60,55,55,55,52,52,52,52,52,52,52,55,55,60,60,60,60,63,68,68,68,76,76,79,84,84,92,92,100,103,108,116,119,124,132,140,148,156,164,172,180,188,196,204,215,228,236,252,220,180,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,68,108,148,188,231,244,236,223,212,204,191,183,172,164,156,148,140,135,127,124,116,108,103,100,92,92,84,84,76,76,71,68,68,63,60,60,60,55,52,52,52,52,52,52,47,47,47,47,47,52,52,52,52,52,52,55,60,60,60,63,68,68,76,76,79,84,87,92,100,100,108,111,116,124,132,140,148,156,164,172,180,188,196,207,220,228,239,252,212,172,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,52,92,135,180,220,252,236,228,215,204,196,183,175,164,156,148,140,135,127,124,116,108,103,100,92,87,84,79,76,71,68,68,63,60,60,55,52,52,52,47,47,44,44,44,44,44,44,44,44,44,44,44,44,44,47,52,52,52,52,55,60,60,63,68,68,76,76,84,84,92,95,100,108,111,116,124,132,140,148,156,164,172,180,188,199,212,220,231,244,239,196,156,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,119,164,204,244,244,228,220,207,196,188,180,167,159,151,143,135,127,124,116,108,100,100,92,87,84,76,76,68,68,63,60,60,55,52,52,47,44,44,44,44,44,39,39,39,39,36,36,36,39,39,39,44,44,44,44,44,47,52,52,52,55,60,60,68,68,71,76,79,84,92,92,100,108,111,116,124,132,140,148,156,164,172,180,191,204,212,223,236,247,228,183,140,100,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,100,148,188,228,244,236,220,212,199,188,180,172,164,151,143,135,127,124,116,108,100,100,92,84,84,76,76,68,68,60,60,55,52,52,47,44,44,44,39,39,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,39,44,44,44,47,52,52,52,60,60,63,68,71,76,79,84,92,92,100,108,111,116,124,132,140,148,156,164,172,183,196,204,215,228,239,252,212,164,124,79,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,124,172,212,252,236,228,212,204,191,180,172,164,156,148,140,132,124,116,108,100,100,92,84,84,76,71,68,63,60,60,52,52,47,44,44,44,39,36,36,36,36,36,31,31,31,28,28,28,28,28,31,31,31,36,36,36,36,36,39,44,44,47,52,52,55,60,60,68,68,76,76,84,87,92,100,108,111,119,124,132,140,148,156,167,180,188,196,207,220,231,244,236,191,148,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,108,148,196,236,244,228,220,207,196,188,175,164,156,148,140,132,124,116,108,103,100,92,84,79,76,71,68,63,60,55,52,52,44,44,44,39,36,36,36,31,31,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,31,36,36,36,36,39,44,44,47,52,52,60,60,68,68,76,76,84,87,92,100,108,116,119,127,135,143,151,164,172,180,191,204,212,223,236,252,215,172,127,84,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,84,132,172,220,252,236,223,212,199,188,180,172,159,148,140,132,124,116,111,103,100,92,84,84,76,71,68,60,60,52,52,47,44,44,39,36,36,36,31,28,28,28,28,28,23,23,23,23,23,23,23,23,23,23,28,28,28,28,28,31,31,36,36,36,44,44,44,52,52,55,60,63,68,76,76,84,92,95,100,108,116,124,132,140,148,156,164,172,183,196,204,220,228,244,239,196,151,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,108,151,196,239,244,228,220,204,196,183,172,164,156,148,135,127,119,116,108,100,92,87,84,76,71,68,60,60,52,52,47,44,44,36,36,36,31,28,28,28,28,23,23,20,20,20,20,20,20,20,20,20,20,20,20,20,23,28,28,28,28,31,36,36,36,39,44,44,52,52,55,60,63,68,76,79,84,92,95,100,108,116,124,132,140,148,156,167,180,188,199,212,223,236,247,220,172,127,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,124,172,220,247,236,223,212,199,188,180,167,156,148,140,132,124,116,108,100,92,87,84,76,71,68,60,60,52,52,44,44,39,36,36,31,28,28,28,28,23,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,23,23,28,28,28,31,36,36,39,44,44,47,52,55,60,63,68,76,79,84,92,100,103,111,119,127,135,143,156,164,172,183,196,204,220,228,244,239,196,148,103,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,55,100,148,191,236,244,228,220,204,196,180,172,164,151,143,132,124,116,108,103,95,92,84,76,76,68,63,60,52,52,44,44,39,36,36,31,28,28,28,23,20,20,20,20,20,15,15,15,15,12,12,12,12,12,15,15,15,20,20,20,20,20,23,23,28,28,28,36,36,36,44,44,47,52,55,60,68,68,76,84,87,92,100,108,116,124,132,140,148,156,167,180,188,199,212,223,236,252,215,172,124,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,119,164,212,252,236,223,212,199,188,180,164,156,148,140,132,124,116,108,100,92,84,79,76,68,63,60,52,52,44,44,39,36,36,31,28,28,23,20,20,20,20,20,15,15,12,12,12,12,12,12,12,12,12,12,12,12,12,15,15,20,20,20,20,23,28,28,28,31,36,36,44,44,47,52,60,60,68,71,76,84,92,95,100,108,116,124,132,140,151,164,172,180,196,204,220,228,244,236,188,143,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,183,228,244,231,220,204,196,183,172,164,151,140,132,124,116,108,100,92,87,84,76,68,68,60,55,52,47,44,39,36,36,31,28,28,23,20,20,20,20,15,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,15,15,20,20,20,20,28,28,28,31,36,36,44,44,52,52,60,60,68,76,79,84,92,100,108,116,124,132,140,148,156,167,180,188,199,212,223,236,252,207,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,247,239,228,212,204,188,180,167,156,148,140,132,119,111,108,100,92,84,76,71,68,60,60,52,47,44,39,36,36,31,28,28,23,20,20,20,15,12,12,12,12,12,12,12,7,7,7,7,7,7,7,7,12,12,12,12,12,12,12,15,15,20,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,84,87,92,100,108,116,124,132,140,151,164,172,183,196,207,220,231,244,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,247,236,220,207,196,183,172,164,151,143,132,124,116,108,100,92,87,79,76,68,63,60,52,52,44,44,36,36,31,28,28,23,20,20,20,15,12,12,12,12,12,7,7,7,7,0,0,0,0,0,0,0,7,7,7,12,12,12,12,12,12,15,20,20,20,23,28,28,31,36,39,44,47,52,55,60,68,71,76,84,92,100,108,111,124,132,140,148,156,167,180,188,204,212,228,239,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,244,228,215,204,191,180,172,156,148,140,132,124,116,108,100,92,84,76,71,68,60,55,52,44,44,36,36,31,28,28,23,20,20,20,15,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,20,20,20,23,28,28,36,36,39,44,47,52,60,63,68,76,79,87,92,100,108,116,124,132,143,156,164,172,188,196,212,220,236,252,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,156,204,252,236,228,212,199,188,175,164,156,148,135,124,116,108,100,92,87,79,76,68,63,60,52,47,44,39,36,31,28,28,23,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,28,28,31,36,36,44,44,52,55,60,68,71,76,84,92,100,108,116,124,132,140,148,159,172,180,196,204,220,231,244,228,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,71,119,167,215,247,236,220,207,196,183,172,164,151,140,132,124,116,108,100,92,84,76,71,68,60,55,52,44,44,36,36,28,28,23,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,23,28,28,31,36,39,44,47,52,60,63,68,76,84,87,95,100,108,116,127,135,148,156,167,180,188,204,212,228,244,239,191,143,95,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,244,228,220,204,191,180,172,156,148,140,127,119,111,103,95,87,84,76,68,63,60,52,47,44,39,36,31,28,28,20,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,28,36,36,44,44,52,55,60,68,71,76,84,92,100,108,116,124,132,143,156,164,172,188,196,212,223,236,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,143,191,244,239,228,212,204,188,180,164,156,148,135,124,116,108,100,92,84,79,71,68,60,55,52,44,44,36,36,28,28,23,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,20,28,28,31,36,39,44,47,52,60,63,68,76,84,92,95,103,111,124,132,140,148,159,172,183,196,207,220,236,247,215,167,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,252,236,223,212,196,188,172,164,151,140,132,124,116,108,100,92,84,76,68,63,60,52,47,44,39,36,31,28,28,20,20,20,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,36,36,44,44,52,55,60,68,76,79,87,92,100,108,116,127,140,148,156,167,180,191,204,220,228,244,228,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,247,236,220,207,196,183,172,159,148,140,132,119,111,103,95,87,84,76,68,60,60,52,44,44,36,36,28,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,28,28,31,36,39,44,52,52,60,68,71,76,84,92,100,108,116,124,132,143,156,164,180,188,204,212,228,244,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,244,231,220,204,191,180,172,156,148,140,127,116,108,100,92,84,79,76,68,60,55,52,44,39,36,31,28,28,20,20,20,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,28,36,36,44,47,52,60,63,68,76,84,92,100,108,116,124,132,140,151,164,172,188,196,212,228,236,247,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,244,228,215,204,188,180,164,156,148,135,124,116,108,100,92,84,76,71,68,60,52,47,44,39,36,31,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,20,20,20,28,28,36,36,44,44,52,55,60,68,76,84,87,95,103,111,124,132,140,148,164,172,183,196,212,220,236,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,92,140,188,236,244,228,212,199,188,175,164,156,143,132,124,116,108,100,92,84,76,68,63,60,52,47,44,36,36,28,28,23,20,20,15,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,39,44,52,55,60,68,76,79,87,92,100,108,119,127,140,148,159,172,180,196,207,220,236,247,212,164,116,68,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,95,148,196,244,239,228,212,199,188,172,164,151,140,132,124,116,108,100,92,84,76,68,60,55,52,44,44,36,36,28,28,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,31,36,39,44,47,52,60,68,71,76,84,92,100,108,116,127,140,148,156,172,180,191,204,220,231,244,220,172,119,71,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,199,252,236,223,212,196,188,172,164,151,140,132,124,111,103,95,87,79,76,68,60,55,52,44,39,36,31,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,20,20,23,28,28,36,36,44,47,52,60,63,68,76,84,92,100,108,116,124,135,148,156,167,180,191,204,220,228,244,223,175,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,108,156,204,252,236,220,212,196,183,172,159,148,140,132,119,111,100,92,87,79,76,68,60,55,52,44,39,36,31,28,23,20,20,15,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,36,36,44,47,52,60,63,68,76,84,92,100,108,116,124,132,143,156,164,180,188,204,215,228,244,228,180,132,79,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,207,252,236,220,207,196,183,172,159,148,140,127,119,108,100,92,84,79,71,68,60,52,52,44,39,36,31,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,36,36,44,44,52,60,63,68,76,84,92,100,108,116,124,132,143,156,164,180,188,204,212,228,244,231,180,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,252,236,220,207,196,180,172,159,148,140,127,119,108,100,92,84,76,71,68,60,52,47,44,39,36,28,28,23,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,44,44,52,55,60,68,76,84,92,100,108,116,124,132,143,156,164,175,188,204,212,228,244,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,236,220,207,196,180,172,156,148,140,127,116,108,100,92,84,76,71,68,60,52,47,44,36,36,28,28,23,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,44,44,52,55,60,68,76,84,92,100,108,116,124,132,140,156,164,175,188,199,212,228,244,236,188,135,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,236,220,207,196,180,172,156,148,140,127,116,108,100,92,84,76,71,68,60,52,47,44,36,36,28,28,23,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,31,36,44,44,52,55,60,68,76,84,92,100,108,116,124,132,140,156,164,175,188,199,212,228,244,236,188,140,87,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,164,212,252,236,220,207,196,180,172,159,148,140,127,116,108,100,92,84,76,71,68,60,52,47,44,36,36,28,28,23,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,44,44,52,55,60,68,76,84,92,100,108,116,124,132,143,156,164,175,188,199,212,228,244,236,188,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,159,212,252,236,220,207,196,180,172,159,148,140,127,119,108,100,92,84,79,71,68,60,52,47,44,39,36,28,28,23,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,44,44,52,55,60,68,76,84,92,100,108,116,124,132,143,156,164,175,188,204,212,228,244,236,183,132,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,60,108,156,204,252,236,220,207,196,183,172,159,148,140,132,119,111,100,92,84,79,71,68,60,52,52,44,39,36,31,28,23,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,36,36,44,44,52,60,63,68,76,84,92,100,108,116,124,132,143,156,164,180,188,204,215,228,244,231,180,132,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,103,151,204,252,236,223,212,196,183,172,164,148,140,132,119,111,103,95,87,79,76,68,60,55,52,44,39,36,31,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,20,20,23,28,28,36,36,44,47,52,60,63,68,76,84,92,100,108,116,124,135,148,156,164,180,188,204,215,228,244,228,180,127,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,247,236,223,212,196,188,172,164,151,140,132,124,116,103,95,87,84,76,68,60,55,52,44,44,36,31,28,28,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,20,23,28,28,36,36,44,47,52,60,68,71,76,84,92,100,108,116,124,135,148,156,167,180,191,204,220,231,244,220,172,124,76,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,191,244,239,228,212,199,188,175,164,156,140,132,124,116,108,100,92,84,76,68,63,60,52,44,44,36,36,28,28,20,20,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,31,36,39,44,52,52,60,68,71,79,84,92,100,108,116,127,140,148,156,172,180,196,204,220,236,247,215,167,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,135,188,236,244,228,212,204,188,180,164,156,143,132,124,116,108,100,92,84,76,68,63,60,52,47,44,36,36,28,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,28,28,31,36,44,44,52,55,60,68,76,79,87,95,103,111,119,132,140,148,159,172,183,196,207,220,236,252,212,159,111,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,79,127,180,228,244,228,220,204,191,180,167,156,148,135,124,116,108,100,92,84,76,71,68,60,52,52,44,39,36,31,28,28,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,28,36,36,44,44,52,60,60,68,76,84,92,100,108,116,124,132,140,151,164,172,188,196,212,223,236,252,204,156,103,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,71,119,172,220,247,236,220,204,196,180,172,159,148,140,132,119,111,100,95,87,79,76,68,60,55,52,44,44,36,36,28,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,31,36,39,44,47,52,60,63,68,76,84,92,100,108,116,124,132,143,156,164,175,188,199,212,228,239,244,196,143,95,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,111,159,207,252,236,220,212,196,183,172,164,151,140,132,124,116,108,100,92,84,76,68,63,60,52,47,44,36,36,31,28,23,20,20,15,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,15,20,20,23,28,28,31,36,44,44,52,55,60,68,71,79,84,92,100,108,116,124,135,148,156,167,180,188,204,215,228,244,236,183,135,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,239,228,212,199,188,175,164,156,143,132,124,116,108,100,92,84,76,71,68,60,52,52,44,39,36,31,28,28,23,20,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,23,28,31,36,36,44,47,52,60,60,68,76,84,87,95,103,111,119,132,140,148,159,172,180,196,204,220,231,244,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,140,188,236,244,228,215,204,188,180,167,156,148,135,127,116,108,100,92,87,79,76,68,60,55,52,47,44,36,36,31,28,23,20,20,15,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,23,28,28,31,36,39,44,52,52,60,68,68,76,84,92,100,108,116,124,132,140,151,164,172,183,196,212,220,236,252,212,164,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,124,172,220,244,231,220,204,196,180,172,159,148,140,132,124,116,103,100,92,84,76,68,63,60,52,52,44,39,36,36,28,28,23,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,15,20,20,20,28,28,31,36,36,44,44,52,55,60,68,76,79,84,92,100,108,116,124,135,143,156,164,175,188,199,212,228,239,244,196,148,100,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,252,236,223,212,196,188,172,164,156,143,132,124,116,108,100,92,84,79,76,68,60,55,52,47,44,36,36,31,28,28,20,20,20,15,12,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,15,20,20,20,23,28,28,36,36,39,44,52,52,60,63,68,76,84,92,95,103,111,119,132,140,148,156,172,180,191,204,215,228,244,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,244,239,228,212,204,188,180,167,156,148,140,127,119,111,103,95,92,84,76,68,63,60,52,52,44,44,36,36,28,28,28,20,20,20,15,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,15,20,20,20,23,28,28,31,36,39,44,47,52,55,60,68,76,79,84,92,100,108,116,124,132,140,151,164,172,183,196,207,220,236,247,220,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,180,228,244,231,220,204,196,180,172,164,148,140,132,124,116,108,100,92,84,79,76,68,60,60,52,47,44,39,36,36,28,28,23,20,20,20,15,12,12,12,12,12,7,7,7,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,52,52,60,63,68,76,84,87,95,103,108,119,127,135,148,156,164,180,188,199,212,228,236,252,204,156,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,68,116,164,212,252,236,223,212,196,188,175,164,156,148,135,127,116,108,103,95,92,84,76,68,68,60,55,52,44,44,39,36,31,28,28,23,20,20,20,15,15,12,12,12,12,12,12,7,7,7,7,7,7,7,7,7,7,7,12,12,12,12,12,12,15,20,20,20,23,28,28,31,36,36,44,44,47,52,60,60,68,76,79,84,92,100,108,116,124,132,140,148,159,172,180,191,204,220,228,244,236,188,140,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,100,148,196,239,244,228,215,204,191,180,172,159,148,140,132,124,116,108,100,92,84,79,76,68,63,60,52,52,44,44,36,36,31,28,28,23,20,20,20,20,15,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,15,15,20,20,20,23,28,28,31,36,36,39,44,47,52,55,60,68,71,76,84,92,95,103,108,116,127,135,148,156,164,175,188,196,212,220,236,247,215,172,124,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,84,132,175,220,247,236,220,207,196,188,172,164,156,148,135,127,119,111,103,95,92,84,76,71,68,60,60,52,52,44,44,36,36,31,28,28,28,23,20,20,20,15,15,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,15,15,20,20,20,20,23,28,28,31,36,36,39,44,47,52,55,60,63,68,76,84,87,92,100,108,116,124,132,140,148,159,172,180,191,204,212,228,239,244,196,151,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,156,204,247,239,228,212,204,188,180,172,159,148,140,132,124,116,108,100,92,87,84,76,71,68,60,55,52,47,44,44,36,36,31,28,28,28,23,20,20,20,20,20,15,15,12,12,12,12,12,12,12,12,12,12,15,15,15,20,20,20,20,23,28,28,28,31,36,36,39,44,44,52,52,60,63,68,76,79,84,92,100,103,111,119,127,135,148,156,164,172,188,196,207,220,236,244,228,180,132,87,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,135,180,228,244,231,220,207,196,188,172,164,156,148,140,127,119,116,108,100,92,84,79,76,68,68,60,55,52,47,44,44,36,36,36,31,28,28,28,23,20,20,20,20,20,20,15,15,15,15,15,15,15,15,20,20,20,20,20,20,23,23,28,28,28,31,36,36,39,44,44,52,52,60,60,68,71,76,84,92,95,100,108,116,124,132,140,148,159,172,180,188,204,212,228,239,252,204,159,116,68,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,164,204,252,236,228,212,204,191,180,172,159,151,140,132,124,116,108,103,100,92,84,79,76,68,63,60,55,52,47,44,44,39,36,36,31,28,28,28,28,23,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,23,23,28,28,28,31,36,36,36,44,44,47,52,52,60,60,68,71,76,84,87,92,100,108,116,124,132,140,148,156,164,175,188,196,207,220,231,244,228,183,140,92,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,95,140,183,228,244,231,220,207,196,188,175,164,156,148,140,132,124,116,108,100,95,92,84,76,76,68,63,60,55,52,52,44,44,39,36,36,36,31,28,28,28,28,28,23,23,20,20,20,20,20,20,20,20,23,23,23,28,28,28,28,31,31,36,36,39,44,44,47,52,52,60,60,68,71,76,84,84,92,100,108,111,119,127,135,143,151,164,172,180,191,204,212,228,239,252,207,164,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,116,164,204,252,239,228,215,204,196,180,172,164,156,148,135,127,124,116,108,100,92,92,84,76,76,68,63,60,60,52,52,47,44,44,39,36,36,36,31,28,28,28,28,28,28,28,28,28,23,28,28,28,28,28,28,28,28,31,31,36,36,36,39,44,44,47,52,55,60,60,68,71,76,84,84,92,100,103,108,116,124,132,140,148,156,167,180,188,196,212,220,236,244,228,183,140,95,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,92,140,180,228,244,236,220,212,199,188,180,172,159,151,140,132,127,119,111,108,100,92,87,84,76,76,68,68,60,60,52,52,47,44,44,44,39,36,36,36,36,31,31,28,28,28,28,28,28,28,28,28,28,28,31,31,36,36,36,36,39,44,44,47,52,52,55,60,63,68,71,76,84,84,92,100,103,108,116,124,132,140,148,156,164,172,183,196,204,215,228,239,247,204,159,116,71,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,116,156,204,244,244,228,220,204,196,188,175,164,156,148,140,132,124,116,111,108,100,92,87,84,79,76,68,68,60,60,55,52,52,47,44,44,44,39,36,36,36,36,36,36,36,31,31,31,31,31,36,36,36,36,36,36,39,44,44,44,44,52,52,52,60,60,63,68,71,76,84,84,92,100,100,108,116,124,132,140,148,156,164,172,180,188,204,212,223,236,247,220,180,135,92,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,92,132,175,220,252,236,228,212,204,196,183,172,164,156,148,140,132,124,116,111,108,100,92,92,84,79,76,71,68,63,60,60,55,52,52,47,44,44,44,44,39,39,36,36,36,36,36,36,36,36,36,36,36,39,44,44,44,44,47,52,52,52,55,60,60,68,68,76,76,84,84,92,100,100,108,116,124,127,135,143,151,159,172,180,188,196,207,220,231,244,236,196,156,111,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,148,191,231,244,236,220,212,199,188,180,172,164,156,148,140,132,124,116,111,108,100,95,92,84,84,76,76,68,68,63,60,60,55,52,52,52,47,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,47,52,52,52,55,60,60,63,68,71,76,79,84,87,92,100,103,108,116,124,127,135,140,148,156,167,175,188,196,204,215,228,239,252,212,172,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,79,124,164,204,244,244,228,220,207,196,188,180,172,164,156,148,140,132,124,116,111,108,100,95,92,87,84,79,76,71,68,68,63,60,60,55,52,52,52,52,47,47,47,44,44,44,44,44,44,44,47,47,52,52,52,52,55,60,60,60,63,68,68,76,76,84,84,92,92,100,103,108,116,124,127,132,140,148,156,164,172,183,196,204,212,223,236,247,228,183,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,135,175,215,252,236,228,215,204,196,188,180,172,164,156,148,140,132,124,119,116,108,100,100,92,92,84,84,76,76,71,68,68,63,60,60,60,55,55,52,52,52,52,52,52,52,52,52,52,52,52,52,55,60,60,60,60,68,68,68,76,76,79,84,87,92,95,100,108,108,116,124,127,135,140,148,156,164,172,180,191,204,212,220,236,244,236,196,156,116,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,148,188,228,247,236,228,212,204,196,188,180,172,164,156,148,140,132,124,119,116,108,103,100,95,92,87,84,79,76,76,71,68,68,68,63,60,60,60,60,60,60,55,55,55,55,55,60,60,60,60,60,60,63,68,68,68,76,76,79,84,84,92,92,100,100,108,111,116,124,132,135,140,148,156,164,172,180,188,199,212,220,228,244,244,207,167,127,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,76,116,156,196,236,244,236,223,212,204,196,188,180,172,164,156,148,140,132,127,124,116,111,108,100,100,92,92,87,84,84,76,76,76,71,68,68,68,68,63,63,63,60,60,60,60,60,63,63,68,68,68,68,68,71,76,76,79,84,84,87,92,95,100,103,108,116,119,124,132,140,143,148,156,164,172,180,188,199,207,220,228,239,252,215,175,140,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,47,87,124,164,204,244,244,236,220,212,204,196,188,180,172,164,156,148,140,135,132,124,119,116,108,108,100,100,92,92,87,84,84,79,76,76,76,76,71,68,68,68,68,68,68,68,68,68,68,71,71,76,76,76,79,84,84,84,92,92,95,100,103,108,111,116,124,127,132,140,148,151,156,164,172,180,188,199,207,220,228,236,252,220,183,148,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,92,132,172,207,244,244,231,220,212,204,196,188,180,172,164,156,148,143,140,132,127,124,116,116,108,108,100,100,95,92,92,87,84,84,84,79,76,76,76,76,76,76,76,76,76,76,76,76,79,79,84,84,84,87,92,92,95,100,103,108,108,116,119,124,132,135,140,148,156,159,167,172,180,188,199,207,220,228,236,247,228,188,151,116,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,63,100,140,175,212,247,244,231,220,212,204,196,188,180,172,164,156,151,148,140,135,132,124,124,116,111,108,108,100,100,95,92,92,92,87,87,84,84,84,84,84,84,84,84,84,84,84,84,84,87,92,92,92,95,100,100,103,108,108,116,116,124,127,132,140,143,148,156,164,172,175,183,191,199,207,220,228,236,247,228,196,156,119,84,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,108,140,180,212,247,244,231,223,212,204,196,188,180,172,167,164,156,148,143,140,132,132,124,124,116,116,108,108,103,100,100,100,95,92,92,92,92,92,92,92,92,92,92,92,92,92,92,95,100,100,100,103,108,108,111,116,119,124,127,132,135,140,148,151,156,164,172,180,188,196,204,212,220,228,236,247,231,196,159,124,84,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,143,180,212,247,244,236,223,215,207,199,191,183,180,172,164,156,156,148,140,140,132,132,124,124,116,116,111,108,108,108,103,100,100,100,100,100,100,100,100,100,100,100,100,100,100,103,108,108,108,111,116,116,119,124,127,132,135,140,148,148,156,164,167,172,180,188,196,204,212,220,228,236,247,228,196,159,124,92,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,175,212,244,244,236,228,220,212,204,196,188,180,172,167,164,156,151,148,140,140,132,132,127,124,124,116,116,116,111,108,108,108,108,108,108,108,108,108,108,108,108,108,108,111,116,116,116,119,124,124,132,132,135,140,148,148,156,159,164,172,180,183,188,196,204,212,220,228,239,252,228,196,159,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,108,140,172,204,236,244,236,228,220,212,204,196,191,183,180,172,167,164,156,151,148,143,140,135,132,132,127,124,124,124,119,116,116,116,116,116,116,116,116,116,116,116,116,116,119,124,124,124,132,132,135,140,140,148,148,156,159,164,172,175,180,188,196,204,207,215,223,236,244,252,220,188,156,124,92,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,68,103,135,167,199,231,252,239,231,223,215,207,204,196,188,183,180,172,167,164,156,156,148,148,143,140,140,132,132,132,127,127,124,124,124,124,124,124,124,124,124,124,124,127,132,132,132,135,140,140,143,148,151,156,159,164,172,172,180,188,191,196,204,212,220,228,236,244,244,215,183,151,119,84,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,68,100,132,164,191,220,252,244,236,228,220,212,204,199,196,188,180,180,172,167,164,159,156,156,148,148,143,140,140,140,140,135,132,132,132,132,132,132,132,132,135,135,140,140,140,143,148,148,151,156,156,164,164,172,175,180,188,188,196,204,212,215,223,231,239,247,236,207,175,148,116,84,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,92,124,151,180,212,239,247,239,231,223,220,212,204,199,196,188,183,180,172,172,167,164,159,156,156,151,148,148,148,148,143,143,143,140,140,143,143,143,148,148,148,148,151,156,156,159,164,164,172,172,180,180,188,191,196,204,207,212,220,228,236,244,252,228,196,167,140,108,76,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,84,111,140,172,196,228,252,244,236,228,223,220,212,204,199,196,188,188,180,180,172,172,167,164,164,164,159,156,156,156,156,156,156,156,156,156,156,156,156,156,159,164,164,167,172,172,175,180,183,188,191,196,204,207,212,220,228,236,239,247,236,212,183,156,127,100,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,71,100,132,156,183,212,236,252,244,236,228,223,220,212,207,204,196,196,188,188,183,180,180,172,172,172,172,167,164,164,164,164,164,164,164,164,164,167,172,172,172,175,180,180,183,188,191,196,199,204,212,212,220,228,231,239,244,247,220,196,172,140,116,84,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,87,116,140,164,191,215,239,247,244,236,228,223,220,212,212,204,204,196,196,191,188,188,183,180,180,180,180,180,175,175,175,175,175,180,180,180,180,183,188,188,188,196,196,199,204,207,212,220,220,228,236,239,244,252,228,204,180,156,127,100,76,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,71,100,124,148,172,196,220,239,252,244,236,231,228,220,220,212,212,207,204,204,196,196,196,191,191,188,188,188,188,188,188,188,188,188,191,196,196,196,199,204,204,212,212,215,220,228,228,236,239,244,252,228,207,183,159,135,111,84,60,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,55,79,108,127,151,172,196,215,236,252,244,239,236,231,228,223,220,220,212,212,212,207,204,204,204,204,204,199,199,199,204,204,204,204,204,207,212,212,215,220,220,228,228,236,236,244,247,244,228,204,183,164,140,116,92,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,84,108,127,148,172,188,207,228,244,252,244,244,236,236,228,228,228,220,220,220,220,215,212,212,212,212,212,212,215,215,220,220,220,223,228,228,231,236,239,244,247,252,236,215,196,180,159,140,116,95,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,60,84,103,124,143,164,180,196,212,228,239,252,247,244,244,236,236,236,231,228,228,228,228,228,228,228,228,228,228,231,236,236,236,239,244,244,252,244,236,220,204,188,172,151,132,116,92,71,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,60,76,95,116,132,148,164,180,191,204,215,228,236,247,252,247,244,244,244,244,244,244,244,244,244,244,244,244,247,252,252,244,236,220,212,196,188,172,156,140,124,108,87,68,47,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,52,68,84,100,116,132,143,156,167,180,188,196,204,212,220,223,228,231,236,236,236,236,236,231,228,220,215,212,204,196,183,172,164,148,140,124,108,92,76,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,68,84,95,108,119,132,140,148,156,164,172,175,180,180,188,188,188,188,183,180,180,172,167,159,156,143,135,124,116,100,92,76,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,47,60,71,84,92,100,108,116,119,124,132,132,135,135,140,135,132,132,127,124,116,111,103,95,84,76,68,52,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,52,60,68,71,76,79,84,84,87,87,84,84,84,76,76,68,60,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,28,28,36,36,36,36,36,36,31,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t * FLARE_TEXTURES[6] ={ + FLARE_TEXTURE_0, + FLARE_TEXTURE_1, + FLARE_TEXTURE_2, + FLARE_TEXTURE_3, + FLARE_TEXTURE_4, + FLARE_TEXTURE_5, + }; + const int FLARE_TEXTURE_WIDTHS[10] = { + 128, + 128, + 128, + 128, + 128, + 128, + }; + const int FLARE_TEXTURE_HEIGHTS[10] = { + 128, + 128, + 128, + 128, + 128, + 128, + }; + } +} +#endif diff --git a/src/igl/opengl2/gl.h b/src/igl/opengl2/gl.h new file mode 100644 index 000000000..7147c9289 --- /dev/null +++ b/src/igl/opengl2/gl.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_GL_H +#define IGL_OPENGL2_GL_H + +#ifdef IGL_OPENGL_GL_H +# error "igl/opengl/gl.h already included" +#endif + +// Always use this: +// #include "gl.h" +// Instead of: +// #include +// or +// #include +// + +// For now this includes glu, glew and glext (perhaps these should be +// separated) +#ifdef _WIN32 +# define NOMINMAX +# include +# undef DrawText +# undef NOMINMAX +#endif + +#ifndef __APPLE__ +# define GLEW_STATIC +# include +#endif + +#ifdef __APPLE__ +# include +# define __gl_h_ /* Prevent inclusion of the old gl.h */ +#else +# include +#endif + +#endif diff --git a/src/igl/opengl2/glext.h b/src/igl/opengl2/glext.h new file mode 100644 index 000000000..78ff45fea --- /dev/null +++ b/src/igl/opengl2/glext.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL_GLEXT_H +#define IGL_OPENGL_GLEXT_H + +#ifdef IGL_OPENGL2_GLEXT_H +# error "igl/opengl2/glext.h already included" +#endif + +// Always use this: +// #include "gl.h" +// Instead of: +// #include +// or +// #include +// + +#ifdef __APPLE__ +# include +#elif _WIN32 +// do nothing(?) +#else +# include +#endif + +#endif + diff --git a/src/igl/opengl2/glu.h b/src/igl/opengl2/glu.h new file mode 100644 index 000000000..d4ab5e16c --- /dev/null +++ b/src/igl/opengl2/glu.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_GLU_H +#define IGL_OPENGL2_GLU_H + +#ifdef IGL_OPENGL_GLU_H +# error "igl/opengl/glu.h already included" +#endif + +// Always use this: +// #include "glu.h" +// Instead of: +// #include +// or +// #include +// + +#ifdef __APPLE__ +# include +#else +# include +#endif + +#endif + diff --git a/src/igl/opengl2/lens_flare.cpp b/src/igl/opengl2/lens_flare.cpp new file mode 100644 index 000000000..c99f634c8 --- /dev/null +++ b/src/igl/opengl2/lens_flare.cpp @@ -0,0 +1,195 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "lens_flare.h" + +#include "../C_STR.h" +#include "unproject.h" +#include "project.h" +#include "shine_textures.h" +#include "flare_textures.h" + +#include +#include + +// http://www.opengl.org/archives/resources/features/KilgardTechniques/LensFlare/glflare.c + +IGL_INLINE void igl::opengl2::lens_flare_load_textures( + std::vector & shine_id, + std::vector & flare_id) +{ + + const auto setup_texture =[]( + const uint8_t * texture, + const int width, + const int height, + GLuint texobj, + GLenum minFilter, GLenum maxFilter) + { + glBindTexture(GL_TEXTURE_2D, texobj); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilter); + glTexImage2D(GL_TEXTURE_2D, 0, 1, width, height, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, texture); + }; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + shine_id.resize(10); + glGenTextures(10,&shine_id[0]); + for (int i = 0; i < (int)shine_id.size(); i++) { + setup_texture( + SHINE_TEXTURES[i], + SHINE_TEXTURE_WIDTHS[i], + SHINE_TEXTURE_HEIGHTS[i], + shine_id[i], GL_LINEAR, GL_LINEAR); + } + flare_id.resize(6); + glGenTextures(6,&flare_id[0]); + for (int i = 0; i < (int)flare_id.size(); i++) { + setup_texture( + FLARE_TEXTURES[i], + FLARE_TEXTURE_WIDTHS[i], + FLARE_TEXTURE_HEIGHTS[i], + flare_id[i], GL_LINEAR, GL_LINEAR); + } +} + +IGL_INLINE void igl::opengl2::lens_flare_create( + const float * A, + const float * B, + const float * C, + std::vector & flares) +{ + using namespace std; + flares.resize(12); + /* Shines */ + flares[0] = Flare(-1, 1.0f, 0.1f, C, 1.0); + flares[1] = Flare(-1, 1.0f, 0.15f, B, 1.0); + flares[2] = Flare(-1, 1.0f, 0.35f, A, 1.0); + + /* Flares */ + flares[3] = Flare(2, 1.3f, 0.04f, A, 0.6); + flares[4] = Flare(3, 1.0f, 0.1f, A, 0.4); + flares[5] = Flare(1, 0.5f, 0.2f, A, 0.3); + flares[6] = Flare(3, 0.2f, 0.05f, A, 0.3); + flares[7] = Flare(0, 0.0f, 0.04f, A, 0.3); + flares[8] = Flare(5, -0.25f, 0.07f, A, 0.5); + flares[9] = Flare(5, -0.4f, 0.02f, A, 0.6); + flares[10] = Flare(5, -0.6f, 0.04f, A, 0.4); + flares[11] = Flare(5, -1.0f, 0.03f, A, 0.2); +} + +IGL_INLINE void igl::opengl2::lens_flare_draw( + const std::vector & flares, + const std::vector & shine_ids, + const std::vector & flare_ids, + const Eigen::Vector3f & light, + const float near_clip, + int & shine_tic) +{ + bool ot2 = glIsEnabled(GL_TEXTURE_2D); + bool ob = glIsEnabled(GL_BLEND); + bool odt = glIsEnabled(GL_DEPTH_TEST); + bool ocm = glIsEnabled(GL_COLOR_MATERIAL); + bool ol = glIsEnabled(GL_LIGHTING); + int obsa,obda,odf,odwm; + glGetIntegerv(GL_BLEND_SRC_ALPHA,&obsa); + glGetIntegerv(GL_BLEND_DST_ALPHA,&obda); + glGetIntegerv(GL_DEPTH_FUNC,&odf); + glGetIntegerv(GL_DEPTH_WRITEMASK,&odwm); + + glDisable(GL_COLOR_MATERIAL); + glEnable(GL_DEPTH_TEST); + //glDepthFunc(GL_LEQUAL); + glDepthMask(GL_FALSE); + glEnable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + + using namespace Eigen; + using namespace std; + + //// view_dir direction from eye to position it is looking at + //const Vector3f view_dir = (at - from).normalized(); + + //// near_clip distance from eye to near clipping plane along view_dir + //// center position on near clipping plane along viewdir from eye + //const Vector3f center = from + near_clip*view_dir; + + Vector3f plight = project(light); + // Orthogonal vectors to view direction at light + Vector3f psx = plight; + psx(0) += 1; + Vector3f psy = plight; + psy(1) += 1; + + // axis toward center + int vp[4]; + glGetIntegerv(GL_VIEWPORT,vp); + Vector3f center = unproject(Vector3f(0.5*vp[2],0.5*vp[3],plight[2]-1e-3)); + //Vector3f center(0,0,1); + Vector3f axis = light-center; + //glLineWidth(4.); + //glColor3f(1,0,0); + //glBegin(GL_LINES); + //glVertex3fv(center.data()); + //glVertex3fv(light.data()); + //glEnd(); + + const Vector3f SX = unproject(psx).normalized(); + const Vector3f SY = unproject(psy).normalized(); + + for(int i = 0; i < (int)flares.size(); i++) + { + const Vector3f sx = flares[i].scale * SX; + const Vector3f sy = flares[i].scale * SY; + glColor3fv(flares[i].color); + if (flares[i].type < 0) { + glBindTexture(GL_TEXTURE_2D, shine_ids[shine_tic]); + shine_tic = (shine_tic + 1) % shine_ids.size(); + } else + { + glBindTexture(GL_TEXTURE_2D, flare_ids[flares[i].type]); + } + + /* position = center + flare[i].loc * axis */ + const Vector3f position = center + flares[i].loc * axis; + Vector3f tmp; + + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); + tmp = position + sx; + tmp = tmp + sy; + glVertex3fv(tmp.data()); + + glTexCoord2f(1.0, 0.0); + tmp = position - sx; + tmp = tmp + sy; + glVertex3fv(tmp.data()); + + glTexCoord2f(1.0, 1.0); + tmp = position - sx; + tmp = tmp - sy; + glVertex3fv(tmp.data()); + + glTexCoord2f(0.0, 1.0); + tmp = position + sx; + tmp = tmp - sy; + glVertex3fv(tmp.data()); + glEnd(); + } + ot2?glEnable(GL_TEXTURE_2D):glDisable(GL_TEXTURE_2D); + ob?glEnable(GL_BLEND):glDisable(GL_BLEND); + odt?glEnable(GL_DEPTH_TEST):glDisable(GL_DEPTH_TEST); + ocm?glEnable(GL_COLOR_MATERIAL):glDisable(GL_COLOR_MATERIAL); + ol?glEnable(GL_LIGHTING):glDisable(GL_LIGHTING); + glBlendFunc(obsa,obda); + glDepthFunc(odf); + glDepthMask(odwm); +} diff --git a/src/igl/opengl2/lens_flare.h b/src/igl/opengl2/lens_flare.h new file mode 100644 index 000000000..280c27b69 --- /dev/null +++ b/src/igl/opengl2/lens_flare.h @@ -0,0 +1,93 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_LENS_FLARE_H +#define IGL_OPENGL2_LENS_FLARE_H + +#include "../igl_inline.h" +#include "gl.h" +#include +#include + +namespace igl +{ + namespace opengl2 + { + + struct Flare{ + int type; /* flare texture index, 0..5 */ + float scale; + float loc; /* position on axis */ + float color[3]; + Flare(): + type(-1), + scale(0), + loc(0) + {} + Flare(int type, float location, float scale, const float color[3], float colorScale) : + type(type), + scale(scale), + loc(location) + { + this->color[0] = color[0] * colorScale; + this->color[1] = color[1] * colorScale; + this->color[2] = color[2] * colorScale; + } + }; + + + // Initialize shared data for lens flates + // + // Inputs: + // start_id starting texture id location (should have at least id:id+16 free) + // Outputs: + // shine list of texture ids for shines + // flare list of texture ids for flares + IGL_INLINE void lens_flare_load_textures( + std::vector & shine_ids, + std::vector & flare_ids); + + // Create a set of lens flares + // + // Inputs: + // A primary color + // B secondary color + // C secondary color + // Outputs: + // flares list of flare objects + IGL_INLINE void lens_flare_create( + const float * A, + const float * B, + const float * C, + std::vector & flares); + + // Draw lens flares + // + // Inputs: + // flares list of Flare objects + // shine_ids list of shine textures + // flare_ids list of flare texture ids + // light position of light + // near_clip near clipping plane + // shine_tic current "tic" in shine textures + // Outputs: + // shine_tic current "tic" in shine textures + IGL_INLINE void lens_flare_draw( + const std::vector & flares, + const std::vector & shine_ids, + const std::vector & flare_ids, + const Eigen::Vector3f & light, + const float near_clip, + int & shine_tic); + } +}; + +#ifndef IGL_STATIC_LIBRARY +# include "lens_flare.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/model_proj_viewport.cpp b/src/igl/opengl2/model_proj_viewport.cpp new file mode 100644 index 000000000..9fec32a49 --- /dev/null +++ b/src/igl/opengl2/model_proj_viewport.cpp @@ -0,0 +1,31 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "model_proj_viewport.h" +#include "gl.h" + +template +IGL_INLINE void igl::opengl2::model_proj_viewport( + Eigen::PlainObjectBase & model, + Eigen::PlainObjectBase & proj, + Eigen::PlainObjectBase & viewport) +{ + Eigen::Matrix4d MV,P; + Eigen::Vector4i VPi; + glGetDoublev(GL_MODELVIEW_MATRIX,MV.data()); + glGetDoublev(GL_PROJECTION_MATRIX,P.data()); + glGetIntegerv(GL_VIEWPORT,VPi.data()); + viewport = VPi.cast(); + model = MV.cast(); + proj = P.cast(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::opengl2::model_proj_viewport, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::opengl2::model_proj_viewport, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/opengl2/model_proj_viewport.h b/src/igl/opengl2/model_proj_viewport.h new file mode 100644 index 000000000..025247ae7 --- /dev/null +++ b/src/igl/opengl2/model_proj_viewport.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_MODEL_PROJ_VIEW_H +#define IGL_OPENGL2_MODEL_PROJ_VIEW_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace opengl2 + { + // Collect the model-view, projection and viewport matrices + // + // Outputs: + // model 4x4 modelview matrix + // proj 4x4 projection matrix + // viewport 4x1 viewport vector + // + template + IGL_INLINE void model_proj_viewport( + Eigen::PlainObjectBase & model, + Eigen::PlainObjectBase & proj, + Eigen::PlainObjectBase & viewport); + } +} +#ifndef IGL_STATIC_LIBRARY +# include "model_proj_viewport.cpp" +#endif +#endif diff --git a/src/igl/opengl2/print_gl_get.cpp b/src/igl/opengl2/print_gl_get.cpp new file mode 100644 index 000000000..7182dc558 --- /dev/null +++ b/src/igl/opengl2/print_gl_get.cpp @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "print_gl_get.h" + +#include +IGL_INLINE void igl::opengl2::print_gl_get(GLenum pname) +{ + double dM[16]; + + int rows = 4; + int cols = 4; + switch(pname) + { + case GL_MODELVIEW_MATRIX: + case GL_PROJECTION_MATRIX: + { + rows = 4; + cols = 4; + glGetDoublev(pname,dM); + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_PRINT_GL_GET_H +#define IGL_OPENGL2_PRINT_GL_GET_H +#include "gl.h" +#include "../igl_inline.h" + +namespace igl +{ + namespace opengl2 + { + // Prints the value of pname found by issuing glGet*(pname,*) + // Inputs: + // pname enum key to gl parameter + IGL_INLINE void print_gl_get(GLenum pname); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "print_gl_get.cpp" +#endif + +#endif diff --git a/src/igl/opengl2/project.cpp b/src/igl/opengl2/project.cpp new file mode 100644 index 000000000..a953197b7 --- /dev/null +++ b/src/igl/opengl2/project.cpp @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "project.h" +#include "../project.h" +#include "gl.h" +#include "glu.h" +#include + +IGL_INLINE int igl::opengl2::project( + const double objX, + const double objY, + const double objZ, + double* winX, + double* winY, + double* winZ) +{ + using namespace std; + // Put model, projection, and viewport matrices into double arrays + double MV[16]; + double P[16]; + int VP[4]; + glGetDoublev(GL_MODELVIEW_MATRIX, MV); + glGetDoublev(GL_PROJECTION_MATRIX, P); + glGetIntegerv(GL_VIEWPORT, VP); + int ret = gluProject(objX,objY,objZ,MV,P,VP,winX,winY,winZ); + return ret; +} + +template +IGL_INLINE int igl::opengl2::project( + const Eigen::PlainObjectBase & obj, + Eigen::PlainObjectBase & win) +{ + assert(obj.size() >= 3); + Eigen::Vector3d dobj(obj(0),obj(1),obj(2)); + Eigen::Vector3d dwin; + int ret = igl::opengl2::project(dobj(0),dobj(1),dobj(2), + &dwin.data()[0], + &dwin.data()[1], + &dwin.data()[2]); + win(0) = dwin(0); + win(1) = dwin(1); + win(2) = dwin(2); + return ret; +} + +template +IGL_INLINE Derivedobj igl::opengl2::project( + const Eigen::PlainObjectBase & obj) +{ + Derivedobj win; + igl::opengl2::project(obj,win); + return win; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template int igl::opengl2::project, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template int igl::opengl2::project, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template int igl::opengl2::project, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::project >(Eigen::PlainObjectBase > const&); +#endif + diff --git a/src/igl/opengl2/project.h b/src/igl/opengl2/project.h new file mode 100644 index 000000000..befd09007 --- /dev/null +++ b/src/igl/opengl2/project.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_PROJECT_H +#define IGL_OPENGL2_PROJECT_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace opengl2 + { + // Wrapper for gluProject that uses the current GL_MODELVIEW_MATRIX, + // GL_PROJECTION_MATRIX, and GL_VIEWPORT + // Inputs: + // obj* 3D objects' x, y, and z coordinates respectively + // Outputs: + // win* pointers to screen space x, y, and z coordinates respectively + // Returns return value of gluProject call + IGL_INLINE int project( + const double objX, + const double objY, + const double objZ, + double* winX, + double* winY, + double* winZ); + // Eigen wrapper + template + IGL_INLINE int project( + const Eigen::PlainObjectBase & obj, + Eigen::PlainObjectBase & win); + // Eigen wrapper with return + template + IGL_INLINE Derivedobj project( + const Eigen::PlainObjectBase & obj); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "project.cpp" +#endif + +#endif + diff --git a/src/igl/opengl2/right_axis.cpp b/src/igl/opengl2/right_axis.cpp new file mode 100644 index 000000000..a0b905176 --- /dev/null +++ b/src/igl/opengl2/right_axis.cpp @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "right_axis.h" +#include "gl.h" + +IGL_INLINE void igl::opengl2::right_axis(double * x, double * y, double * z) +{ + double mv[16]; + glGetDoublev(GL_MODELVIEW_MATRIX, mv); + igl::opengl2::right_axis(mv,x,y,z); +} + +IGL_INLINE void igl::opengl2::right_axis(const double * mv,double * x, double * y, double * z) +{ + *x = -mv[0*4+0]; + *y = -mv[1*4+0]; + *z = -mv[2*4+0]; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/opengl2/right_axis.h b/src/igl/opengl2/right_axis.h new file mode 100644 index 000000000..4e6787cdd --- /dev/null +++ b/src/igl/opengl2/right_axis.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_RIGHT_AXIS_H +#define IGL_OPENGL2_RIGHT_AXIS_H +#include "../igl_inline.h" +namespace igl +{ + namespace opengl2 + { + // Determines the right axis or depth axis of the current gl matrix + // Outputs: + // x pointer to x-coordinate in scene coordinates of the un-normalized + // right axis + // y pointer to y-coordinate in scene coordinates of the un-normalized + // right axis + // z pointer to z-coordinate in scene coordinates of the un-normalized + // right axis + // mv pointer to modelview matrix + // + // Note: Right axis is returned *UN-normalized* + IGL_INLINE void right_axis(double * x, double * y, double * z); + IGL_INLINE void right_axis(const double * mv, double * x, double * y, double * z); + } +}; + +#ifndef IGL_STATIC_LIBRARY +# include "right_axis.cpp" +#endif +#endif diff --git a/src/igl/opengl2/shine_textures.h b/src/igl/opengl2/shine_textures.h new file mode 100644 index 000000000..05001989d --- /dev/null +++ b/src/igl/opengl2/shine_textures.h @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_SHINE_TEXTURES_H +#define IGL_OPENGL2_SHINE_TEXTURES_H + +#include + +namespace igl +{ + namespace opengl2 + { + + const uint8_t SHINE_TEXTURE_0[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,7,20,15,7,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,36,36,0,0,0,0,0,0,0,0,0,0,0,0,20,20,15,12,20,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,28,36,36,0,0,0,0,0,0,0,0,0,0,0,12,28,20,12,20,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,39,28,31,28,0,0,0,0,0,0,0,0,0,0,0,28,28,20,15,28,28,12,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,44,31,36,28,0,0,0,0,0,0,0,0,0,0,15,31,28,20,36,36,20,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,20,0,0,0,0,0,0,0,0,12,12,7,12,12,47,47,44,44,36,0,0,0,0,0,0,0,0,0,0,31,36,28,28,44,28,12,0,0,0,12,23,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,0,0,0,15,15,15,20,20,47,47,44,44,23,0,0,0,0,0,0,7,12,7,20,36,36,28,44,39,20,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,39,23,0,0,0,0,0,0,0,12,15,12,12,12,47,47,44,44,15,0,0,0,0,0,0,12,12,12,36,36,36,44,52,28,7,0,0,7,20,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,39,7,0,0,0,0,0,0,12,20,20,0,0,47,47,47,47,7,0,0,0,0,0,12,15,15,23,36,44,44,60,44,20,0,0,0,28,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,28,0,0,0,0,0,0,12,23,23,12,12,52,52,52,52,0,0,0,0,0,0,20,23,23,39,47,47,60,60,28,0,0,0,28,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,44,12,0,0,0,0,0,0,28,31,31,23,52,60,52,52,0,0,0,0,0,0,28,28,36,44,60,55,68,44,12,0,0,28,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,52,36,0,0,0,0,0,0,31,39,52,36,60,68,52,52,0,0,0,0,0,20,28,36,44,60,63,76,60,23,0,0,23,47,36,12,0,0,0,0,0,0,0,0,0,0,0,12,23,23,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,52,20,0,0,15,15,0,36,44,60,36,71,76,55,52,0,0,0,0,0,31,36,44,52,71,76,76,36,0,0,23,47,39,12,0,7,12,0,0,0,0,0,0,0,7,20,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,52,39,0,0,20,20,12,36,55,92,39,79,100,68,60,0,0,0,0,20,36,47,52,76,84,92,60,20,0,20,47,44,20,0,12,20,15,0,0,0,0,0,0,20,28,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,55,20,0,20,23,20,31,79,111,44,84,127,84,55,0,0,0,0,36,39,60,68,92,92,79,28,0,20,47,52,23,0,0,12,20,7,0,0,0,0,20,36,36,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,7,0,0,0,0,0,0,0,31,60,47,0,7,28,28,20,84,116,52,100,164,100,52,0,0,0,12,44,60,63,92,103,100,52,7,20,47,60,28,0,12,15,7,7,0,0,0,15,36,47,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,12,12,7,0,0,0,0,0,7,52,60,28,0,28,28,20,84,119,79,119,188,116,44,0,0,0,36,44,76,76,111,108,76,20,12,44,60,31,12,20,28,12,0,0,0,12,36,52,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,7,0,0,0,0,20,20,12,0,0,0,12,20,7,0,0,0,0,0,28,63,52,0,28,36,28,76,116,100,124,188,116,36,0,0,7,47,68,79,108,119,100,36,12,44,68,36,20,28,39,23,0,0,7,31,60,68,52,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,20,20,12,0,0,12,15,23,20,12,0,0,20,23,12,0,0,0,0,0,55,68,31,12,39,39,76,119,132,127,180,100,28,0,0,28,55,92,95,140,124,68,20,44,68,44,28,31,44,28,7,0,20,52,76,71,52,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,28,20,12,0,0,20,36,31,15,0,7,23,28,12,0,0,0,0,28,68,60,12,44,44,71,116,151,140,175,100,20,0,7,55,84,108,140,148,100,36,44,71,52,36,36,47,28,0,12,39,76,87,71,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,31,23,12,0,20,36,39,28,7,12,28,36,20,0,0,0,0,55,71,39,36,44,76,116,167,156,180,100,15,12,39,76,111,127,167,132,60,44,76,60,44,44,52,28,7,28,68,92,92,63,28,0,0,7,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,36,20,7,12,31,44,36,15,12,28,36,20,0,0,0,28,71,68,36,52,76,119,172,180,188,108,20,28,76,108,140,172,164,103,60,76,68,52,52,52,28,20,52,95,108,84,47,12,0,12,20,20,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,39,39,23,15,28,47,44,28,20,36,44,23,12,12,12,55,76,52,60,76,124,164,188,180,100,20,60,103,143,164,183,132,92,87,84,68,60,52,31,36,84,116,108,76,28,7,15,28,23,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,44,39,28,28,44,52,36,28,36,52,36,28,20,36,76,76,84,84,148,175,207,172,100,36,108,140,175,191,164,124,108,100,76,68,52,44,60,100,124,100,52,20,20,31,28,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,52,52,44,39,44,60,52,44,44,60,47,39,28,68,84,108,100,172,196,228,167,108,76,156,188,204,196,156,132,124,92,76,60,60,84,116,116,84,52,31,36,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,55,55,52,55,60,60,55,60,71,68,52,52,84,108,127,188,220,239,164,116,119,188,228,212,188,156,140,111,84,71,79,100,124,111,84,63,52,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,31,52,60,68,68,76,71,76,84,87,79,63,84,100,148,188,236,252,172,140,164,220,236,212,180,164,132,100,87,100,116,132,124,100,84,68,52,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,0,0,0,0,0,12,39,60,68,84,92,87,92,100,108,95,87,108,148,191,244,252,183,172,212,244,236,212,188,148,124,108,116,135,151,140,111,87,68,44,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,20,28,28,20,0,0,0,20,52,68,84,108,111,116,116,132,116,124,148,204,239,252,204,199,244,252,236,215,172,148,135,143,156,164,140,108,87,68,44,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,36,36,39,31,12,0,28,63,95,127,140,143,143,164,156,164,212,244,252,228,228,252,252,244,212,188,167,180,180,156,124,108,92,60,36,28,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,36,39,44,47,52,44,44,63,100,140,167,191,204,204,212,223,252,252,244,244,252,252,244,228,212,204,183,156,132,108,68,44,36,36,31,23,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,47,52,55,60,76,100,132,156,188,228,244,239,244,252,252,252,252,252,252,252,244,228,204,164,116,79,68,79,76,60,44,31,20,20,12,7,0,0,0,0,7,15,15,7,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,55,60,68,87,132,180,228,244,252,252,252,252,252,252,252,252,252,244,212,172,143,135,127,108,87,76,68,68,68,68,68,60,55,52,68,68,60,55,52,47,39,28,36,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,15,28,28,28,28,28,28,28,23,20,15,15,15,15,15,15,15,15,12,20,44,68,79,103,164,220,244,252,252,252,252,252,252,252,252,244,223,204,191,180,172,180,180,172,151,135,132,132,127,124,116,108,124,119,108,95,92,76,60,44,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,31,68,84,84,84,84,84,84,92,92,92,84,84,84,84,84,92,95,100,92,87,84,87,103,140,188,228,244,252,252,252,252,252,252,252,252,239,212,191,191,180,159,140,124,124,124,124,132,132,124,111,100,84,87,76,60,52,52,47,47,39,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,47,52,52,52,52,52,52,60,63,68,68,68,71,84,95,116,132,148,159,172,188,207,223,231,244,247,252,252,252,252,252,252,252,252,252,244,220,172,132,108,92,84,76,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,63,71,76,76,76,84,103,132,156,164,172,167,172,196,228,244,252,252,252,252,252,252,252,252,252,252,244,220,188,159,132,100,76,55,47,44,39,36,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,28,44,44,52,52,71,84,103,127,140,148,143,140,132,119,132,148,172,196,228,244,252,252,244,244,252,252,252,239,220,223,244,244,223,204,183,164,140,127,108,84,60,39,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,28,47,55,84,100,111,100,92,79,71,68,68,71,84,87,100,140,196,228,236,236,252,228,220,228,247,247,252,239,212,183,188,212,228,228,228,223,196,159,124,92,68,63,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,36,52,52,52,52,60,60,60,60,55,39,28,36,52,71,108,164,204,215,207,212,236,244,188,180,212,239,236,236,247,212,167,132,140,164,188,191,188,188,180,167,164,148,116,84,63,60,52,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,44,44,47,52,52,52,52,44,28,7,0,12,28,44,60,92,124,151,188,188,172,175,204,228,215,148,156,220,236,212,212,236,220,180,127,92,92,103,132,151,148,140,151,148,132,132,148,140,111,76,60,52,52,47,39,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,36,36,39,44,44,44,44,36,15,0,0,0,0,12,28,36,44,71,92,108,140,172,164,140,148,180,204,215,164,108,148,239,244,188,188,204,204,188,156,108,68,63,68,87,111,119,108,100,124,140,132,108,111,124,124,103,71,52,44,44,44,39,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,23,28,31,36,36,36,36,20,0,0,0,0,0,0,0,12,23,28,36,60,71,76,100,148,164,135,108,111,156,180,196,183,108,84,164,252,252,183,164,175,196,180,172,140,92,60,39,44,68,79,95,100,84,68,87,116,124,116,103,103,103,100,84,63,44,39,36,36,36,31,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,12,20,23,28,23,12,0,0,0,0,0,0,0,0,0,0,0,15,23,44,52,52,71,108,135,140,116,84,84,132,156,164,188,148,52,84,180,252,252,196,140,148,188,164,172,164,108,76,60,31,20,44,68,76,79,76,68,52,60,79,100,108,103,108,103,95,84,63,52,39,36,31,31,23,20,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,39,52,76,100,116,116,87,55,68,116,127,127,172,175,95,20,100,204,252,252,220,132,116,172,156,140,156,140,84,63,55,31,12,12,44,68,68,68,60,52,44,44,52,68,87,95,100,100,92,84,68,47,31,28,23,20,12,20,12,15,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,36,60,71,92,100,92,52,39,52,100,116,108,143,188,140,44,20,119,220,252,252,236,140,92,132,167,124,116,143,119,68,52,52,31,12,0,20,44,63,63,55,44,36,36,36,36,44,55,76,84,87,84,76,71,63,44,20,7,12,12,15,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,36,52,71,84,84,63,28,28,44,84,103,87,108,167,167,84,7,36,140,236,252,244,228,148,76,87,156,132,100,111,135,103,52,39,44,31,12,0,0,20,47,60,60,47,31,23,31,28,28,28,28,36,52,68,71,68,68,60,52,36,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,36,52,60,71,60,36,12,20,36,76,92,79,76,127,172,116,28,0,60,151,236,231,220,204,143,68,52,116,156,100,79,116,124,84,39,28,39,28,12,0,0,0,28,52,60,55,39,20,12,20,28,20,20,12,0,12,36,55,60,55,52,44,36,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,36,52,63,55,39,12,0,12,36,60,84,76,60,87,151,132,60,0,0,68,148,220,212,204,188,148,68,31,79,140,119,79,68,116,108,68,28,20,28,23,12,0,0,0,0,28,52,52,52,36,12,0,12,20,12,0,0,0,0,7,28,44,44,39,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,52,39,20,0,0,12,28,52,71,76,44,55,116,143,87,20,0,0,76,143,196,196,183,183,143,76,28,52,100,132,87,52,68,116,95,52,20,12,20,12,0,0,0,0,0,7,28,47,47,44,28,7,0,0,0,0,0,0,0,0,0,0,20,28,28,20,15,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,52,39,20,0,0,0,7,23,39,60,71,44,28,76,132,103,44,0,0,12,71,132,183,188,156,167,140,84,44,23,76,116,95,60,44,79,108,84,44,15,0,0,12,12,0,0,0,0,0,12,28,44,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,7,12,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,44,23,0,0,0,0,7,20,28,52,68,44,12,44,100,116,68,12,0,0,20,71,132,167,172,132,140,116,100,52,0,55,79,100,71,36,39,84,100,68,36,12,0,12,12,0,0,0,0,0,0,0,12,31,44,39,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,31,12,0,0,0,0,0,12,20,44,63,44,12,20,63,108,84,36,0,0,0,12,60,124,148,164,124,119,100,108,52,0,28,68,92,76,60,20,52,92,84,52,20,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,36,20,0,0,0,0,0,0,0,12,36,60,47,15,0,44,87,92,52,7,0,0,0,20,60,124,140,156,116,100,92,108,47,15,0,55,68,76,60,36,15,60,92,68,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,31,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,23,12,0,0,0,0,0,0,0,0,23,52,47,20,0,28,63,95,68,28,0,0,0,0,20,52,135,135,148,111,84,100,108,47,36,0,36,60,68,52,47,20,20,68,84,52,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,28,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,12,0,0,0,0,0,0,0,0,0,20,47,52,20,0,12,44,84,76,44,0,0,0,0,0,23,44,124,116,108,108,71,108,92,52,44,0,7,55,60,47,44,36,0,28,76,79,52,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,15,44,52,23,0,0,36,60,84,55,20,0,0,0,0,0,28,44,108,87,76,103,68,108,71,60,44,0,0,36,55,52,36,44,20,0,39,79,68,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,0,0,0,0,0,0,0,0,0,0,12,36,52,28,0,0,20,44,76,63,36,0,0,0,0,0,0,28,52,76,68,55,100,68,108,60,71,44,7,0,12,52,52,31,36,36,0,0,52,84,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,7,28,47,28,0,0,12,36,60,68,52,12,0,0,0,0,0,0,15,52,63,55,44,92,63,100,52,84,36,23,0,0,36,52,36,28,36,20,0,12,60,79,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,28,0,0,0,28,44,63,52,28,0,0,0,0,0,0,0,12,60,52,47,39,79,60,84,52,76,36,36,0,0,12,52,47,20,28,28,7,0,23,68,76,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,28,7,0,0,12,36,52,55,44,7,0,0,0,0,0,0,7,20,68,47,44,36,68,60,68,47,60,31,31,0,0,0,36,47,28,15,28,20,0,0,31,68,63,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,7,0,0,0,28,36,52,44,20,0,0,0,0,0,0,0,0,12,60,47,36,28,52,52,44,47,52,28,28,0,0,0,15,44,44,12,20,20,0,0,0,36,76,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,15,28,44,44,36,0,0,0,0,0,0,0,0,0,0,52,47,23,28,52,47,31,47,44,28,20,7,0,0,0,36,44,20,7,20,12,0,0,7,47,76,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,12,0,0,0,0,20,28,44,39,15,0,0,0,0,0,0,0,0,0,0,44,44,12,28,47,44,28,47,44,36,20,15,0,0,0,20,39,36,0,12,12,12,0,0,15,52,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,12,20,31,36,28,0,0,0,0,0,0,0,0,0,0,0,44,39,0,28,47,52,36,52,44,36,12,12,0,0,0,0,36,36,15,0,12,12,0,0,0,20,60,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,12,15,20,36,36,12,0,0,0,0,0,0,0,0,0,0,0,39,39,0,23,44,47,28,52,39,39,12,12,0,0,0,0,20,36,28,0,0,0,0,0,0,0,28,55,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,15,20,28,31,20,0,0,0,0,0,0,0,0,0,0,0,12,36,36,0,20,36,44,20,44,36,36,7,7,0,0,0,0,0,31,36,12,0,0,0,0,0,0,0,31,52,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,0,12,20,28,28,0,0,0,0,0,0,0,0,0,0,0,0,23,36,36,0,15,36,28,12,20,28,28,0,7,0,0,0,0,0,20,31,23,0,0,0,0,0,0,0,7,31,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,7,7,23,28,15,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,15,36,15,0,0,28,28,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,12,31,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,15,0,15,36,23,0,0,23,23,7,0,0,0,0,0,0,0,20,28,20,0,0,0,0,0,0,0,0,12,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,15,0,12,28,28,12,12,31,31,20,0,0,0,0,0,0,0,7,28,23,0,0,0,0,0,0,0,0,0,7,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,28,28,20,20,23,23,20,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,20,20,7,7,15,20,20,0,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,23,20,0,0,12,28,28,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,28,20,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,31,28,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,20,12,0,0,0,15,15,7,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_1[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,20,20,7,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,12,12,0,0,0,0,12,12,12,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,20,20,15,0,0,0,0,12,12,0,0,0,0,12,12,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,12,20,20,0,0,0,0,12,15,15,0,0,12,20,20,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,20,20,0,0,0,0,28,31,28,0,0,0,12,12,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,20,20,0,0,0,12,36,44,28,0,0,0,0,0,12,28,28,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,28,28,20,0,0,20,47,60,28,0,0,7,7,0,20,20,20,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,28,31,28,0,0,15,44,68,36,0,0,20,20,12,20,20,12,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,15,36,36,0,0,12,44,68,36,0,0,28,28,12,23,23,12,0,0,0,12,12,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,7,36,36,0,0,0,44,68,36,0,0,23,23,0,28,28,0,0,0,0,12,7,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,28,31,15,36,36,12,0,0,28,60,36,0,0,20,20,12,28,28,0,0,0,12,12,0,0,0,0,12,28,23,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,28,7,0,0,0,0,0,12,12,0,0,0,0,0,0,0,23,36,28,36,36,28,0,0,28,60,36,0,0,20,20,28,36,36,0,0,0,20,20,0,0,0,0,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,36,23,12,0,0,0,0,0,15,12,0,0,0,0,0,0,12,36,36,36,44,39,0,0,44,52,20,0,12,36,36,36,36,36,0,0,15,20,20,0,0,0,15,28,23,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,28,12,0,0,0,0,15,15,0,0,0,0,0,0,0,36,36,31,44,44,0,12,52,60,28,0,28,36,36,39,39,20,0,0,28,28,12,0,0,0,28,31,12,0,0,0,0,0,0,0,12,20,15,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,36,44,44,28,7,0,0,0,7,20,12,0,0,0,0,0,0,36,39,36,44,44,0,0,52,84,55,12,44,39,39,44,44,7,0,0,28,28,0,0,0,20,36,28,0,0,0,0,0,0,0,12,31,28,12,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,36,44,39,31,20,0,0,0,12,20,12,0,0,0,0,0,20,44,44,47,44,20,0,52,92,71,15,47,31,39,44,44,0,0,20,31,28,0,0,7,36,36,12,0,0,0,0,0,0,12,36,44,20,7,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,28,52,52,36,20,7,0,12,28,28,0,0,0,0,0,0,44,44,55,52,36,12,60,100,84,12,52,36,55,44,44,0,0,31,36,12,0,0,28,39,28,0,0,0,0,0,0,7,28,52,28,12,20,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,63,52,44,28,0,0,20,36,23,0,0,0,0,0,44,44,63,52,52,23,76,108,87,0,52,44,76,47,44,0,12,36,36,0,0,12,44,44,12,0,0,0,0,0,7,36,52,36,20,20,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,76,68,52,28,7,0,36,36,12,0,0,0,0,31,47,63,52,52,20,68,124,108,0,44,44,76,52,36,0,28,44,36,0,0,28,44,28,0,0,0,0,0,0,36,60,44,20,20,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,68,79,71,52,28,7,20,44,36,0,0,0,0,12,52,55,60,52,28,63,140,116,0,47,47,68,52,20,0,44,44,20,0,12,44,44,12,0,0,0,0,0,36,68,52,28,23,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,76,87,84,60,28,12,44,52,28,0,0,0,0,52,52,79,55,44,60,143,124,0,52,55,60,52,0,12,44,44,0,0,36,52,28,0,0,0,0,0,36,76,71,44,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,68,92,95,92,60,20,28,60,52,12,0,0,0,44,52,92,55,71,71,159,124,0,52,71,60,52,0,36,47,39,0,20,52,47,12,0,0,0,0,31,76,84,55,36,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,79,103,108,100,60,20,52,68,39,0,0,0,20,55,92,60,71,68,164,119,20,60,92,60,55,0,47,52,20,0,44,55,28,0,0,0,0,28,79,92,68,44,47,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,84,116,116,108,60,36,68,68,20,0,0,0,60,76,76,68,68,164,124,47,76,108,60,47,15,52,52,0,23,60,52,7,0,0,0,23,76,100,76,52,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,52,100,132,140,108,52,55,76,52,0,0,0,52,63,103,63,92,172,140,76,92,119,60,36,39,52,44,0,52,60,28,0,0,0,20,71,103,87,63,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,63,108,148,156,108,60,71,76,28,0,0,36,60,119,63,116,180,159,92,92,124,60,23,55,60,20,28,63,52,0,0,0,20,68,108,92,68,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,76,124,172,167,103,68,76,60,12,0,12,63,116,76,127,172,172,111,108,116,63,28,60,60,12,55,68,28,0,0,15,63,108,103,76,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,84,148,196,167,100,84,79,44,0,0,60,100,103,124,156,180,127,127,116,68,47,60,44,36,68,52,0,0,12,60,108,111,84,55,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,100,180,212,164,100,92,76,20,0,44,84,132,124,156,188,140,148,108,68,60,63,36,60,68,28,0,20,68,108,119,95,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,132,212,220,151,108,92,55,0,20,76,140,124,172,196,148,159,100,76,68,63,47,71,52,0,20,76,119,132,108,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,148,220,212,156,124,87,31,0,68,148,148,183,199,175,180,100,87,68,68,68,71,28,20,76,124,143,119,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,68,159,223,212,167,124,68,12,60,135,164,191,199,204,196,108,87,76,76,76,52,20,71,132,159,132,76,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,71,167,228,212,172,111,44,36,124,183,207,212,228,191,127,79,92,79,76,44,71,140,167,140,84,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,15,84,180,223,212,164,92,36,100,180,220,220,231,183,148,84,116,92,84,79,156,196,156,84,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,12,0,20,92,188,220,220,151,68,84,172,239,236,244,204,164,108,124,116,108,156,212,180,95,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,15,31,36,23,7,28,100,191,236,212,127,84,148,236,244,252,228,172,143,132,151,164,212,191,108,31,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,20,20,12,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,20,28,36,44,39,23,36,111,199,239,188,111,135,223,252,252,236,183,172,180,188,207,188,108,31,0,0,0,0,15,20,15,7,0,0,0,0,0,0,0,0,12,20,20,12,7,20,28,44,44,39,36,28,23,20,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,31,20,0,0,0,0,0,12,20,28,28,39,44,52,52,39,52,124,204,228,156,143,196,252,252,236,212,204,220,215,188,108,31,0,0,12,28,28,23,20,12,0,0,0,0,0,20,31,36,36,44,60,63,60,52,52,44,44,36,36,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,36,39,44,44,44,28,7,0,0,20,28,36,44,68,76,68,60,76,140,212,204,172,188,247,252,236,236,231,236,188,108,31,0,20,39,44,36,28,15,0,0,7,28,44,52,60,76,84,92,87,84,76,63,52,44,44,44,36,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,39,44,47,60,71,76,76,60,36,23,28,39,47,68,92,100,92,111,172,212,207,212,244,252,247,247,236,188,103,44,36,52,52,44,39,28,20,36,60,79,100,116,124,108,103,100,92,76,60,52,47,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,68,76,84,84,84,84,84,76,76,76,76,100,132,143,164,204,223,236,244,252,252,236,188,116,84,76,76,68,68,71,76,92,111,124,132,132,132,124,116,84,68,60,52,36,23,20,15,12,0,12,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,68,84,84,84,84,87,100,127,143,156,172,196,215,236,252,252,252,244,212,159,132,140,148,148,132,119,119,124,132,140,148,143,135,119,108,92,71,52,44,31,36,44,44,39,28,23,39,52,47,44,44,44,55,60,52,55,52,36,23,28,47,39,28,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,68,84,92,100,108,132,172,212,244,247,252,252,252,244,215,196,207,228,212,196,196,204,204,196,188,175,164,148,143,143,148,148,140,140,140,116,108,108,103,100,84,76,76,79,68,60,60,60,60,60,55,55,52,36,23,28,47,39,28,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,12,12,20,20,20,20,20,44,60,76,76,76,76,76,76,76,76,76,76,76,71,68,68,76,76,84,92,92,92,92,92,92,92,92,92,92,92,100,111,132,156,180,199,223,244,252,252,252,252,247,244,244,252,247,247,252,247,231,204,175,156,156,156,159,164,167,164,151,132,116,108,84,76,68,68,71,71,68,63,63,60,60,60,55,52,52,44,44,36,23,12,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,12,12,12,20,23,23,20,20,20,39,60,68,68,68,76,84,92,92,100,103,108,135,159,167,172,172,164,156,164,180,180,175,172,164,164,156,151,151,156,172,188,204,220,236,244,247,252,252,252,252,252,252,252,252,252,247,244,239,220,188,164,124,92,63,44,28,28,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,23,28,36,39,68,92,100,100,100,84,68,68,84,84,79,76,84,100,116,124,127,124,127,148,175,204,220,228,236,252,252,252,252,252,252,252,252,247,244,228,199,164,135,124,108,92,68,52,44,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,7,0,0,0,12,31,52,60,68,68,68,68,76,100,132,164,172,164,164,183,215,236,244,244,244,252,252,252,252,252,239,212,196,188,188,172,140,108,79,60,52,47,44,36,36,28,20,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,55,60,60,60,63,68,71,92,116,132,132,116,108,111,135,156,183,215,236,220,215,239,252,252,252,252,252,239,220,191,164,148,148,135,108,68,28,12,20,28,36,36,28,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,44,47,52,52,52,52,55,60,60,60,60,76,92,100,100,100,100,92,95,111,132,164,212,236,196,175,204,244,252,252,252,252,252,252,228,196,164,140,132,143,156,143,116,76,36,12,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,36,36,39,44,44,44,47,52,52,44,28,15,23,44,68,84,100,100,100,87,63,52,76,108,140,164,199,220,188,140,156,215,228,236,252,252,252,252,252,231,215,196,151,103,92,116,143,164,159,140,100,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,20,23,28,28,36,36,39,44,36,20,0,0,0,20,36,52,60,68,76,92,84,63,31,20,47,100,127,140,156,180,204,183,116,116,167,220,188,212,252,252,252,252,252,236,204,196,188,156,92,52,60,92,135,164,172,156,124,84,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,12,15,20,20,23,23,20,12,0,0,0,0,0,20,36,44,47,52,55,60,52,39,23,7,0,36,84,124,132,127,132,164,196,175,100,71,135,183,188,143,188,244,236,236,236,236,252,223,188,164,172,156,100,39,20,39,79,124,156,172,159,132,95,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,12,0,7,0,0,0,0,0,0,0,12,28,36,36,44,44,44,39,20,12,0,0,0,23,68,116,140,124,100,103,148,191,167,95,44,87,180,172,148,127,167,228,220,220,199,220,247,247,212,164,143,156,156,108,44,0,0,28,63,100,140,159,156,116,84,55,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,31,36,36,28,12,0,0,0,0,0,12,47,95,132,132,100,68,87,140,172,148,92,28,44,127,180,148,108,132,156,212,204,204,164,191,228,252,247,207,148,116,148,148,108,44,0,0,0,15,52,79,108,132,132,108,76,52,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,28,28,20,12,0,0,0,0,0,0,0,31,79,116,124,108,68,44,79,132,159,127,84,28,20,76,156,140,127,84,116,132,180,196,188,151,148,191,244,252,244,204,132,100,124,132,100,47,15,0,0,0,12,31,60,79,100,108,95,68,44,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,23,23,20,0,0,0,0,0,0,0,0,0,20,55,92,119,108,68,28,20,68,132,148,116,68,28,0,44,108,159,108,100,76,84,100,140,180,180,148,100,156,220,252,252,244,196,119,76,95,116,103,60,20,0,0,0,0,0,28,44,63,76,84,76,52,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,7,0,0,0,0,0,0,0,0,0,0,12,39,71,100,100,79,36,12,15,68,116,119,84,44,20,0,20,76,140,132,87,84,76,52,76,103,148,172,143,71,108,172,244,252,252,244,196,108,60,76,108,100,68,28,0,0,0,0,0,0,15,36,52,60,68,68,47,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,60,79,92,76,44,15,0,15,60,108,111,68,28,0,0,0,52,95,135,100,68,71,71,44,76,84,127,172,151,79,55,127,204,236,244,252,244,188,100,52,55,92,92,71,36,12,0,0,0,0,0,0,12,28,44,52,55,55,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,68,84,76,52,20,0,0,20,55,92,92,68,36,7,0,0,28,68,116,103,63,60,68,60,44,76,76,100,151,159,92,23,79,156,215,228,244,244,228,164,84,36,44,84,87,71,39,20,0,0,0,0,0,0,0,0,20,36,36,39,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,52,68,68,52,28,12,0,0,20,52,79,71,47,36,20,0,0,0,52,84,108,76,28,63,68,44,44,76,68,63,108,151,103,20,36,108,180,212,223,231,212,188,135,63,28,36,68,76,68,44,23,0,0,0,0,0,0,0,0,0,12,28,31,31,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,39,60,68,52,31,12,0,0,0,23,55,68,52,28,20,12,0,0,0,28,63,100,84,55,12,68,68,20,44,68,55,39,71,148,119,28,7,60,132,180,196,212,199,172,151,116,52,20,28,52,68,68,47,28,7,0,0,0,0,0,0,0,0,0,12,20,28,28,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,44,55,52,36,20,0,0,0,0,28,52,60,36,12,12,7,0,0,0,12,52,76,92,68,28,20,63,63,0,44,60,31,20,47,132,127,44,0,20,84,140,172,196,204,172,143,127,100,44,12,20,44,60,60,47,28,12,0,0,0,0,0,0,0,0,0,0,0,12,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,44,36,20,7,0,0,0,0,28,52,52,28,0,0,0,0,0,0,0,36,60,79,68,52,0,44,63,55,0,44,60,15,0,20,103,124,60,0,0,39,100,148,172,191,180,140,116,108,92,44,12,12,36,47,44,39,23,7,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,31,28,28,20,12,0,0,0,0,0,28,52,44,20,0,0,0,0,0,0,0,12,52,60,76,63,28,0,55,60,36,0,47,60,20,7,0,76,116,76,7,0,12,68,124,143,172,183,148,111,100,100,84,36,0,7,20,31,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,28,20,12,0,0,0,0,0,0,7,28,52,36,15,0,0,0,0,0,0,0,0,36,52,68,63,55,0,0,60,60,12,0,52,60,23,15,0,60,103,84,23,0,0,36,92,124,143,172,164,116,84,87,87,76,28,0,0,12,20,15,20,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,12,7,0,0,0,0,0,0,12,31,52,31,12,0,0,0,0,0,0,0,0,20,52,52,68,60,31,0,20,60,60,0,0,52,60,23,20,0,44,84,92,31,0,0,7,60,103,116,148,164,140,87,68,76,76,63,23,0,0,0,0,7,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,28,7,0,0,0,0,0,0,0,0,0,36,47,52,60,52,0,0,36,55,52,0,0,52,52,12,12,0,20,68,84,28,0,0,0,28,76,108,111,148,156,108,68,60,68,71,60,23,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,39,23,0,0,0,0,0,0,0,0,0,0,20,44,39,52,55,36,0,0,52,52,36,0,0,52,52,0,0,0,0,55,76,28,0,0,0,0,47,92,92,116,140,132,76,52,52,68,68,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,36,20,0,0,0,0,0,0,0,0,0,0,0,36,44,36,52,52,12,0,0,52,52,12,0,0,52,52,0,0,0,0,52,68,44,7,0,0,0,28,68,92,84,119,132,100,60,44,44,63,47,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,12,0,0,0,0,0,0,0,0,0,0,0,20,39,28,44,52,36,0,0,12,52,52,0,0,0,52,52,0,0,0,0,44,60,52,0,0,0,0,0,44,76,76,92,116,111,68,36,28,36,44,28,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,23,20,12,0,0,0,0,0,0,0,0,0,0,0,7,36,36,28,52,52,12,0,0,36,47,44,0,0,0,47,47,0,0,0,0,28,52,52,0,0,0,0,0,20,52,79,68,87,108,87,44,28,28,36,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,20,31,47,36,0,0,0,44,47,28,0,0,0,44,44,0,0,0,0,7,47,47,0,0,0,0,0,0,36,63,68,68,84,92,63,36,20,20,36,52,44,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,31,15,44,44,15,0,0,0,44,44,12,0,0,0,44,44,0,0,0,0,0,44,44,12,0,0,0,0,0,15,44,68,52,63,76,76,39,20,20,28,31,44,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,31,15,23,44,36,0,0,0,12,44,44,0,0,0,0,44,44,0,0,0,0,0,44,44,28,0,0,0,0,0,0,36,52,60,52,60,71,55,28,20,12,20,28,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,7,39,44,20,0,0,0,28,39,36,0,0,0,0,44,44,0,0,0,0,0,28,44,39,0,0,0,0,0,0,12,36,60,44,52,60,68,44,20,12,12,15,15,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,20,39,36,0,0,0,0,36,39,28,0,0,0,0,39,39,0,0,0,0,0,12,39,39,0,0,0,0,0,0,0,28,44,52,36,44,52,52,20,7,0,0,0,12,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,20,0,31,36,20,0,0,0,0,36,36,12,0,0,0,0,36,36,0,0,0,0,0,0,36,36,0,0,0,0,0,0,0,12,36,52,39,36,39,44,31,12,0,0,0,15,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,12,36,31,0,0,0,0,12,36,36,0,0,0,0,0,28,28,0,0,0,0,0,0,36,36,20,0,0,0,0,0,0,0,20,36,44,28,36,36,36,15,0,0,0,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,28,36,20,0,0,0,0,15,28,28,0,0,0,0,0,12,12,0,0,0,0,0,0,28,36,28,0,0,0,0,0,0,0,12,28,44,36,31,31,36,28,7,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,12,31,31,0,0,0,0,0,23,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,31,0,0,0,0,0,0,0,0,20,28,36,23,28,28,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,20,28,20,0,0,0,0,0,23,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,23,31,28,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,12,23,28,15,15,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,20,0,0,0,0,0,12,20,20,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,20,20,20,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,12,20,20,15,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,12,15,12,7,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,7,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,15,20,20,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_2[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,7,7,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,7,23,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,7,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,20,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,0,0,12,12,0,0,0,0,28,28,12,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,0,12,12,0,0,0,0,20,23,20,0,0,0,0,12,12,0,0,7,7,0,0,0,0,0,0,0,15,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,12,15,12,0,0,0,15,23,23,0,0,0,0,28,28,0,0,20,20,0,0,0,0,0,0,0,28,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,15,0,0,0,0,0,20,20,0,0,0,12,23,23,0,0,0,0,36,36,0,20,28,28,0,0,0,0,12,12,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,28,7,0,0,0,0,28,28,12,0,0,0,28,28,0,0,0,0,36,36,0,20,20,20,0,0,0,7,12,12,28,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,20,0,0,0,0,20,28,23,0,0,0,28,28,12,0,0,0,36,36,0,23,28,20,0,0,0,20,20,20,36,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,36,12,0,0,0,0,28,28,0,0,0,36,36,28,0,0,0,36,36,0,36,36,23,0,0,0,15,15,28,39,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,28,0,0,0,0,28,36,20,0,0,28,44,44,0,0,0,36,36,0,36,36,12,0,0,0,12,12,36,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,28,39,20,0,0,0,20,36,31,0,0,20,44,52,12,0,0,36,36,0,36,36,0,0,0,12,20,28,44,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,12,0,0,0,12,44,39,12,0,0,0,36,36,12,0,0,44,52,12,0,0,36,36,0,39,39,0,0,0,20,20,39,44,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,28,44,28,0,0,0,36,39,28,0,0,44,60,28,0,0,36,36,7,44,44,0,0,7,31,36,52,47,20,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,12,44,44,20,0,0,20,44,39,0,0,44,71,55,12,0,44,44,20,44,44,0,0,20,36,55,52,47,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,20,36,20,0,0,0,28,52,39,0,0,0,44,44,15,0,39,68,68,15,0,52,52,31,44,44,0,0,36,36,68,52,31,0,0,12,12,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,12,0,0,0,0,0,0,0,0,0,28,39,20,0,0,0,44,52,28,0,0,31,44,36,0,28,68,76,20,0,60,60,44,52,52,0,0,36,52,60,52,12,0,12,20,12,0,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,15,0,0,0,0,0,0,0,0,7,31,39,20,0,0,20,52,47,12,0,12,52,47,0,12,76,92,44,0,60,60,47,52,47,0,23,44,76,52,44,0,0,28,28,7,0,0,0,0,0,0,0,0,20,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,23,12,0,0,0,0,0,0,0,12,36,44,20,0,0,36,55,36,0,0,47,52,23,0,68,100,60,0,60,60,52,60,36,7,44,52,84,55,23,0,20,28,20,0,0,0,0,0,0,0,7,28,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,31,20,0,0,0,0,0,0,0,20,44,44,15,0,15,52,55,20,0,31,52,44,0,60,108,84,0,60,60,52,68,36,20,52,76,71,55,0,12,36,31,0,0,0,0,0,0,0,12,28,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,36,28,7,0,0,0,0,0,0,31,52,39,12,0,36,60,44,0,12,55,55,12,52,116,108,23,60,63,55,76,31,39,60,100,60,44,0,28,36,20,0,0,0,0,0,0,12,36,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,36,15,0,0,0,0,12,28,52,60,36,7,12,52,60,28,0,52,60,36,36,124,127,44,60,76,60,84,36,60,79,108,60,15,20,44,36,0,0,0,0,0,0,20,39,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,44,28,0,0,0,12,23,44,55,60,36,0,28,68,55,12,28,60,55,20,116,140,68,60,87,68,92,52,79,103,92,55,12,39,44,15,0,0,0,0,0,20,44,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,52,36,12,0,0,12,28,52,68,60,31,7,52,68,39,12,60,60,28,100,135,84,68,108,87,108,87,100,124,76,36,36,52,31,0,0,0,0,0,28,52,47,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,44,23,0,0,12,36,63,76,60,28,23,68,68,20,52,63,47,84,135,100,79,124,108,132,116,124,124,68,39,60,52,12,0,0,0,0,28,55,52,20,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,52,60,36,12,0,15,44,76,87,60,28,44,76,52,31,68,63,76,132,116,92,132,116,148,135,156,108,63,60,76,36,0,0,0,0,36,60,52,20,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,52,60,47,20,0,20,52,87,100,60,36,68,76,39,68,68,76,132,124,108,143,124,164,143,164,87,68,84,60,7,0,0,12,39,68,52,20,0,7,12,20,12,0,7,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,63,60,31,7,20,60,100,108,52,52,76,63,60,71,87,124,127,124,156,135,188,175,156,95,84,84,36,0,0,12,44,71,52,20,0,20,31,28,15,12,12,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,28,20,20,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,60,68,44,20,28,76,111,108,60,71,79,68,76,84,124,124,140,172,167,212,204,140,111,108,76,12,0,15,52,76,52,20,12,28,36,36,28,23,23,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,20,23,36,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,28,55,76,60,36,36,87,124,111,76,84,79,100,84,140,124,156,188,196,236,199,140,124,108,44,0,20,55,79,60,28,28,44,44,39,36,31,28,23,12,0,0,0,7,20,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,36,36,39,44,36,15,0,0,0,0,0,0,0,0,0,0,15,47,76,71,44,47,100,140,116,100,92,124,103,156,132,172,199,223,244,188,156,132,92,20,28,60,100,68,52,47,52,52,52,44,36,31,20,0,0,12,23,28,23,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,12,0,7,20,36,39,44,44,44,47,39,20,0,0,0,0,0,0,0,0,0,36,68,84,63,68,119,156,132,116,132,148,172,172,204,220,236,228,188,164,140,60,36,76,108,100,68,63,68,68,60,44,36,28,12,12,28,36,36,31,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,20,20,20,28,28,28,36,44,47,52,52,52,47,28,7,0,0,0,0,0,0,28,60,84,79,92,140,159,148,135,172,196,220,236,244,247,220,204,172,116,68,92,124,124,100,92,92,87,63,47,36,28,31,44,44,36,36,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,28,28,36,44,52,52,47,39,44,52,60,60,55,55,60,60,55,36,12,0,0,0,0,12,52,84,92,111,156,172,164,183,215,244,252,252,252,228,212,156,108,119,159,156,124,124,124,100,68,55,52,52,52,47,44,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,12,20,20,15,28,44,55,60,68,68,84,79,84,84,92,92,79,68,63,68,60,44,20,0,0,0,36,76,103,132,172,188,204,228,252,252,252,252,236,196,148,148,196,196,172,156,132,116,103,92,68,52,52,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,0,7,20,20,20,28,52,52,52,60,76,92,92,100,108,108,111,124,132,132,119,100,84,76,68,52,28,7,28,63,108,156,196,223,239,252,252,252,252,231,191,180,212,236,212,180,156,148,124,84,63,60,52,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,7,20,20,15,20,36,28,28,36,52,76,84,108,127,148,156,156,140,132,132,151,167,164,143,124,108,92,68,52,68,108,172,228,247,252,252,252,252,228,215,228,252,252,228,204,172,124,84,76,55,36,23,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,44,44,44,47,52,63,84,108,132,156,172,180,188,196,204,212,212,196,167,135,111,116,156,207,244,252,252,252,252,244,244,252,252,244,215,180,159,132,84,55,36,28,23,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,28,44,52,44,20,20,44,60,60,60,60,60,68,84,95,100,100,100,108,116,132,156,180,196,204,223,244,247,236,204,180,180,212,244,252,252,252,252,252,252,252,252,239,207,156,111,68,47,44,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,28,44,52,44,20,20,44,60,60,68,68,60,68,68,71,68,68,76,76,92,103,116,132,159,196,231,244,252,252,252,252,244,244,247,252,252,252,252,252,252,252,252,236,204,172,140,103,76,68,60,60,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,68,76,100,111,132,140,140,143,156,180,204,215,236,252,252,252,252,252,252,252,252,252,252,252,252,252,252,239,215,188,159,148,132,116,100,87,68,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,28,44,55,76,79,95,103,116,140,159,172,172,172,199,228,239,239,244,252,252,252,252,252,252,252,252,244,236,231,223,196,156,127,100,92,87,84,84,84,76,63,47,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,44,68,84,84,92,92,87,84,100,132,164,183,172,180,207,239,252,252,252,252,252,252,252,252,244,231,212,196,188,188,188,172,156,140,111,92,84,84,84,84,84,79,79,76,71,60,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,23,28,36,44,52,63,84,111,132,140,127,116,116,132,156,196,236,252,252,252,252,252,252,252,252,247,236,207,183,164,156,159,156,148,116,100,95,95,92,79,76,79,79,79,79,79,79,76,76,76,71,55,52,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,20,20,23,28,36,36,47,71,95,108,108,108,100,100,100,116,164,220,247,252,252,252,252,252,239,239,244,252,252,239,196,143,124,116,111,124,135,140,124,95,71,63,60,47,36,28,36,52,68,76,76,76,71,60,60,55,36,28,31,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,12,12,0,0,20,52,79,100,108,100,79,60,60,76,111,140,188,223,247,239,239,252,252,252,228,215,220,236,236,239,228,196,148,100,84,79,71,76,92,116,124,108,84,63,60,55,47,28,12,0,0,12,23,36,47,52,36,28,31,20,12,20,12,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,47,63,76,84,84,60,28,31,60,79,108,124,156,196,236,228,220,228,252,252,252,228,188,172,199,204,204,220,204,188,156,108,79,60,44,47,60,63,71,92,108,92,68,55,52,52,47,36,20,0,0,0,0,0,0,12,12,12,20,12,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,52,60,60,52,28,7,12,44,68,79,100,108,132,172,220,231,196,196,223,244,252,252,244,180,143,143,156,164,172,196,183,164,140,116,84,68,47,23,23,44,55,55,60,68,84,76,60,52,44,44,44,39,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,44,47,47,36,15,0,0,28,52,68,71,84,92,108,148,204,244,204,172,180,220,220,239,252,244,188,124,100,132,127,140,140,164,164,148,124,108,92,71,63,36,12,0,28,44,52,52,47,52,60,60,52,44,44,39,36,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,36,36,44,39,20,0,0,0,12,39,60,63,60,68,84,95,116,180,236,228,156,156,180,191,172,220,244,236,196,116,84,100,116,108,116,124,140,135,127,108,95,84,71,68,55,31,0,0,7,28,44,44,44,39,39,39,39,36,36,36,36,36,31,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,31,36,36,23,12,0,0,0,0,23,52,60,52,47,60,84,84,92,140,204,228,175,127,164,183,156,132,199,231,212,188,116,71,60,95,95,92,100,108,116,108,108,100,84,71,63,63,60,52,23,0,0,0,12,28,39,36,36,36,31,23,20,28,31,31,28,28,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,28,28,28,12,0,0,0,0,0,12,36,52,55,36,36,52,84,60,60,100,172,212,196,124,116,183,159,119,116,196,212,180,172,116,60,39,68,87,84,76,92,92,100,84,84,76,84,63,52,52,60,60,44,20,0,0,0,0,12,28,36,31,28,28,20,7,7,15,23,20,20,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,23,15,0,0,0,0,0,0,0,23,44,52,44,23,28,55,76,52,31,60,124,175,196,140,76,127,188,124,84,108,188,204,172,172,116,63,44,28,68,76,76,63,84,84,84,68,60,60,84,68,44,39,52,55,52,36,12,0,0,0,0,0,12,28,28,20,12,15,12,7,0,0,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,12,28,44,44,28,12,28,60,76,44,20,39,92,135,180,164,87,68,156,172,84,63,103,172,188,164,180,108,79,44,20,36,63,68,60,52,71,68,76,63,44,47,71,76,52,28,31,52,52,44,28,0,0,0,0,0,0,0,0,12,15,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,36,15,0,31,63,68,36,12,31,79,108,156,175,116,39,84,172,143,52,55,100,156,175,164,172,103,87,36,15,12,36,63,63,47,52,60,63,68,60,36,31,60,76,60,28,15,36,44,44,36,20,0,0,0,0,0,0,0,0,12,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,20,0,0,36,68,60,31,0,28,60,84,124,172,148,60,28,116,175,116,28,52,100,151,164,164,156,108,84,36,20,0,0,36,68,52,36,52,52,52,60,60,31,20,44,68,60,28,0,12,36,44,39,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,23,12,0,12,36,68,55,28,0,20,52,68,87,143,164,92,12,44,140,159,76,20,52,100,148,156,156,148,119,60,44,28,0,0,0,44,60,36,28,52,44,44,52,60,31,12,28,52,52,28,12,0,15,36,36,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,0,0,12,39,63,47,20,0,12,44,60,63,103,156,116,36,0,79,164,140,44,31,47,100,148,148,148,143,132,44,60,28,0,0,0,15,52,55,28,23,44,36,39,47,60,36,7,15,36,44,31,12,0,0,20,31,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,7,0,0,0,15,44,60,39,12,0,12,36,52,52,76,140,132,60,0,20,108,164,100,15,39,44,92,143,140,132,140,127,47,60,23,15,0,0,0,28,52,44,15,20,44,36,28,44,60,36,7,0,20,36,28,15,0,0,0,20,28,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,20,44,55,36,7,0,0,28,52,36,44,108,140,87,20,0,55,132,148,60,0,44,44,79,140,140,111,135,119,60,44,20,15,0,0,0,0,31,52,31,7,23,44,28,23,36,55,36,12,0,12,28,28,20,0,0,0,0,20,28,23,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,20,44,52,28,0,0,0,23,44,31,36,79,132,108,44,0,0,84,148,116,36,0,44,44,68,140,140,100,132,119,76,36,20,7,0,0,0,0,12,39,47,20,0,20,36,20,20,36,52,36,12,0,0,20,28,20,0,0,0,0,7,20,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,47,44,23,0,0,0,20,39,31,15,39,103,124,63,7,0,20,100,143,76,15,0,28,28,52,132,135,92,119,124,84,28,28,0,0,0,0,0,0,20,44,39,12,0,20,36,20,12,28,47,36,12,0,0,12,15,12,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,39,20,0,0,0,12,36,31,15,20,71,108,84,23,0,0,52,108,124,44,0,0,28,28,39,124,132,84,100,124,79,20,20,0,0,0,0,0,0,0,23,44,28,0,0,20,23,7,0,28,44,36,12,0,0,0,7,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,36,12,0,0,0,0,20,28,15,12,44,92,92,44,0,0,0,71,116,92,28,0,12,36,36,36,116,124,68,76,116,68,20,20,0,0,0,0,0,0,0,0,28,39,20,0,0,12,7,0,0,20,39,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,28,12,0,0,0,0,12,23,12,0,20,68,100,68,12,0,0,20,84,116,55,12,0,15,23,23,36,116,119,52,60,111,60,20,12,0,0,0,0,0,0,0,0,12,36,36,12,0,0,0,0,0,0,20,36,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,20,0,0,0,0,0,15,20,7,0,0,39,84,79,28,0,0,0,44,84,100,36,0,0,20,20,20,36,108,116,44,47,108,63,36,12,12,0,0,0,0,0,0,0,0,20,36,28,0,0,0,0,7,0,0,15,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,20,0,0,0,0,0,0,20,12,0,0,12,63,84,52,0,0,0,0,60,92,68,23,0,0,20,20,12,20,84,103,39,44,95,68,36,7,7,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,12,7,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,44,84,68,15,0,0,0,15,60,84,39,12,0,0,20,20,0,0,52,92,39,44,76,68,36,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,0,0,0,0,0,0,0,0,0,0,0,20,68,79,36,0,0,0,0,31,55,68,20,0,0,0,0,0,0,0,39,79,36,31,52,60,28,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,44,76,52,0,0,0,0,0,39,55,44,12,0,0,0,0,0,0,0,36,76,36,28,36,55,23,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,23,68,63,20,0,0,0,0,7,36,52,15,0,0,0,0,7,7,0,0,36,68,28,20,36,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,52,63,36,0,0,0,0,0,20,36,39,7,0,0,0,0,0,0,0,12,47,68,28,7,36,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,44,0,0,0,0,0,0,31,39,28,7,0,0,0,0,0,0,0,15,52,68,28,0,20,36,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,47,52,20,0,0,0,0,0,12,31,36,0,0,0,0,0,0,0,0,0,12,52,63,28,0,15,28,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,47,28,0,0,0,0,0,0,20,28,23,0,0,0,0,0,0,0,0,0,12,47,47,12,0,20,23,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,36,7,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,0,0,44,36,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,28,12,0,0,0,0,0,0,12,28,28,0,0,0,0,0,0,0,0,0,0,0,36,36,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,15,0,0,0,0,0,0,0,15,20,12,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_3[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,15,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,20,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,20,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,28,0,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,20,7,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,0,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,31,20,20,15,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,31,0,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,28,31,28,28,20,20,12,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,31,44,28,23,15,7,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,36,36,44,36,31,20,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,15,0,12,12,0,0,0,0,0,0,0,0,0,7,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,52,39,52,44,39,28,12,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,28,12,12,20,12,12,12,0,0,0,0,0,0,12,0,0,0,39,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,52,55,52,52,44,36,20,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,28,12,20,20,20,20,12,0,0,0,0,0,12,12,0,0,39,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,68,55,60,52,44,28,7,12,23,15,0,0,0,0,0,0,0,7,12,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,12,0,0,0,0,0,0,0,0,0,0,7,7,0,0,28,36,20,15,28,28,20,20,0,0,0,0,12,15,20,12,0,44,44,23,28,23,0,0,0,0,0,0,0,0,0,0,0,44,68,71,63,60,55,36,12,15,28,20,0,0,0,0,0,0,0,7,20,15,20,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,28,15,0,0,0,0,0,0,0,0,0,7,12,12,0,15,36,36,20,28,28,31,23,12,0,0,0,0,12,28,12,0,44,44,36,44,44,0,0,0,0,0,0,0,0,0,0,20,60,87,71,71,60,47,28,20,36,36,12,0,0,0,0,0,0,0,12,20,23,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,20,36,39,23,0,0,0,0,0,0,0,0,0,7,20,12,7,20,44,31,23,31,36,28,28,0,0,0,0,20,36,15,0,44,44,44,47,44,0,0,0,0,0,0,0,0,0,0,47,79,92,79,76,60,36,28,44,44,20,0,0,0,0,0,0,15,15,15,23,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,0,0,0,0,0,15,36,44,28,12,0,0,0,0,0,0,0,0,15,28,28,12,36,44,31,31,36,44,28,12,0,0,0,20,44,31,12,44,47,52,52,39,0,0,0,0,0,0,0,0,0,28,68,100,84,84,68,52,36,44,52,23,0,0,0,0,0,0,20,31,28,31,36,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,12,31,47,36,20,0,0,0,0,0,0,0,12,36,44,31,28,44,44,36,36,47,36,28,0,0,0,20,44,44,23,44,52,60,52,36,0,0,0,0,0,0,0,0,0,60,100,108,92,87,60,44,44,60,31,0,0,0,0,0,0,20,28,39,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,15,0,0,0,0,0,28,47,44,23,0,0,0,0,0,0,0,15,47,60,36,36,52,47,47,52,55,36,12,0,0,12,44,60,31,36,52,68,47,28,0,0,0,0,0,0,0,0,28,92,124,108,100,79,52,55,60,44,12,0,0,0,0,12,23,36,36,44,36,20,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,28,12,0,0,0,0,23,47,52,31,7,0,0,0,0,0,0,23,60,60,36,47,60,60,52,60,44,31,0,0,7,39,71,36,36,52,71,47,28,0,0,0,0,0,0,0,0,68,116,132,116,100,76,68,68,52,20,0,0,0,0,20,36,47,47,44,31,15,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,15,28,28,23,12,0,0,0,20,44,55,39,20,0,0,0,0,0,0,36,68,60,44,55,68,68,60,68,44,15,0,0,36,76,47,36,52,76,52,28,0,0,0,0,0,0,0,31,100,148,132,116,92,76,79,68,28,0,0,0,0,23,44,60,60,47,28,12,0,0,7,15,12,7,12,12,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,20,7,0,0,0,12,20,31,36,20,7,0,0,12,36,60,52,23,0,0,0,0,0,12,44,76,63,52,60,79,76,84,55,36,0,0,39,84,68,47,60,84,60,28,0,0,0,0,0,0,0,71,132,151,132,108,87,92,84,36,0,0,0,7,28,52,63,63,47,28,0,0,0,15,20,20,20,20,23,28,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,20,12,0,0,0,15,31,36,36,20,0,0,0,31,60,60,36,0,0,0,0,0,15,60,87,68,63,79,95,84,79,47,12,0,39,92,84,52,60,92,60,28,0,0,0,0,0,0,36,108,167,159,132,108,103,92,52,7,0,0,12,36,63,68,68,44,20,0,0,20,28,28,28,28,28,36,39,31,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,28,28,15,0,0,7,23,36,44,36,15,0,0,28,52,68,44,23,20,15,0,0,23,76,95,84,79,103,103,100,68,36,0,28,87,95,60,60,92,60,28,0,0,0,7,12,15,76,148,180,156,124,116,108,63,15,0,0,15,39,76,76,68,44,20,0,12,28,36,36,36,36,39,47,44,28,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,28,20,0,0,12,31,44,44,28,12,0,20,47,68,60,44,28,20,7,0,36,92,100,87,92,119,108,100,55,12,20,79,108,68,63,100,63,28,0,0,0,12,15,39,119,191,188,148,135,124,84,23,0,0,20,52,84,84,68,36,12,7,28,44,44,52,44,44,55,52,36,20,12,7,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,31,36,36,20,0,0,20,44,47,44,23,0,12,44,68,68,52,31,20,0,0,52,108,111,103,119,132,124,79,36,20,84,124,84,76,108,63,20,0,0,12,20,31,84,172,220,188,156,148,108,36,0,0,28,63,92,92,68,31,12,20,39,47,60,55,60,63,63,44,28,20,12,20,20,20,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,39,28,7,12,31,47,52,44,20,7,36,68,76,60,44,23,15,12,68,119,124,116,148,148,124,60,28,76,148,108,95,108,60,20,0,0,28,28,60,140,220,215,183,159,124,55,0,0,36,76,100,100,60,28,20,36,52,60,68,71,71,76,55,36,23,20,23,23,20,20,12,0,0,7,12,15,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,44,44,28,12,20,44,52,52,36,15,28,60,76,71,60,39,20,20,84,132,132,143,172,164,92,44,68,156,132,116,116,63,28,7,12,31,44,100,191,239,212,180,140,71,12,12,44,92,111,100,52,28,28,52,68,76,84,84,87,68,44,31,31,31,28,28,23,12,0,12,20,20,20,15,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,44,52,36,23,28,52,60,52,31,28,52,84,87,76,52,23,36,95,140,148,188,199,148,68,60,148,148,140,124,68,36,20,31,36,79,143,228,239,212,164,87,20,12,52,108,119,95,47,36,44,68,84,100,100,100,76,60,44,44,36,36,31,28,12,12,23,28,28,23,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,12,36,52,52,52,39,36,44,60,63,52,36,47,76,100,92,63,36,52,108,148,180,204,196,108,76,132,172,159,140,84,39,20,44,55,119,196,244,236,188,108,31,20,68,124,124,92,52,47,68,92,111,116,108,92,71,60,52,39,36,36,28,23,28,36,36,28,28,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,23,20,0,0,0,12,36,52,52,60,52,52,55,68,63,52,52,71,103,103,84,44,68,119,167,204,215,167,95,124,172,180,164,108,44,44,47,95,156,236,244,212,124,47,31,92,140,132,87,60,63,92,124,135,132,116,100,76,60,44,44,39,36,36,36,36,36,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,20,28,28,28,28,20,7,0,12,36,55,60,60,60,63,68,68,68,68,76,108,124,108,63,84,140,196,223,220,132,124,167,196,196,140,60,76,68,140,196,252,228,156,68,47,108,156,140,92,76,92,132,159,159,140,132,108,84,55,47,52,55,52,44,44,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,36,39,44,39,28,12,12,36,60,60,63,76,84,79,76,76,84,119,148,135,92,100,172,220,239,183,148,164,212,215,156,63,100,108,180,236,239,180,87,63,119,164,148,108,100,132,172,188,188,172,140,100,68,68,71,68,52,52,44,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,47,55,60,52,47,31,20,36,60,68,76,95,100,87,87,100,132,164,156,116,132,196,239,228,167,172,212,228,159,84,116,164,212,244,196,116,84,132,172,164,132,140,175,196,207,204,172,119,92,92,87,68,52,52,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,60,76,79,84,60,44,52,68,71,84,111,116,100,116,148,175,164,140,164,215,247,196,188,199,236,172,124,140,204,236,212,140,108,140,180,175,172,188,212,231,220,188,148,124,116,92,68,55,52,47,28,0,0,0,0,0,12,12,15,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,52,76,95,95,100,100,84,79,84,92,111,132,132,132,156,188,172,164,191,231,223,212,212,247,196,172,188,236,223,172,135,159,196,196,196,220,244,239,220,188,156,124,92,68,60,52,36,12,0,15,28,39,44,39,39,36,36,28,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,7,7,0,0,12,12,12,20,44,63,84,100,108,108,108,108,124,132,127,143,159,164,175,196,196,196,228,247,231,231,252,228,223,228,236,199,172,188,220,215,220,236,252,244,228,188,132,92,68,60,44,36,36,55,71,84,87,84,76,68,52,44,36,28,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,12,20,20,20,44,39,36,39,36,28,39,60,92,124,132,124,119,132,156,180,196,204,207,212,228,228,236,252,247,247,252,244,244,247,231,204,212,236,239,239,252,247,228,188,140,103,84,84,76,84,92,100,100,100,95,92,92,84,68,52,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,20,31,28,28,47,60,68,84,87,92,92,95,111,135,156,167,175,199,220,236,236,244,247,252,252,252,252,252,252,252,252,236,236,244,252,252,236,212,188,164,148,132,116,103,100,100,100,100,100,100,87,68,47,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,28,36,44,44,44,52,71,92,103,108,116,140,175,204,220,236,244,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,239,220,196,164,132,108,100,100,100,100,87,71,52,31,12,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,12,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,15,15,15,15,15,15,15,15,15,15,15,12,0,0,0,0,0,0,0,7,20,36,52,60,60,68,79,108,132,148,156,188,228,252,252,252,252,252,252,252,252,252,252,252,252,252,252,236,212,172,143,132,135,132,116,100,84,68,60,60,63,68,63,68,68,63,63,63,60,31,28,36,36,36,36,36,28,28,20,12,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,23,28,36,36,36,36,36,44,44,44,36,36,39,44,47,47,47,52,55,60,60,60,63,71,84,100,116,132,156,183,212,231,236,244,252,252,252,252,252,252,252,252,252,252,252,236,220,204,188,164,151,148,132,124,124,119,100,84,76,68,68,68,68,63,63,63,60,31,28,36,36,36,36,36,28,28,20,12,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,15,15,20,23,28,28,28,28,36,39,44,44,44,44,52,52,52,52,60,68,84,100,116,132,156,175,196,212,228,244,252,252,252,252,252,252,252,252,252,236,223,220,228,244,228,191,156,148,132,124,116,116,100,76,68,63,60,52,52,52,44,44,44,36,15,12,28,36,28,28,28,28,23,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,36,52,60,76,92,111,127,148,164,175,180,180,199,231,252,252,252,252,252,252,252,252,247,231,199,183,180,172,159,140,116,95,100,100,92,84,76,60,52,55,60,52,52,52,52,44,44,44,36,15,12,28,36,28,28,28,28,23,20,7,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,44,47,52,52,68,87,108,124,156,183,204,207,199,183,175,183,204,220,244,252,244,239,239,252,252,252,252,247,231,196,140,108,103,116,116,92,68,60,52,52,47,44,44,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,15,12,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,36,36,36,39,44,52,71,92,108,111,124,143,159,159,164,159,143,132,132,148,156,172,196,228,247,244,207,204,228,236,236,252,252,252,247,220,172,132,100,76,71,68,63,52,44,39,44,44,44,36,28,23,20,15,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,12,20,20,20,20,44,60,76,87,92,92,92,95,100,100,103,108,103,92,84,84,92,100,100,124,164,196,220,244,236,188,156,180,220,204,204,247,244,244,252,252,231,180,116,68,60,60,63,60,60,52,28,0,0,0,12,20,20,15,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,15,12,20,36,52,52,52,60,47,47,52,68,68,76,76,76,60,47,44,44,47,60,68,76,100,140,156,164,188,215,220,172,116,140,204,212,172,180,244,220,212,228,231,239,236,196,135,68,28,28,47,60,55,52,52,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,28,28,23,28,36,36,44,52,55,60,47,39,20,23,28,31,20,12,36,55,63,68,84,108,116,108,124,156,196,199,156,87,84,164,223,167,140,172,236,188,172,180,196,215,228,236,196,148,92,44,0,7,28,47,52,44,44,39,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,15,20,20,20,20,23,28,36,31,20,15,12,20,12,20,7,0,0,20,44,60,60,63,76,84,84,76,95,116,132,164,172,132,63,47,116,196,204,116,108,180,204,156,135,132,167,172,204,228,223,183,148,103,60,15,0,0,12,31,44,44,36,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,15,20,20,12,12,0,0,0,0,0,12,0,0,0,0,0,28,44,52,55,60,60,63,60,60,76,84,100,116,148,140,100,44,28,76,156,220,164,63,92,196,180,140,103,108,140,140,156,196,228,212,164,132,100,68,28,0,0,0,0,15,31,36,31,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,47,52,52,52,44,44,52,55,63,68,68,84,108,127,116,76,28,7,36,108,191,212,119,28,100,196,156,124,84,108,100,127,124,148,196,231,188,132,100,92,68,36,12,0,0,0,0,0,15,28,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,44,47,44,28,28,39,47,52,55,52,52,76,100,108,100,60,20,0,12,60,148,220,175,76,15,108,196,140,92,79,92,71,108,108,116,135,188,199,132,79,68,68,60,44,20,0,0,0,0,0,0,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,44,44,36,15,15,31,44,44,44,36,28,52,68,92,92,84,44,12,0,0,28,92,172,212,127,31,20,116,180,108,60,76,68,63,76,100,100,103,124,164,156,100,60,44,47,52,44,20,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,36,36,36,20,0,12,28,36,36,39,28,20,20,52,63,79,76,68,28,7,0,0,7,52,119,183,164,84,0,31,116,180,84,28,63,55,55,60,79,87,100,87,108,140,132,84,44,20,28,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,28,31,36,28,12,0,7,20,31,36,36,20,12,0,20,39,60,68,63,47,23,0,0,0,0,20,76,140,172,119,44,0,44,119,172,71,20,52,52,47,52,60,76,92,87,71,108,132,124,76,36,0,7,20,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,28,15,0,0,0,15,28,28,31,20,0,0,0,20,36,47,55,52,36,15,0,0,0,0,0,47,108,148,143,84,12,0,52,116,164,60,7,28,52,47,28,60,60,68,100,76,60,108,124,116,60,20,0,0,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,23,20,7,0,0,0,12,20,23,23,15,0,0,0,0,20,36,47,36,28,15,12,0,0,0,0,0,28,76,116,143,116,52,0,0,60,100,140,60,0,0,44,44,15,52,55,44,79,92,55,55,108,116,100,52,12,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,0,0,0,0,0,7,12,15,20,15,0,0,0,0,0,20,36,44,36,20,0,0,0,0,0,0,0,7,52,92,108,124,84,15,0,0,63,76,100,55,0,0,44,44,23,28,52,44,44,92,76,39,60,108,108,92,47,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,20,36,31,20,12,12,0,0,0,0,0,0,0,36,68,84,103,100,60,0,0,0,60,60,68,52,0,0,28,39,36,0,44,44,28,60,92,60,28,63,103,100,84,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,7,20,31,23,7,0,0,0,0,0,0,0,0,0,15,52,76,68,100,84,23,0,0,0,60,60,52,47,0,0,12,36,36,0,28,44,36,20,76,87,39,28,60,92,84,68,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,15,0,0,0,0,0,0,0,0,0,0,0,36,60,52,68,87,63,0,0,0,7,60,60,44,47,0,0,0,31,31,7,0,39,39,12,39,84,71,20,23,60,84,68,52,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,12,0,0,0,0,0,0,0,0,0,0,0,20,52,47,28,76,79,36,0,0,0,20,60,60,44,44,0,0,0,28,28,20,0,23,36,28,0,52,76,47,7,28,60,68,52,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,39,52,23,36,76,68,0,0,0,0,28,55,60,36,39,0,0,0,15,20,20,0,7,36,36,7,15,60,68,31,0,23,52,55,47,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,47,36,7,63,76,44,0,0,0,0,39,52,52,28,36,0,0,0,0,20,20,0,0,20,28,20,0,28,60,52,15,0,20,44,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,44,15,28,76,68,12,0,0,0,0,44,52,44,28,36,0,0,0,0,12,12,0,0,0,28,28,0,0,28,52,36,0,0,20,44,39,36,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,28,0,55,76,47,0,0,0,0,0,47,47,31,28,36,0,0,0,0,12,12,12,0,0,15,20,12,0,0,36,44,20,0,0,20,39,36,28,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,7,20,68,68,20,0,0,0,0,0,44,44,20,23,28,0,0,0,0,0,0,0,0,0,0,20,20,0,0,12,39,39,12,0,0,20,36,28,28,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,20,0,44,68,52,0,0,0,0,0,0,44,44,7,20,23,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,20,44,28,0,0,0,20,36,28,23,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,28,0,12,68,68,28,0,0,0,0,0,0,44,44,0,15,20,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,28,36,20,0,0,0,20,23,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,36,68,60,0,0,0,0,0,0,0,39,39,0,15,20,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,12,31,31,12,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,20,0,0,60,68,31,0,0,0,0,0,0,15,36,36,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,20,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,28,63,60,0,0,0,0,0,0,0,28,36,36,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,44,60,36,0,0,0,0,0,0,0,28,28,28,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,28,7,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,12,47,47,12,0,0,0,0,0,0,0,28,28,20,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,31,44,28,0,0,0,0,0,0,0,0,31,31,20,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,7,36,36,7,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,23,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,7,0,0,0,0,0,0,0,0,0,7,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_4[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,20,23,12,0,0,0,12,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,15,0,31,31,7,0,0,0,23,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,36,36,0,0,0,12,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,20,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,12,23,36,36,0,0,0,28,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,28,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,36,36,28,0,0,7,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,28,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,0,39,39,12,0,0,23,36,28,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,44,44,0,0,0,36,36,12,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,39,31,28,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,12,36,36,28,44,44,0,0,20,44,36,0,0,0,0,7,12,12,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,20,36,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,44,44,36,20,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,20,28,28,44,44,31,0,0,39,44,20,0,0,0,0,20,20,7,0,0,7,20,20,12,0,0,0,0,0,0,0,0,12,28,36,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,47,52,47,36,7,0,0,0,0,0,0,15,20,20,12,12,0,0,0,0,28,31,28,47,47,12,0,20,44,44,0,0,0,0,20,28,12,0,0,0,15,20,15,12,0,0,0,0,0,0,0,20,36,44,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,52,60,44,28,0,0,0,0,0,0,7,20,28,12,12,0,0,0,0,36,36,36,52,52,0,0,36,44,28,0,0,0,12,28,23,0,0,0,12,15,20,15,12,0,0,0,0,0,7,28,44,47,31,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,63,60,44,15,0,0,0,0,0,0,28,28,12,7,0,0,0,0,44,44,52,52,47,0,12,52,52,12,0,0,7,28,36,12,0,0,20,20,12,20,15,0,0,0,0,0,15,36,60,52,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,60,71,60,36,0,0,0,0,0,0,31,31,28,15,0,0,0,0,44,44,55,52,36,0,36,52,36,0,0,0,28,36,20,0,0,28,44,31,23,15,0,0,0,0,0,23,52,63,55,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,52,68,76,52,23,0,0,0,0,0,36,36,60,28,12,0,0,0,47,52,55,55,20,0,52,52,20,0,0,20,39,36,7,0,28,44,44,28,20,0,0,0,0,12,36,63,68,52,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,39,68,79,76,47,12,0,0,0,0,28,36,76,36,28,0,0,20,52,68,60,60,0,28,55,47,0,0,12,36,39,20,0,28,47,44,36,23,12,0,0,0,20,52,76,68,44,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,60,76,87,68,36,0,0,0,0,12,44,76,36,36,0,0,52,68,92,60,55,0,52,60,28,0,0,36,52,28,12,28,52,52,44,28,12,0,0,7,36,68,84,68,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,68,92,95,60,20,0,0,0,0,44,71,47,39,0,0,68,71,100,60,44,28,60,55,0,0,28,52,52,23,31,52,60,52,28,12,0,0,20,52,84,87,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,60,84,100,92,52,12,0,0,0,44,63,68,44,0,0,68,76,100,60,23,52,60,36,0,20,52,60,39,39,60,68,60,36,12,0,0,31,76,100,84,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,76,100,116,87,44,12,0,0,44,55,92,44,0,0,68,84,92,68,28,63,63,12,7,44,76,52,44,68,76,68,36,12,0,12,55,100,111,79,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,52,92,124,124,92,44,7,0,28,52,100,52,7,28,84,124,79,63,47,68,52,0,36,76,76,68,76,87,76,36,12,0,28,79,124,111,76,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,23,68,111,140,124,76,28,0,7,52,108,60,20,60,108,156,71,63,68,68,23,23,71,87,84,84,92,84,44,15,12,52,100,127,108,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,23,20,12,0,0,12,7,0,0,0,0,0,0,0,0,31,95,140,156,127,60,12,0,60,116,76,36,71,111,156,71,68,71,60,20,60,95,100,92,108,92,44,20,28,76,124,132,92,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,31,28,20,12,15,12,0,0,0,0,0,0,12,52,127,175,172,116,39,0,52,116,100,52,100,140,143,84,76,76,44,47,100,119,108,124,103,52,23,44,100,140,124,76,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,15,0,0,0,12,20,31,36,39,44,31,28,28,23,12,0,0,0,0,0,20,84,164,207,172,87,20,44,108,124,63,132,180,135,108,76,79,52,100,132,132,135,111,60,36,68,119,140,108,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,36,28,31,28,12,7,7,20,47,68,60,60,44,36,36,23,7,0,0,0,0,36,124,207,220,148,60,28,100,135,84,148,212,140,132,84,92,108,148,164,159,124,68,52,87,132,127,84,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,44,63,68,55,36,12,20,47,68,79,76,76,60,47,39,20,0,0,0,0,68,164,228,212,116,36,92,143,108,164,228,164,135,111,124,172,191,188,148,87,76,108,132,108,60,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,15,20,36,60,68,71,76,76,63,47,44,60,84,92,100,92,76,60,36,15,0,0,20,103,199,244,175,76,87,148,140,172,231,191,140,151,183,220,220,175,124,103,124,127,92,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,20,20,20,15,12,12,15,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,20,28,36,60,84,84,84,84,84,79,76,71,76,92,108,124,124,92,63,36,12,0,44,140,223,223,132,95,140,164,188,231,199,164,188,236,244,204,151,127,132,116,68,20,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,36,31,28,28,39,52,52,44,31,31,28,20,20,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,20,28,31,44,47,63,103,124,124,108,92,84,92,100,108,116,132,148,140,100,63,28,12,68,164,228,188,124,143,191,212,239,212,204,223,244,220,180,151,132,95,44,0,0,0,0,0,0,0,0,0,20,36,44,44,44,44,52,63,84,92,92,84,79,68,60,47,36,31,23,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,44,52,76,76,87,116,143,148,132,108,108,124,148,148,156,164,148,108,60,36,84,180,220,159,164,204,236,252,236,236,244,228,196,164,119,71,20,0,0,0,0,0,0,20,39,52,52,55,68,84,100,116,124,116,95,87,84,79,79,79,63,47,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,52,71,79,79,92,119,151,164,156,148,164,183,183,188,188,156,108,76,116,196,196,196,212,252,252,252,252,247,220,172,108,52,12,0,0,0,28,44,68,76,92,100,116,135,140,140,119,100,92,92,92,84,79,68,47,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,79,84,100,124,164,188,196,204,212,228,228,204,148,124,156,196,228,228,252,252,252,252,228,180,100,39,12,28,52,76,100,124,143,159,164,156,143,124,108,92,92,87,79,68,44,28,12,0,0,0,0,0,12,20,15,7,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,7,0,12,23,31,36,47,60,76,111,143,164,183,191,204,220,236,252,231,191,180,207,239,247,252,252,244,212,164,116,87,84,108,132,148,156,164,167,164,148,132,124,116,111,100,87,71,60,52,52,60,68,76,76,52,39,47,60,52,39,20,12,0,15,31,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,7,0,12,23,31,36,44,52,52,76,100,116,143,172,204,228,239,252,252,247,228,228,244,252,252,252,236,204,164,148,159,183,207,223,228,207,196,183,172,164,148,140,132,135,140,148,151,148,148,140,148,135,119,108,76,60,68,103,108,76,44,36,20,28,44,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,44,47,63,84,92,100,100,116,140,164,196,220,239,252,252,252,252,252,252,236,220,215,236,247,252,252,244,228,207,199,196,204,204,204,204,204,207,204,199,196,191,175,156,156,135,108,84,60,44,44,71,76,44,20,20,20,20,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,52,63,76,84,92,92,92,108,127,143,156,172,180,196,220,244,252,252,252,252,252,252,252,252,252,252,252,239,236,236,236,228,212,207,204,204,191,172,156,140,127,124,108,92,84,76,68,52,36,28,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,39,52,60,71,84,87,92,92,92,92,92,92,92,95,108,132,148,167,188,191,188,172,164,180,204,228,244,252,252,252,252,252,252,252,252,252,252,252,236,215,196,172,156,140,143,148,148,132,116,92,84,79,76,76,71,76,76,63,63,60,52,44,36,44,36,28,28,15,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,12,44,60,52,36,55,84,92,92,92,92,92,92,92,92,92,92,92,103,124,140,148,143,135,124,116,108,108,103,108,119,148,175,204,228,247,252,252,252,252,247,247,252,252,252,252,252,228,196,164,140,116,100,87,76,60,44,44,52,52,52,60,60,60,60,52,44,44,44,31,23,20,36,36,28,28,15,0,0,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,28,20,28,60,79,60,39,55,84,92,92,87,84,76,68,68,71,84,92,87,79,76,76,76,76,76,76,68,52,52,68,100,132,172,196,215,236,252,244,236,236,252,228,228,231,252,252,252,252,244,220,188,148,116,92,76,55,52,44,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,0,0,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,28,28,28,20,23,47,60,44,28,23,28,20,12,20,36,52,60,68,68,68,68,68,71,71,68,52,31,12,0,20,60,103,132,159,172,172,172,204,236,252,212,204,212,236,220,191,196,236,252,252,247,231,207,191,167,127,100,87,84,71,52,28,20,15,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,12,7,0,0,0,0,0,12,28,36,36,44,44,52,60,60,68,68,63,52,31,12,0,0,0,0,28,60,92,124,148,156,148,124,132,167,212,244,244,172,167,199,220,204,172,148,188,244,247,236,228,220,196,164,148,132,92,60,44,44,44,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,20,23,28,36,36,36,44,44,52,44,28,12,0,0,0,0,0,0,0,28,60,84,108,124,132,124,100,84,108,143,180,215,244,212,132,140,212,196,180,180,116,124,199,244,231,212,204,196,188,159,132,108,87,60,44,28,20,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,20,20,20,20,23,28,36,28,12,0,0,0,0,0,0,0,0,0,0,7,31,55,76,100,108,100,92,84,68,63,95,140,164,175,212,236,164,95,132,220,175,156,188,108,92,124,212,236,204,183,188,188,180,156,124,95,76,55,31,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,20,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,68,84,92,76,68,63,55,47,52,87,143,156,148,180,220,204,116,60,132,212,148,148,180,132,84,63,143,220,220,188,151,164,180,172,135,108,84,68,52,39,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,47,60,71,76,60,52,47,47,44,36,39,87,148,151,132,143,183,212,156,63,31,124,180,103,127,156,140,84,52,63,164,220,204,143,108,116,156,167,148,108,76,52,39,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,52,60,60,39,28,31,36,36,36,20,31,92,156,148,116,119,156,180,172,108,23,28,127,156,60,95,140,127,92,68,20,79,172,207,164,111,71,76,124,156,156,124,76,36,23,28,28,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,39,44,44,31,20,15,12,20,23,28,20,12,31,95,156,140,95,100,140,151,172,140,63,0,28,116,140,44,68,132,108,100,84,31,20,100,180,196,140,92,60,55,92,140,156,140,103,52,20,12,20,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,36,36,36,20,0,0,0,7,7,15,20,12,0,36,100,148,135,76,84,124,132,148,156,100,20,0,31,100,127,31,52,119,108,100,79,60,0,31,116,183,180,124,84,52,39,68,108,140,143,116,76,28,0,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,28,31,28,15,0,0,0,0,0,0,7,0,0,0,39,103,148,132,68,68,108,127,116,148,124,60,0,0,36,87,116,28,44,95,116,84,76,76,23,0,44,132,180,156,103,79,39,28,44,79,116,132,124,92,47,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,23,28,20,12,0,0,0,0,0,0,0,0,0,0,0,44,108,148,124,55,52,95,119,108,124,135,92,20,0,0,44,84,108,20,28,63,116,71,68,76,55,0,0,60,140,164,124,92,76,36,20,28,60,84,108,116,100,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,47,108,140,116,52,36,84,108,103,92,132,116,60,0,0,0,52,84,92,15,12,44,108,68,52,76,71,20,0,12,68,148,156,108,68,68,39,12,20,39,60,84,100,100,76,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,111,132,108,44,20,60,100,100,79,108,124,92,20,0,0,0,60,84,79,12,0,36,92,76,55,55,76,47,0,0,20,84,148,132,76,55,68,39,12,12,28,44,60,76,84,76,60,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,55,108,124,100,36,7,31,79,87,76,79,124,116,60,0,0,0,0,60,84,68,0,0,28,60,76,63,28,71,68,12,0,0,28,92,132,95,52,44,60,36,12,0,20,36,39,52,76,79,63,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,60,108,111,87,36,0,15,55,68,68,60,100,119,95,20,0,0,0,0,60,87,55,0,0,20,31,84,63,20,60,68,39,0,0,0,28,92,111,76,39,36,60,36,12,0,12,20,28,36,55,68,68,52,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,60,108,108,76,28,0,7,44,63,60,60,68,111,116,60,0,0,0,0,0,55,76,31,0,0,12,20,76,60,36,28,68,60,7,0,0,0,36,92,103,63,31,36,55,36,12,0,0,12,20,28,36,52,63,52,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,92,92,68,28,0,0,31,60,52,52,52,84,116,92,20,0,0,0,0,0,52,60,7,0,0,0,12,52,60,52,0,60,68,31,0,0,0,7,44,92,95,52,23,31,52,36,12,0,0,0,12,12,20,39,52,52,39,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,79,76,60,20,0,0,20,52,52,44,52,60,100,108,55,0,0,0,0,0,12,52,52,0,0,0,0,12,28,60,60,0,31,63,55,0,0,0,0,12,52,92,92,44,20,28,47,31,12,0,0,0,0,0,12,20,36,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,63,60,52,20,0,0,12,44,52,39,44,47,71,108,92,20,0,0,0,0,0,23,52,52,0,0,0,0,0,12,60,60,23,0,52,60,23,0,0,0,0,20,55,87,79,36,12,23,44,36,12,0,0,0,0,0,0,7,20,28,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,47,36,20,0,0,0,36,52,36,36,44,44,84,100,52,0,0,0,0,0,0,36,52,52,0,0,0,0,0,0,52,55,44,0,28,52,39,0,0,0,0,0,20,60,79,68,28,7,20,44,31,12,0,0,0,0,0,0,0,0,20,15,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,44,36,20,0,0,0,28,52,36,28,39,36,60,95,76,15,0,0,0,0,0,0,44,47,44,0,0,0,0,0,0,28,52,52,0,0,39,44,15,0,0,0,0,0,28,63,76,60,23,0,20,36,28,12,0,0,0,0,0,0,0,0,0,12,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,20,20,15,0,0,0,20,44,44,20,31,36,36,76,92,44,0,0,0,0,0,0,0,44,44,36,0,0,0,0,0,0,7,52,52,12,0,20,36,28,0,0,0,0,0,0,28,63,68,47,20,0,15,28,28,12,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,12,36,44,20,20,36,28,44,84,68,15,0,0,0,0,0,0,0,44,44,28,0,0,0,0,0,0,0,47,52,28,0,0,36,36,12,0,0,0,0,0,12,31,60,63,39,15,0,12,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,28,44,23,12,28,28,28,68,79,39,0,0,0,0,0,0,0,0,36,36,12,0,0,0,0,0,0,0,31,47,44,0,0,20,31,23,0,0,0,0,0,0,12,36,60,55,36,12,0,12,28,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,0,0,0,0,20,39,28,7,20,28,20,36,76,60,12,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,12,44,44,0,0,0,28,28,7,0,0,0,0,0,0,15,28,44,47,28,7,0,12,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,12,0,0,0,0,0,0,0,12,36,31,12,12,28,20,15,52,68,36,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,44,44,15,0,0,20,28,20,0,0,0,0,0,0,0,12,28,44,44,23,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,7,28,36,12,0,15,20,12,31,68,52,12,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,36,44,36,0,0,0,23,23,0,0,0,0,0,0,0,0,20,31,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,20,0,0,12,0,12,52,60,31,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,20,44,44,0,0,0,15,20,12,0,0,0,0,0,0,0,0,12,28,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,20,0,0,12,12,0,28,60,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,39,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,12,23,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,12,12,0,12,44,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,20,0,0,0,12,15,7,0,0,0,0,0,0,0,0,7,12,20,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,7,0,0,23,47,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,31,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,7,15,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,0,0,0,0,0,0,7,31,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,36,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,15,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_5[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,15,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,15,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,31,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,0,0,0,0,20,20,20,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,20,20,0,0,0,0,23,23,7,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,36,36,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,15,12,7,0,0,0,0,0,0,7,36,36,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,28,28,0,0,0,23,36,36,0,12,12,0,0,0,0,0,20,20,0,0,0,0,12,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,44,44,0,0,0,36,36,28,0,12,12,0,0,0,0,15,28,20,0,0,0,0,28,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,12,23,15,15,0,39,39,0,0,0,39,39,20,12,12,12,0,0,0,0,28,28,7,0,0,0,20,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,15,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,7,20,44,28,28,0,44,44,0,0,0,44,44,0,12,12,0,0,0,0,20,28,20,0,0,0,15,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,12,28,44,36,12,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,12,28,31,31,0,44,44,0,0,12,44,47,0,12,12,0,0,0,7,31,36,7,0,0,12,36,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,47,31,12,0,0,12,28,20,0,0,0,0,0,0,0,0,0,15,20,36,36,0,52,52,0,0,31,52,52,20,20,15,0,0,0,23,36,23,0,0,0,28,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,52,28,0,0,0,23,28,12,0,0,0,0,0,0,0,0,23,28,39,28,0,52,52,0,0,44,60,47,28,23,0,0,0,12,36,36,12,0,0,23,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,52,20,0,0,12,28,23,0,0,0,0,0,0,0,0,23,23,47,28,0,52,52,0,0,60,76,55,31,31,0,0,0,28,44,28,0,0,20,44,39,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,20,52,60,52,20,0,0,28,36,15,0,0,0,0,0,0,0,23,28,68,39,0,52,52,0,0,76,84,63,36,31,0,0,12,44,44,12,0,12,39,52,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,0,0,0,0,0,7,23,60,68,44,12,0,15,36,31,0,0,0,0,0,0,0,20,28,76,44,12,55,55,0,20,92,92,60,36,20,0,0,36,44,28,0,0,36,52,44,31,28,12,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,0,0,0,0,0,0,15,20,15,31,68,71,36,7,0,28,36,20,0,0,0,0,0,0,12,36,84,44,23,60,60,0,44,92,92,52,39,0,0,15,52,47,12,0,28,60,60,47,36,12,12,20,12,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,12,23,28,20,0,0,0,0,15,31,23,23,44,79,76,28,0,15,44,39,7,0,0,0,0,0,0,39,92,47,36,55,52,0,76,108,100,44,39,0,0,36,52,31,0,20,52,71,63,47,20,15,31,31,12,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,0,0,0,12,31,44,31,12,0,0,0,20,39,28,28,60,87,68,23,0,31,44,28,0,0,0,0,0,0,44,92,55,44,55,52,20,108,119,100,44,28,0,20,52,52,12,7,44,76,79,60,36,20,36,44,20,0,0,0,0,0,7,20,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,15,0,0,0,12,36,55,44,20,0,0,0,28,44,31,36,76,95,60,15,12,44,44,12,0,0,0,0,0,44,92,71,52,60,52,36,124,132,95,44,12,0,44,60,36,0,36,76,92,76,39,28,44,47,20,0,0,0,0,0,15,28,36,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,23,23,12,0,0,12,36,60,60,31,0,0,0,28,44,36,52,92,100,47,7,36,52,36,0,0,0,0,0,44,87,87,63,63,52,60,135,148,76,47,0,20,60,55,12,28,76,108,100,60,39,52,63,31,0,0,0,0,7,23,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,12,0,0,31,60,68,44,12,0,7,28,52,52,68,108,95,36,20,52,55,36,12,0,0,0,44,84,103,71,63,47,79,140,148,60,39,0,47,60,36,15,60,111,119,84,60,63,84,47,12,0,0,0,15,36,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,31,36,23,12,0,23,52,71,52,20,0,12,39,68,68,87,116,92,28,36,55,63,28,12,0,0,36,76,108,76,60,52,100,140,140,55,20,23,68,60,20,52,108,132,108,84,84,92,60,20,0,0,0,28,44,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,20,0,15,47,76,60,28,0,12,47,76,84,111,132,87,31,52,71,52,28,0,0,20,76,116,84,68,63,116,156,119,55,7,52,68,47,44,108,143,132,100,92,92,60,20,0,0,20,36,52,44,20,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,36,20,12,44,71,71,44,12,15,55,92,100,135,132,68,44,63,84,47,28,0,7,84,132,108,71,84,127,172,100,52,28,68,68,60,111,164,159,124,100,100,52,12,0,0,28,52,52,36,12,0,0,0,0,0,0,0,12,20,12,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,47,36,28,36,68,76,52,20,20,63,100,119,148,124,60,60,84,79,44,12,0,76,140,124,68,100,132,180,84,36,52,68,68,100,172,188,156,124,116,71,20,0,15,44,60,52,28,0,0,0,0,0,12,20,28,36,28,23,23,20,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,52,52,39,44,68,84,63,28,28,68,108,140,156,108,68,76,108,68,31,0,68,140,164,92,116,148,164,68,44,68,84,100,180,212,180,156,140,95,36,12,28,52,60,44,20,0,0,0,12,20,28,44,44,36,39,44,36,23,20,28,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,55,63,60,60,68,92,76,44,36,71,119,156,148,100,79,100,108,60,12,60,140,199,116,132,172,148,68,60,84,108,164,223,212,180,156,108,44,23,44,63,60,36,12,0,0,15,28,44,55,52,47,52,55,44,36,44,44,28,20,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,63,76,76,92,95,87,52,52,76,132,164,148,103,100,140,108,47,52,140,228,143,148,196,132,79,76,116,156,223,244,220,172,116,52,39,60,68,52,28,0,7,20,39,60,71,68,68,68,63,52,55,60,52,36,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,84,100,108,111,103,68,60,84,148,180,140,111,135,148,84,52,140,236,164,167,196,124,92,108,151,207,252,244,204,124,68,60,68,68,44,15,7,23,52,71,84,84,84,84,76,71,76,68,52,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,103,124,132,124,87,79,108,180,196,151,140,175,127,68,127,228,180,183,196,140,116,156,196,244,247,212,143,92,79,84,68,36,20,36,68,84,100,103,108,100,95,92,84,71,52,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,63,100,140,159,148,111,95,135,196,196,164,183,164,92,119,220,196,191,204,167,164,196,231,247,212,156,108,100,92,60,39,44,84,108,124,132,132,124,124,116,100,76,47,36,20,0,0,0,0,0,0,0,0,12,15,20,20,20,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,39,92,132,172,175,148,124,164,204,196,188,196,127,127,204,228,220,228,204,212,223,247,212,164,124,103,84,68,68,100,132,156,167,180,167,159,140,116,84,60,36,7,0,0,0,0,15,28,36,36,39,44,47,39,31,28,20,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,12,20,23,28,44,76,116,164,196,180,159,183,204,212,212,159,156,191,244,239,244,236,239,239,220,180,140,108,95,92,111,151,188,204,212,188,172,143,119,87,60,28,7,0,20,36,60,68,79,84,76,60,52,52,47,47,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,15,12,20,12,12,15,23,36,47,68,92,119,156,196,199,188,204,220,236,204,183,196,252,252,252,252,247,228,191,148,124,124,132,164,207,236,223,196,164,140,108,71,52,36,44,60,79,92,100,100,100,103,100,95,84,60,36,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,15,12,20,23,36,36,44,52,55,60,76,100,127,164,191,204,212,228,244,236,220,220,252,252,252,252,244,212,175,156,159,172,212,231,215,180,148,124,108,92,79,84,92,116,124,132,140,148,140,119,108,92,60,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,23,36,36,44,44,52,76,108,132,140,156,180,212,223,236,247,252,244,244,252,252,252,252,244,220,196,196,212,212,196,180,164,148,132,124,127,143,164,175,180,183,175,159,135,116,84,39,23,28,20,7,0,0,0,0,0,0,0,12,12,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,47,52,63,92,143,188,204,215,236,247,252,252,252,252,252,252,252,252,247,236,228,236,228,207,196,180,167,172,188,204,215,228,236,223,199,172,132,116,116,108,84,52,44,52,44,44,36,28,12,0,20,28,28,23,20,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,55,76,108,140,172,196,223,244,252,252,252,252,252,252,252,252,252,252,244,228,212,212,228,244,247,244,236,215,204,196,180,159,124,87,68,79,76,60,52,44,47,44,39,36,28,12,0,20,28,28,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,92,132,167,212,244,252,252,252,252,252,252,252,252,252,252,252,244,239,231,228,220,204,196,183,172,156,148,135,116,100,92,92,87,84,79,68,52,36,28,20,15,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,23,39,60,76,92,116,143,172,180,196,220,244,252,252,252,252,252,239,228,223,239,252,244,220,183,151,132,108,92,76,68,60,52,39,36,36,36,36,36,36,36,31,28,20,12,12,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,76,92,108,124,140,159,175,191,212,236,252,252,252,252,252,252,252,252,252,252,239,215,188,164,164,180,172,156,132,108,87,84,84,76,76,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,60,79,92,100,116,132,151,164,180,191,220,236,252,252,247,236,231,236,244,252,252,252,252,252,252,252,252,244,212,172,148,127,103,92,103,116,116,116,111,100,87,84,76,71,47,44,44,28,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,23,52,60,68,92,100,100,108,132,148,156,172,180,188,196,212,212,231,244,236,212,199,188,188,204,231,239,236,236,252,252,252,252,252,247,236,204,164,132,108,71,44,36,36,36,36,36,47,60,63,68,47,44,44,28,20,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,36,47,55,63,84,84,92,127,148,143,140,124,124,132,140,140,140,148,172,191,199,180,172,159,140,132,156,188,204,204,204,228,252,252,244,236,236,247,223,196,180,164,140,119,84,36,7,0,12,12,15,12,0,0,0,0,12,12,15,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,28,36,36,39,44,55,76,92,108,103,87,79,71,76,92,95,100,92,84,100,127,148,148,132,127,124,108,92,100,124,151,180,172,156,188,228,244,236,236,212,212,236,228,196,172,156,140,124,111,95,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,15,20,28,36,44,60,60,55,52,44,36,52,68,76,68,60,52,47,63,84,100,103,95,87,87,84,76,60,68,76,95,124,148,148,124,151,212,223,223,220,228,196,188,207,239,207,172,156,132,108,92,92,92,76,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,28,28,23,20,20,28,36,44,52,44,28,28,31,36,39,52,60,68,68,68,68,68,60,52,44,52,44,52,76,108,124,124,100,116,180,220,204,204,196,204,188,148,159,228,228,188,143,127,119,92,71,68,76,76,68,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,20,20,12,15,15,23,20,20,28,23,28,44,55,55,60,55,47,36,36,36,39,28,20,28,63,84,100,111,84,76,127,212,196,180,196,188,180,196,119,111,196,223,196,156,100,87,92,84,52,47,60,76,76,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,12,36,47,52,52,52,44,28,20,20,28,31,23,12,0,28,52,68,79,100,84,55,84,172,220,164,164,204,175,164,212,124,76,148,199,188,164,132,68,55,76,92,60,31,36,60,71,68,52,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,44,47,44,28,12,0,12,20,23,20,7,0,0,23,44,47,55,76,84,44,52,124,196,204,140,148,223,164,124,196,124,60,100,180,180,156,143,108,52,36,68,84,63,28,12,36,60,68,68,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,44,44,36,15,0,0,0,0,12,12,0,0,0,0,28,44,39,36,52,68,44,36,84,148,199,172,124,140,231,156,84,164,116,68,52,148,188,148,140,132,92,44,20,47,76,68,36,7,12,36,60,63,60,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,31,36,36,36,20,0,0,0,0,0,0,0,0,0,0,0,7,23,39,28,28,36,52,28,12,60,108,156,188,132,103,143,228,143,55,119,108,87,20,100,172,164,119,124,124,76,28,0,31,63,71,44,15,0,12,39,60,60,55,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,28,28,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,20,20,31,47,28,0,36,84,124,159,164,95,92,151,212,140,52,92,111,100,12,47,132,167,132,100,111,111,68,23,0,20,52,68,52,20,0,0,15,44,60,55,52,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,23,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,15,12,28,44,28,0,20,68,100,116,172,140,68,95,164,188,132,44,68,116,100,31,12,84,148,132,100,92,103,100,60,20,0,12,44,68,60,28,0,0,0,20,44,52,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,12,0,20,39,28,0,0,44,76,100,124,156,108,52,100,172,172,124,44,47,108,100,52,0,36,95,132,95,84,84,95,87,52,12,0,0,31,60,60,36,12,0,0,0,20,44,52,44,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,15,31,20,0,0,28,68,84,92,148,135,68,52,100,180,164,116,36,36,92,84,55,0,12,63,108,103,79,68,84,84,76,44,0,0,0,20,47,60,44,15,0,0,0,0,20,39,39,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,12,52,71,76,100,140,108,36,68,100,180,159,111,20,20,60,76,68,0,0,36,76,108,76,68,63,76,68,63,31,0,0,0,12,36,55,44,20,0,0,0,0,0,20,31,31,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,0,0,0,36,60,71,68,116,124,76,12,84,108,151,148,92,12,12,39,68,68,12,0,12,52,84,84,68,55,60,60,52,52,23,0,0,0,0,28,52,52,28,0,0,0,0,0,0,20,28,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,55,60,60,79,116,100,44,12,92,111,127,140,76,12,15,28,52,52,12,0,0,28,52,79,68,55,47,60,44,39,44,20,0,0,0,0,20,44,52,28,12,0,0,0,0,0,0,20,23,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,55,52,47,92,108,71,20,15,79,108,108,127,68,15,28,28,44,47,15,0,0,7,44,63,76,60,44,52,52,36,36,36,12,0,0,0,0,12,31,44,36,12,0,0,0,0,0,0,0,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,52,44,60,92,92,44,0,31,76,108,100,124,68,0,20,20,28,31,20,0,0,0,28,44,68,60,47,36,52,39,28,31,28,7,0,0,0,0,0,28,44,36,20,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,52,36,36,76,87,71,28,0,44,68,108,95,116,68,0,0,0,20,28,28,0,0,0,7,44,52,60,55,36,31,47,28,20,20,7,0,0,0,0,0,0,20,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,36,28,44,71,87,44,7,0,52,63,100,100,108,63,0,0,0,7,20,20,0,0,0,0,28,39,52,52,52,23,36,44,20,0,12,7,0,0,0,0,0,0,12,28,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,44,28,28,60,71,68,28,0,0,44,47,84,108,92,60,0,0,0,0,20,20,0,0,0,0,7,36,39,52,52,36,15,36,36,12,7,20,12,0,0,0,0,0,0,0,20,31,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,44,28,20,36,60,76,44,15,0,12,47,44,63,108,76,52,0,0,0,0,20,20,0,0,0,0,0,20,36,39,44,47,20,20,36,28,12,12,15,0,0,0,0,0,0,0,0,12,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,28,15,20,44,52,60,20,0,0,7,31,31,52,108,68,44,0,0,0,0,12,12,0,0,0,0,0,0,31,31,36,47,36,7,20,36,20,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,15,20,23,52,55,36,12,0,0,12,28,36,44,108,60,36,0,0,0,0,0,12,12,0,0,0,0,0,20,28,28,36,44,20,0,28,31,12,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,20,7,12,31,44,52,15,0,0,0,7,20,36,44,103,60,28,0,0,0,0,0,7,7,0,0,0,0,0,0,28,28,28,44,36,7,12,28,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,28,0,12,12,39,44,36,12,0,0,0,12,12,39,39,100,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,20,28,39,28,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,20,36,44,12,0,0,0,0,12,12,44,36,95,55,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,15,36,36,12,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,20,0,0,0,28,36,28,0,0,0,0,0,0,0,39,36,92,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,20,36,28,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,23,0,0,0,7,31,36,12,0,0,0,0,0,0,0,36,44,76,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,12,28,36,12,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,12,0,0,0,20,28,28,0,0,0,0,0,0,0,0,28,39,68,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,12,28,28,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,15,0,0,0,0,28,28,12,0,0,0,0,0,0,0,0,15,36,55,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,12,28,23,0,0,0,0,0,0,0,0,0,12,39,52,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,7,28,60,52,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,12,20,60,47,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,7,52,47,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,52,47,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,12,12,55,44,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,12,12,52,44,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_6[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,23,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,44,0,0,0,0,0,0,0,7,12,7,0,7,7,0,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,44,0,0,0,0,0,0,0,12,12,0,0,7,7,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,36,0,0,0,0,0,0,0,12,12,0,12,12,7,0,0,0,12,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,31,0,0,12,12,0,0,0,12,12,0,12,12,0,0,0,0,20,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,47,20,0,28,28,0,0,12,20,20,0,20,20,0,0,0,12,23,20,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,76,44,0,44,39,7,0,20,20,20,20,28,23,0,0,0,23,28,12,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,7,0,0,0,68,84,60,0,39,36,0,0,23,23,15,20,20,12,0,0,12,28,28,0,0,0,12,12,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,60,76,63,0,36,28,0,0,28,28,20,28,28,0,0,0,28,31,15,0,0,15,20,7,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,52,76,68,12,44,28,0,0,28,28,28,36,28,0,0,12,36,36,0,0,12,23,15,0,0,0,0,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,28,7,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,44,84,76,20,52,36,0,12,31,28,44,44,28,0,0,28,36,20,0,7,28,28,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,28,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,39,92,92,20,68,44,12,31,44,44,47,47,12,0,12,36,36,0,0,23,31,15,0,0,0,0,0,0,0,20,28,12,0,0,0,0,7,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,28,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,36,95,95,28,79,60,20,44,47,55,52,52,0,0,31,44,28,0,20,36,28,0,0,0,0,0,0,0,20,31,15,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,31,44,23,0,0,0,0,0,0,0,0,0,0,31,31,12,0,28,100,100,44,84,60,20,60,60,63,52,44,0,12,44,44,12,12,36,36,12,0,0,0,0,0,12,28,36,20,0,0,0,0,20,23,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,44,20,0,0,0,0,0,0,0,0,0,28,36,28,0,15,100,100,60,87,60,23,60,63,60,52,23,0,36,47,36,0,28,39,20,0,0,0,0,0,12,31,39,20,0,0,0,12,20,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,44,20,0,0,0,0,0,0,0,0,12,36,36,0,7,100,100,68,84,52,36,68,79,60,55,0,12,47,47,12,20,44,36,0,0,0,0,0,12,36,44,23,0,0,0,20,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,23,52,44,20,0,0,0,0,0,0,0,0,39,39,12,0,95,100,84,84,52,52,68,100,60,55,0,36,52,36,12,39,44,20,0,0,0,0,12,36,44,23,0,0,12,28,39,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,0,0,0,0,0,0,0,0,28,60,44,15,0,0,0,0,0,0,0,36,44,28,0,87,100,95,95,60,68,76,111,71,44,12,52,55,20,31,52,28,0,0,0,0,15,36,44,20,0,0,20,36,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,36,68,44,20,0,0,0,0,0,0,23,44,44,0,79,100,111,111,68,68,92,116,84,36,36,60,44,28,52,44,12,0,0,0,20,44,44,20,0,7,28,44,52,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,12,0,0,0,0,0,0,12,44,68,55,28,7,0,0,0,0,7,52,52,0,68,100,124,132,100,76,119,124,92,28,55,60,36,47,55,23,0,0,0,28,47,52,28,0,15,36,60,52,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,20,31,36,20,7,0,0,0,0,0,20,52,79,63,28,0,0,0,0,0,52,55,28,60,100,132,124,108,84,143,124,87,52,60,52,44,60,39,0,0,0,28,52,52,28,0,28,52,68,60,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,12,0,0,0,0,0,12,28,36,36,15,0,0,0,0,0,23,68,87,68,23,0,0,0,0,47,63,52,47,100,143,124,124,108,188,135,84,63,68,52,60,55,20,12,12,28,55,60,28,12,36,68,76,63,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,23,23,15,0,0,0,0,7,28,44,44,28,7,0,0,0,0,31,76,92,68,20,0,0,0,36,76,71,44,100,151,124,135,135,204,132,84,68,71,60,63,31,12,28,52,68,60,31,23,52,84,84,60,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,31,20,7,0,0,0,28,52,55,36,20,7,0,0,0,39,92,100,60,12,0,0,15,84,87,60,100,156,124,148,175,212,127,84,71,76,71,52,28,36,68,79,68,36,36,68,92,84,55,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,31,36,36,28,12,0,0,28,52,68,55,31,7,0,12,20,52,108,108,52,7,0,0,79,100,84,108,156,124,164,199,188,132,79,92,76,76,47,52,76,84,63,52,52,92,108,84,44,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,39,39,31,20,12,23,52,76,76,44,28,20,28,31,68,124,111,47,0,0,60,100,108,116,159,140,180,220,172,127,92,100,87,76,60,92,95,71,60,76,116,116,76,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,39,44,47,39,28,20,52,84,92,71,52,44,36,44,87,140,111,36,0,28,95,124,140,175,167,196,220,164,116,116,108,108,87,108,108,84,76,103,132,116,68,23,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,52,52,47,44,47,76,100,92,76,60,52,63,116,148,100,23,0,84,140,180,199,196,212,204,164,116,135,124,124,135,132,108,100,127,143,108,55,20,12,15,7,0,12,12,12,12,0,0,0,0,7,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,52,52,60,60,60,76,103,119,108,79,68,84,140,148,79,12,63,140,212,228,228,223,204,164,151,151,164,164,167,148,135,140,140,92,44,23,20,20,28,31,31,23,20,12,12,20,28,20,20,15,7,12,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,60,68,76,92,116,135,132,108,92,116,164,140,60,44,127,220,244,244,236,212,180,191,196,207,196,188,172,151,124,84,47,36,36,47,52,44,36,28,36,39,39,36,28,28,20,20,20,15,20,12,20,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,15,20,7,0,0,0,0,0,0,31,60,76,84,108,132,143,148,140,140,164,191,132,60,108,207,252,252,244,231,220,228,244,239,223,188,151,116,76,55,60,76,79,68,60,68,71,60,44,36,36,39,52,55,60,60,55,44,44,36,31,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,23,28,36,44,52,52,44,28,12,0,0,12,44,76,100,116,148,164,167,172,196,220,204,116,119,188,252,252,252,247,244,244,252,236,196,156,124,92,87,111,124,116,116,116,92,68,60,68,84,95,100,95,92,84,84,79,60,44,39,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,60,63,76,79,92,84,68,44,28,28,60,92,124,159,191,204,212,236,244,191,172,196,252,252,252,252,252,252,236,212,175,148,140,156,172,175,172,151,140,127,132,132,132,119,103,100,95,95,92,84,76,55,31,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,12,0,12,20,20,12,0,0,0,0,0,12,28,44,68,76,92,92,95,95,95,92,87,87,108,124,148,188,228,247,252,236,220,223,252,252,252,252,252,247,231,204,188,196,207,220,228,228,212,188,172,148,124,108,100,100,95,84,68,47,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,28,20,0,0,12,28,44,44,28,31,44,52,44,44,36,36,44,44,47,47,47,52,52,68,84,100,103,103,116,116,116,127,148,164,183,204,231,247,252,244,244,252,252,252,252,252,247,236,231,236,244,244,231,212,180,143,119,108,103,92,68,52,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,20,15,0,0,0,12,36,44,39,47,68,76,79,84,79,92,124,140,135,132,140,143,140,132,124,119,119,132,164,175,196,212,212,196,191,207,231,247,252,252,252,252,252,252,252,252,252,252,236,220,188,164,140,116,100,84,68,47,28,20,15,15,15,15,15,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,20,31,36,36,36,39,39,44,44,63,92,116,124,132,151,164,164,164,156,156,151,148,151,164,204,231,252,252,252,244,244,247,252,252,252,252,252,252,252,252,252,252,228,191,156,124,92,71,60,60,60,52,47,44,44,36,36,28,23,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,20,44,76,87,71,76,100,119,132,156,172,180,183,199,220,236,244,247,247,252,252,252,252,252,252,252,252,252,236,212,164,124,84,60,52,47,44,36,28,28,20,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,20,47,60,39,44,60,84,108,132,148,164,180,199,204,196,191,207,236,252,252,252,252,252,252,252,252,252,252,236,196,156,108,76,60,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,12,20,31,44,60,79,100,124,148,167,180,172,164,148,148,164,204,228,244,244,252,252,252,247,228,223,236,252,252,239,220,183,148,108,76,60,52,47,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,52,71,92,108,140,172,191,204,191,167,135,108,92,92,116,164,220,220,220,236,252,244,244,247,220,188,180,212,236,228,215,204,204,188,164,135,100,71,52,44,44,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,60,68,84,108,140,180,204,204,188,156,132,100,68,44,47,68,108,164,212,199,180,196,244,252,228,228,252,231,175,124,132,167,191,188,188,188,188,175,151,116,103,103,92,68,44,36,36,23,20,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,28,60,100,132,164,175,172,156,143,116,92,60,28,7,20,47,76,108,156,204,196,148,151,207,252,252,196,199,244,236,196,119,71,76,116,140,143,140,148,148,148,143,132,100,68,60,68,71,60,36,20,20,20,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,84,108,124,116,116,116,108,84,55,23,0,0,0,31,60,76,108,148,196,204,140,108,164,236,247,236,164,172,212,231,196,156,79,36,36,76,108,124,111,100,108,103,87,92,108,103,76,52,47,44,44,36,23,15,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,71,84,87,92,87,76,60,36,12,0,0,0,0,20,44,60,71,92,143,204,212,148,76,108,188,231,228,220,148,148,172,220,196,172,124,60,12,15,47,76,100,103,84,68,84,87,76,68,76,92,84,60,44,39,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,36,55,68,71,76,68,55,39,23,0,0,0,0,0,0,12,28,52,55,60,79,140,212,220,148,60,68,127,188,212,204,204,148,119,148,196,191,172,148,100,52,7,0,28,60,76,84,84,60,52,60,68,68,60,60,68,68,60,44,36,36,31,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,23,39,36,44,60,47,36,28,15,0,0,0,0,0,0,0,0,20,39,47,47,44,63,132,204,223,156,63,36,95,148,156,196,183,188,156,95,116,180,172,164,159,116,87,44,0,0,7,36,60,68,76,63,44,36,44,52,60,55,52,52,52,47,39,31,28,28,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,23,28,28,36,20,20,20,12,0,0,0,0,0,0,0,0,0,12,28,39,44,31,36,60,124,188,212,164,68,15,60,116,140,124,188,175,172,167,87,79,148,156,156,148,132,95,76,36,0,0,0,20,44,60,60,55,44,36,31,28,28,39,47,47,44,44,36,28,23,23,20,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,15,31,36,36,20,23,60,111,164,196,164,76,12,28,92,111,100,119,183,172,159,172,92,47,111,148,124,132,135,95,84,71,28,0,0,0,0,28,52,55,52,44,31,28,23,20,20,28,39,44,44,44,36,23,12,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,31,20,12,20,63,100,143,172,156,79,20,0,60,92,84,60,124,188,164,140,180,100,20,84,148,108,116,127,108,76,76,63,23,0,0,0,0,12,36,52,52,36,28,20,20,20,12,0,12,28,39,39,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,20,12,0,20,60,92,132,156,143,79,20,0,28,76,76,44,44,132,188,140,124,183,108,12,68,116,116,100,100,124,79,60,76,55,20,0,0,0,0,0,20,36,47,44,28,15,12,12,12,0,0,0,15,31,36,36,31,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,12,0,0,20,60,84,116,140,140,84,23,0,0,60,76,44,28,52,127,183,111,103,188,116,12,44,84,132,84,79,108,103,52,52,76,47,12,0,0,0,0,0,0,20,39,44,31,15,0,7,0,0,0,0,0,0,20,31,31,28,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,7,12,44,68,95,124,132,84,28,0,0,28,68,60,12,20,68,127,183,92,87,183,116,20,15,68,124,84,76,76,108,76,39,47,71,44,7,0,0,0,0,0,0,12,28,36,36,23,7,0,0,0,0,0,0,0,0,12,20,28,28,20,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,55,79,116,119,84,28,0,0,0,55,68,36,0,12,79,132,167,76,76,180,108,31,0,60,87,108,68,55,84,95,52,31,47,68,36,0,0,0,0,0,0,0,0,15,28,36,28,12,0,0,0,0,0,0,0,0,0,0,12,20,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,55,71,100,108,84,31,0,0,0,28,63,55,0,0,7,92,135,140,60,68,167,100,39,0,39,68,108,68,52,60,92,76,36,28,52,60,28,0,0,0,0,0,0,0,0,0,20,28,28,20,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,60,92,103,84,36,0,0,0,7,52,60,28,0,0,15,92,135,108,60,68,159,92,47,0,12,60,92,76,60,36,68,84,52,20,20,52,55,23,0,0,0,0,0,0,0,0,0,7,20,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,63,92,87,36,0,0,0,0,31,60,52,0,0,0,28,95,140,84,55,68,156,92,52,0,0,55,68,92,63,36,44,76,71,36,12,23,52,52,20,0,0,0,0,0,0,0,0,0,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,31,68,84,36,0,0,0,0,12,52,60,23,0,0,0,52,84,132,63,44,60,140,95,55,0,0,36,60,87,60,52,20,55,76,52,23,0,28,52,44,12,0,0,0,0,0,0,0,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,20,52,76,39,0,0,0,0,0,36,55,44,0,0,0,0,60,76,108,52,28,44,103,84,55,0,0,12,55,68,68,60,20,36,60,60,28,12,0,28,52,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,15,15,28,52,36,0,0,0,0,0,12,52,52,20,0,0,0,0,68,68,68,44,12,36,68,60,52,0,0,0,52,55,68,55,44,12,47,60,36,12,0,0,28,47,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,15,36,47,20,7,0,0,0,0,0,36,52,36,0,0,0,0,20,63,68,52,39,0,36,47,52,52,0,0,0,36,52,60,55,52,12,28,52,52,23,12,0,0,28,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,52,36,0,0,0,0,0,0,12,47,47,12,0,0,0,0,36,60,71,44,36,0,36,39,52,52,0,0,0,12,47,52,52,52,31,0,39,52,36,7,0,0,7,28,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,28,7,0,0,0,0,0,0,31,44,28,0,0,0,0,0,52,60,68,36,36,0,36,44,52,47,0,0,0,0,44,47,47,52,47,0,20,44,47,20,7,0,0,12,28,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,31,28,0,0,0,0,0,0,0,12,44,44,12,0,0,0,0,0,52,55,44,28,23,0,36,52,60,44,12,0,0,0,31,44,44,44,52,23,0,28,44,36,0,0,0,0,12,28,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,7,0,0,0,0,0,0,0,31,44,28,0,0,0,0,0,7,52,52,28,20,20,0,28,52,71,44,20,0,0,0,12,44,44,31,47,44,0,12,39,44,15,0,0,0,0,12,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,39,36,0,0,0,0,0,0,28,52,52,28,28,15,0,12,36,68,44,28,0,0,0,0,39,44,28,44,44,15,0,20,39,31,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,20,0,0,0,0,0,0,44,52,44,20,20,0,0,0,15,52,31,28,0,0,0,0,28,39,36,28,44,31,0,0,28,36,20,0,0,0,0,0,0,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,28,0,0,0,0,0,0,0,47,52,28,12,12,0,0,0,12,44,31,28,0,0,0,0,7,36,36,12,44,39,7,0,12,36,31,0,0,0,0,0,0,12,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,12,0,0,0,0,0,0,0,47,47,12,12,12,0,0,0,7,31,28,28,0,0,0,0,0,36,36,15,31,36,23,0,0,20,31,20,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,0,0,0,0,0,0,0,12,47,47,0,12,12,0,0,0,0,28,36,36,0,0,0,0,0,23,36,28,12,36,36,0,0,7,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,0,0,0,0,0,31,44,44,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,7,31,31,0,31,36,15,0,0,15,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,44,44,31,0,0,0,0,0,0,0,0,23,23,0,0,0,0,0,0,28,28,12,15,36,28,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,7,0,0,0,0,0,0,0,0,44,44,12,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,20,28,23,0,31,31,12,0,0,12,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,0,0,0,0,0,44,44,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,28,28,0,20,28,20,0,0,0,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,20,39,39,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,23,23,12,0,28,28,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,31,0,0,0,0,0,0,0,0,0,28,28,12,0,0,0,0,0,0,15,20,20,0,20,28,15,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,15,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,20,20,0,7,23,23,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,31,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,20,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_7[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,12,12,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,23,7,0,0,0,0,0,0,0,0,0,0,0,28,28,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,12,15,7,0,0,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,28,12,0,0,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,7,15,15,0,0,0,0,0,20,20,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,47,28,0,0,0,0,0,0,0,0,0,0,36,36,20,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,12,28,20,0,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,44,12,0,0,0,0,0,0,0,0,0,36,36,36,0,0,0,0,0,0,0,0,20,20,12,0,0,0,0,31,28,12,0,0,0,0,12,23,20,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,60,31,0,0,0,0,0,0,0,0,0,31,39,39,0,0,0,0,0,0,0,0,20,20,12,0,0,0,20,36,28,0,0,0,0,0,23,28,12,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,52,15,0,0,0,0,0,0,0,0,20,44,44,0,0,0,0,0,0,0,0,28,23,0,0,0,12,36,36,20,0,0,0,0,20,28,20,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,52,68,36,0,0,0,0,0,0,0,0,7,44,44,0,0,0,0,0,0,0,15,28,28,7,7,0,20,47,36,7,0,0,0,12,31,28,12,20,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,28,68,60,15,0,0,0,0,0,0,0,0,44,44,0,0,0,0,0,0,0,23,31,28,7,7,12,36,52,31,0,0,0,0,28,36,20,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,15,12,0,0,0,0,0,0,12,55,76,44,0,0,0,0,0,0,0,0,47,47,12,0,0,0,0,0,0,44,44,36,12,12,12,52,44,20,0,0,0,20,36,28,20,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,7,12,7,0,0,0,0,0,0,31,76,68,20,0,0,0,0,0,0,0,47,47,28,0,0,0,0,0,7,60,60,28,12,12,36,60,44,0,0,0,12,36,36,28,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,7,0,0,15,20,0,0,0,0,0,12,63,92,55,0,0,0,0,0,0,0,47,52,44,0,0,0,0,0,23,63,68,20,15,23,60,63,28,0,0,0,28,44,36,28,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,12,0,20,36,20,0,0,0,0,0,36,92,84,23,0,0,0,0,0,0,39,52,52,0,0,0,0,0,52,76,84,20,23,36,76,55,12,0,0,20,44,44,36,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,7,20,28,12,0,0,0,0,12,68,100,60,0,0,0,0,0,0,28,52,52,0,0,0,0,0,68,84,84,28,39,60,79,44,0,0,7,39,44,44,39,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,12,7,20,28,20,7,0,0,0,36,92,92,28,0,0,0,0,0,12,55,55,0,0,0,0,0,79,84,71,31,44,84,71,23,0,0,28,52,52,44,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,15,15,36,39,28,12,0,0,12,76,111,76,0,0,0,0,0,0,60,60,0,0,0,0,12,84,100,60,44,60,92,60,0,0,20,52,60,52,52,31,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,20,20,44,39,28,15,7,0,36,108,108,36,0,0,0,0,0,60,60,20,0,0,0,31,84,119,52,60,84,92,36,0,0,44,60,60,52,52,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,39,23,28,47,44,28,12,0,7,84,119,76,0,0,0,0,0,60,60,36,0,0,0,52,92,132,52,79,100,79,20,0,31,60,68,60,52,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,44,28,36,52,44,36,20,0,36,108,108,36,0,0,0,0,55,60,47,0,0,0,68,108,127,76,92,108,60,12,28,60,76,71,76,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,15,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,44,36,44,60,60,39,20,7,76,119,84,12,12,12,0,47,63,60,0,0,0,84,132,116,108,111,108,44,23,60,71,84,87,76,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,31,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,23,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,44,52,68,63,39,15,31,108,111,39,15,15,12,36,68,63,0,0,12,87,156,108,132,124,92,31,52,76,92,100,100,63,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,31,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,31,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,60,52,60,76,71,36,20,76,116,84,28,28,20,20,68,68,0,0,28,95,164,111,140,124,76,44,79,100,124,124,84,28,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,36,44,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,47,68,63,71,84,76,31,36,103,108,52,28,28,12,68,68,12,0,44,116,164,140,132,116,60,76,100,124,140,124,44,0,0,0,0,0,0,0,0,0,0,0,0,7,28,44,44,44,28,7,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,52,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,28,55,76,76,84,92,71,36,71,116,87,44,36,20,68,68,23,0,68,148,167,164,124,103,68,103,132,156,148,76,12,0,0,0,0,0,0,0,0,0,0,0,28,44,47,44,28,12,0,0,0,0,0,7,12,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,52,63,68,60,36,12,0,0,0,0,0,0,0,0,0,12,15,36,60,84,84,100,100,68,52,103,111,71,36,36,68,71,39,0,84,172,180,164,132,92,108,132,172,156,92,23,0,0,0,0,0,0,0,7,7,12,28,47,52,52,36,15,0,0,0,0,7,20,28,23,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,60,71,76,76,52,20,0,0,0,0,0,0,0,7,12,20,36,68,87,92,116,108,63,84,116,100,60,44,76,76,52,0,103,180,196,148,132,103,140,172,180,124,39,0,0,0,0,0,0,12,20,20,36,52,60,52,39,20,0,0,0,0,20,28,28,28,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,39,63,84,92,87,68,28,0,0,0,0,0,0,0,12,28,52,76,100,111,135,116,84,108,108,92,52,92,84,68,20,124,191,199,148,140,143,180,196,156,71,7,0,0,0,0,12,28,39,60,68,63,60,47,23,0,0,0,20,36,36,36,28,28,20,7,0,0,0,0,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,71,92,95,92,76,44,12,0,0,0,0,0,20,39,63,84,116,135,156,124,108,116,124,71,100,92,76,44,140,215,196,164,156,188,212,180,108,28,0,0,0,12,28,52,76,87,84,68,52,28,0,0,20,36,44,39,36,36,28,12,0,0,0,12,20,20,20,20,15,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,92,95,100,84,52,20,0,0,0,0,20,44,76,100,132,164,172,132,132,124,108,116,119,92,60,164,236,199,191,204,228,207,140,44,0,0,0,28,60,95,116,108,79,60,31,7,12,36,47,44,44,44,36,20,0,0,20,36,36,31,23,20,20,20,20,12,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,84,100,100,92,68,28,0,0,0,20,44,84,108,148,180,172,148,132,148,135,159,108,79,180,236,212,220,244,231,164,68,7,0,20,60,116,148,140,100,71,36,23,36,52,52,52,47,44,31,20,20,36,44,39,36,36,36,36,36,23,23,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,79,95,100,95,76,39,12,0,15,44,92,127,180,204,180,164,164,164,183,124,100,204,239,236,236,236,180,84,15,12,47,108,156,164,140,92,55,44,55,68,71,63,52,52,44,44,44,44,44,44,44,52,63,63,47,36,28,15,12,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,71,92,100,100,84,52,23,28,60,100,148,212,212,199,180,196,212,140,124,220,244,244,236,188,92,28,36,87,140,172,156,116,84,79,87,100,92,92,92,84,63,55,52,52,63,76,84,79,60,44,36,36,36,31,28,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,20,20,12,12,0,0,0,0,0,0,0,0,0,0,0,28,60,92,100,100,95,84,76,84,124,180,231,228,220,223,236,159,151,236,252,244,196,108,52,68,124,167,172,156,127,111,108,116,132,148,132,100,84,84,92,103,108,95,76,55,44,44,44,39,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,20,20,20,23,36,36,36,36,39,36,23,12,0,0,0,0,0,0,15,52,84,100,116,132,132,135,164,204,236,244,244,252,188,188,247,247,207,135,87,108,164,204,212,183,156,148,156,172,175,159,140,132,124,124,111,92,68,52,52,47,44,36,20,0,0,0,0,0,0,12,12,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,36,36,36,36,39,44,44,47,52,52,52,52,36,20,7,0,12,44,84,116,140,164,183,204,228,244,252,252,220,220,252,223,172,135,151,204,236,231,220,212,220,220,212,180,156,140,124,108,79,63,60,55,47,36,28,28,36,39,39,36,36,28,12,12,20,28,20,7,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,44,47,52,52,55,60,60,60,60,68,68,68,63,84,119,164,196,215,231,244,252,252,244,244,247,220,188,196,231,252,252,244,236,220,196,175,164,151,132,124,116,108,92,68,60,52,52,52,47,44,44,44,39,36,36,28,12,12,20,28,20,7,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,60,60,60,68,68,68,76,87,108,140,188,228,252,252,252,252,252,252,247,236,236,247,252,252,252,244,228,196,175,156,127,108,84,68,60,60,60,55,52,52,52,52,47,44,44,39,36,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,52,63,71,76,100,132,175,215,239,252,252,252,252,252,252,252,244,228,207,188,159,127,100,84,71,68,68,68,60,60,52,39,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,44,52,63,76,92,100,111,124,124,124,119,116,132,183,228,252,252,252,252,252,252,252,244,223,196,156,116,68,31,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,47,52,52,55,60,68,84,100,124,140,140,140,140,135,132,132,124,124,132,151,180,220,244,252,252,252,252,252,252,252,247,236,199,156,127,108,95,92,87,76,52,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,28,36,36,39,44,44,44,47,60,71,87,100,108,111,116,119,124,132,135,124,108,100,84,68,60,68,92,124,156,196,236,252,252,252,252,252,252,252,236,220,212,199,175,148,119,100,92,92,92,92,92,87,79,63,44,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,7,12,20,28,31,28,28,36,44,60,76,84,84,92,92,92,84,76,68,63,55,55,55,52,44,28,12,0,0,36,76,119,164,204,236,239,239,247,252,252,252,252,252,252,236,212,180,172,180,167,132,100,76,68,71,84,92,87,87,87,84,84,84,76,52,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,15,20,7,12,28,36,44,44,44,52,44,44,36,36,36,39,44,44,44,36,28,12,0,0,0,0,0,0,12,44,84,127,159,191,207,220,212,212,228,247,252,252,252,244,244,247,244,228,188,148,140,172,196,180,132,87,52,23,15,31,52,68,84,84,84,84,60,60,44,44,36,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,12,12,0,0,12,12,12,20,20,20,23,36,36,28,15,0,0,0,0,0,0,0,0,0,0,0,20,52,84,124,156,180,183,180,172,164,172,204,236,252,252,244,244,220,220,236,231,236,228,180,116,100,127,180,196,172,124,84,55,28,0,0,0,12,28,47,44,52,44,44,39,36,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,84,116,148,156,156,148,140,132,124,132,172,220,244,247,252,220,220,196,196,228,207,212,236,223,180,108,68,84,124,164,180,156,111,76,60,36,12,0,0,0,0,0,0,7,20,23,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,68,84,108,132,132,116,116,108,108,103,100,100,156,204,228,231,247,252,188,188,172,172,220,196,180,196,220,212,188,108,52,44,84,124,148,156,140,100,71,55,39,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,68,84,100,124,116,84,84,84,79,84,84,71,84,140,204,220,212,220,244,244,167,167,156,148,199,207,164,156,183,196,199,180,119,52,20,39,84,116,132,127,116,87,68,52,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,68,76,95,108,95,68,60,63,63,68,71,68,52,68,124,199,215,188,188,204,220,223,148,148,156,119,167,228,164,135,143,156,156,180,180,132,60,12,12,44,84,108,111,100,92,76,60,52,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,55,68,84,92,76,52,44,44,52,52,60,52,47,36,52,116,196,220,180,159,172,180,188,196,119,124,159,92,124,215,196,124,108,124,116,132,167,175,140,68,12,0,15,44,76,92,92,76,68,63,52,44,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,52,68,71,60,44,36,36,36,44,47,39,31,28,28,44,100,180,204,164,132,148,156,156,167,164,92,111,164,68,76,175,228,143,92,92,100,87,124,156,172,148,79,20,0,0,20,44,68,76,71,52,44,44,44,39,36,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,23,28,36,47,52,44,36,31,23,28,36,44,39,28,15,20,12,31,100,167,196,156,116,116,148,132,140,156,135,71,108,172,71,36,132,220,180,100,71,87,76,71,116,148,172,156,92,28,0,0,0,15,36,52,60,52,28,12,28,36,36,31,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,36,36,31,28,28,20,15,23,36,39,28,12,0,12,0,20,92,164,180,140,84,84,119,132,108,124,156,108,60,108,180,76,12,100,183,204,132,60,71,71,52,68,108,132,164,156,92,36,0,0,0,0,12,28,36,36,28,12,0,15,28,28,28,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,7,12,23,15,0,12,28,36,31,15,0,0,0,0,20,76,151,180,135,79,63,87,132,108,92,111,148,76,36,108,183,84,0,68,140,212,159,79,44,76,60,44,68,100,116,148,148,100,39,0,0,0,0,0,12,28,28,28,20,7,0,7,20,28,23,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,23,20,0,0,0,0,0,12,63,135,164,132,84,44,60,108,108,79,84,103,135,60,20,108,164,60,0,36,108,191,180,124,36,52,71,44,36,71,87,95,127,140,100,44,0,0,0,0,0,0,12,28,23,20,12,0,0,0,12,15,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,12,60,119,140,116,84,36,44,76,116,79,47,87,108,108,55,0,108,143,44,0,12,92,156,183,148,63,28,63,68,28,36,71,84,79,108,132,100,44,7,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,12,52,108,124,103,84,44,28,60,100,92,63,31,92,124,76,52,0,108,140,36,0,0,71,119,172,156,100,20,36,68,52,15,36,76,71,68,100,116,92,39,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,44,100,108,92,71,44,20,44,68,100,68,36,44,84,124,60,44,0,108,148,47,0,0,44,100,156,148,127,55,7,47,63,36,0,36,71,68,60,84,100,79,39,12,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,92,100,79,68,47,20,20,60,84,84,60,12,68,84,108,52,28,0,108,148,44,7,0,12,92,132,132,143,84,15,20,55,55,20,0,44,68,60,47,76,87,76,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,84,92,76,55,47,28,0,44,63,84,60,36,12,84,87,79,47,7,0,108,140,39,12,0,0,76,108,132,127,108,44,0,28,60,44,7,12,44,68,52,36,60,76,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,71,84,68,52,44,28,0,23,55,71,68,52,7,31,84,95,55,44,0,0,108,132,28,0,0,0,44,100,132,100,119,68,12,0,39,60,31,0,12,44,68,44,31,52,68,55,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,68,76,63,44,44,31,12,0,44,55,68,52,31,0,60,84,95,44,44,0,0,92,108,20,0,0,0,15,92,116,95,111,84,36,0,15,47,52,20,0,12,44,60,36,28,44,52,52,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,76,63,28,28,28,12,0,28,52,52,44,39,12,0,76,84,79,39,36,0,0,76,92,15,0,0,0,0,76,100,108,84,95,55,0,0,28,52,44,7,0,15,44,60,31,20,36,47,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,68,60,31,23,28,12,0,12,44,47,39,31,23,0,20,84,84,52,36,20,0,0,68,84,15,0,0,0,0,44,100,116,63,92,60,28,0,0,36,52,28,0,0,20,44,55,28,15,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,52,44,23,20,20,12,0,0,28,47,36,28,23,7,0,47,84,76,36,36,0,0,0,68,87,20,0,0,0,0,15,92,100,71,68,68,44,0,0,12,44,44,15,0,0,20,47,52,20,12,20,23,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,47,39,12,20,20,12,0,0,15,44,44,20,15,12,0,0,68,84,60,36,36,0,0,0,68,87,20,0,0,0,0,0,71,87,84,44,76,44,20,0,0,20,44,36,0,0,0,20,52,44,20,7,20,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,52,36,12,0,12,12,0,0,0,31,44,23,0,0,0,0,12,79,84,44,31,31,0,0,0,68,87,20,0,0,0,0,0,44,84,84,44,60,52,36,0,0,0,28,39,20,0,0,0,23,47,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,44,39,20,12,0,12,0,0,0,15,39,36,0,0,0,0,0,36,79,76,36,28,23,0,0,0,68,84,15,0,0,0,0,0,15,76,76,47,36,60,44,20,0,0,12,36,36,12,0,0,0,23,47,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,15,7,0,0,0,0,0,0,31,36,15,0,0,0,0,0,60,76,60,20,20,12,0,0,0,68,84,15,0,0,0,0,0,0,60,76,60,28,52,44,36,0,0,0,15,36,28,0,0,0,0,23,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,0,0,0,0,0,0,0,0,0,20,36,28,0,0,0,0,0,0,71,76,28,12,12,0,0,0,0,68,92,20,0,0,0,0,0,0,39,76,68,36,36,52,36,12,0,0,0,20,36,20,0,0,0,0,28,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,7,31,31,12,0,0,0,0,0,20,68,68,12,12,12,0,0,0,0,60,87,28,0,0,0,0,0,0,15,60,60,36,20,39,36,28,0,0,0,7,28,28,12,0,0,0,0,28,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,23,12,0,0,0,0,0,0,0,0,20,28,20,0,0,0,0,0,0,44,68,52,0,12,12,0,0,0,0,44,60,15,0,0,0,0,0,0,0,44,55,36,20,28,39,36,12,0,0,0,12,28,20,0,0,0,0,0,23,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,12,7,0,0,0,0,0,0,0,0,12,28,28,0,0,0,0,0,0,0,60,68,36,7,7,0,0,0,0,0,23,28,0,0,0,0,0,0,0,0,20,36,36,15,15,36,28,20,0,0,0,0,15,23,12,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,12,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,12,52,52,12,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,7,28,28,15,12,20,28,28,0,0,0,0,0,20,20,7,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,20,15,0,0,0,0,0,0,0,28,47,44,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,23,28,12,12,12,28,28,15,0,0,0,0,7,20,15,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,28,31,20,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,12,28,20,12,12,20,23,23,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,12,0,0,0,0,0,0,0,0,36,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,0,0,0,15,20,12,0,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,7,20,20,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,20,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_8[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,12,23,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,12,39,39,0,0,0,0,12,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,7,7,0,0,0,0,7,44,44,7,0,0,0,15,20,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,12,7,0,0,0,7,7,44,44,20,0,0,0,15,20,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,23,12,7,0,0,7,7,47,47,36,0,0,0,15,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,28,12,12,0,0,12,12,60,63,60,0,0,0,15,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,20,12,0,0,0,12,44,68,68,0,0,0,15,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,36,20,12,0,0,20,31,68,68,0,0,0,15,36,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,12,31,36,23,20,0,0,20,23,84,71,20,0,0,12,28,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,28,36,47,28,12,0,20,20,84,71,36,0,0,0,28,23,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,7,7,0,0,0,0,0,0,0,23,36,55,28,23,0,12,20,84,71,55,0,0,0,20,28,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,15,12,0,0,0,0,0,12,15,44,47,44,31,0,7,31,84,76,68,0,0,0,28,39,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,7,12,12,0,0,0,0,12,12,39,36,63,36,15,0,31,68,84,76,0,0,0,28,52,28,28,0,0,0,0,0,0,0,0,0,0,0,0,7,36,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,0,0,20,20,12,0,0,0,0,20,36,44,68,44,31,0,36,52,100,76,12,0,0,36,52,28,28,0,7,7,0,0,0,0,0,0,0,0,0,28,39,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,7,0,12,28,23,0,0,0,0,20,20,52,52,63,36,0,28,44,111,76,28,0,0,36,44,12,12,0,12,12,0,0,0,0,0,0,0,0,12,39,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,12,0,20,31,20,0,0,0,12,23,47,47,84,44,20,20,36,116,76,44,0,0,36,44,12,12,0,12,12,0,0,0,0,0,0,0,0,31,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,28,36,15,0,0,0,20,36,60,76,60,36,0,39,108,84,60,0,0,36,63,28,28,12,12,12,0,0,0,0,0,0,0,15,44,44,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,28,12,12,36,31,7,0,0,12,28,63,60,84,44,7,44,92,95,71,0,0,44,79,44,28,20,20,12,0,0,0,0,0,0,0,36,47,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,28,12,23,44,28,0,0,0,28,52,68,95,55,28,39,76,116,76,0,0,44,84,44,23,20,20,0,0,0,0,0,0,0,20,52,44,0,0,0,0,0,0,0,0,0,0,12,20,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,28,15,36,44,20,0,0,15,36,76,84,76,44,28,60,124,76,12,0,47,92,44,23,28,28,0,0,0,0,0,0,0,44,52,23,0,0,0,0,0,0,0,0,0,7,28,36,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,47,28,20,44,44,12,0,0,36,68,84,100,52,23,52,132,84,31,0,47,92,47,28,36,36,0,0,0,0,0,0,28,55,44,0,0,0,0,0,0,0,0,0,12,39,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,47,28,31,52,36,0,0,23,52,92,108,71,36,52,127,95,47,0,47,92,47,39,36,36,0,0,0,0,0,12,52,55,23,0,0,0,0,0,0,0,0,20,52,63,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,44,47,36,44,55,28,0,0,44,87,103,100,52,52,116,116,68,0,52,100,52,44,36,31,0,0,0,0,0,36,60,44,0,0,0,0,0,0,0,0,20,60,76,60,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,44,44,52,52,20,0,28,68,108,119,68,60,100,132,76,0,55,108,52,44,44,20,0,0,0,0,15,60,60,20,0,0,0,0,0,0,0,28,63,76,60,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,55,47,52,60,44,7,12,52,108,132,95,63,92,148,87,12,60,111,60,44,44,12,0,0,0,0,44,68,44,0,0,0,0,0,0,0,36,87,108,68,20,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,63,52,60,63,36,0,36,87,135,124,71,92,156,108,23,60,116,68,47,47,15,15,12,0,20,68,63,20,0,0,0,0,0,0,36,92,116,84,31,0,0,0,0,0,0,0,0,0,0,12,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,44,68,60,68,60,23,15,68,132,156,95,100,159,127,44,60,116,84,52,52,12,12,0,0,52,68,44,0,0,0,0,0,7,47,100,108,79,36,7,0,0,0,0,0,0,0,0,15,28,28,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,52,68,71,76,52,12,44,124,183,132,108,148,140,60,60,119,103,52,60,20,20,0,28,71,68,12,0,0,0,0,12,60,116,127,84,31,0,0,0,0,0,0,0,0,15,31,36,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,60,76,84,76,44,28,103,196,183,132,151,156,76,68,124,116,55,68,31,28,0,60,76,44,0,0,0,0,12,68,135,148,100,36,0,0,0,0,0,0,0,20,36,36,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,7,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,28,68,84,92,68,36,68,172,220,156,164,156,95,79,140,124,60,63,36,15,36,76,68,15,0,0,0,20,76,156,164,108,36,0,0,0,0,0,0,20,39,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,20,23,20,7,0,0,0,12,12,0,0,12,7,12,12,12,7,0,0,0,0,0,36,76,100,95,60,47,124,220,196,188,164,116,103,156,124,71,52,39,15,68,84,47,7,0,0,23,79,156,172,116,44,7,0,7,0,0,20,44,52,47,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,28,28,28,15,0,0,0,0,0,20,28,28,15,20,15,20,20,0,0,0,0,44,92,108,95,55,87,180,228,207,188,132,132,180,124,92,52,44,44,92,92,36,12,0,23,87,159,164,100,39,15,15,12,12,28,52,63,60,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,31,31,31,36,28,12,0,0,20,28,39,39,36,28,36,36,28,15,0,0,12,68,119,132,92,76,140,228,220,212,143,148,191,124,116,52,44,84,111,84,44,28,39,95,164,159,92,36,23,28,20,31,60,79,84,68,36,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,39,44,39,39,39,28,12,12,28,36,52,60,55,55,55,44,28,7,0,31,92,148,135,87,116,191,236,236,164,167,204,132,116,55,68,116,124,71,52,68,116,164,151,87,44,36,36,44,68,92,100,84,60,28,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,44,55,52,52,44,39,28,28,36,52,68,71,76,71,63,39,20,0,52,116,159,132,108,156,231,247,180,183,212,148,108,76,103,148,116,76,92,135,167,143,87,52,44,55,76,92,100,100,71,31,0,0,0,0,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,44,71,79,71,60,52,44,44,44,60,84,95,100,87,68,31,20,60,132,172,140,148,204,252,207,207,220,172,103,111,148,159,116,116,148,167,140,92,63,68,92,108,100,95,76,39,7,0,12,28,31,23,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,31,44,68,92,95,84,68,63,60,68,84,111,124,124,92,52,36,71,156,180,167,188,239,236,236,236,191,124,143,180,172,156,172,172,148,100,84,100,116,111,100,79,39,12,20,36,39,36,36,31,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,68,92,111,116,95,92,92,100,116,151,159,135,92,60,95,172,188,196,223,252,252,244,196,164,180,215,204,196,196,151,124,119,127,116,100,84,52,36,44,47,44,44,39,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,68,108,140,148,140,140,140,148,167,188,188,132,100,124,180,220,228,252,252,247,204,199,220,236,228,212,180,164,151,140,108,92,76,63,55,52,52,47,44,31,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,47,79,116,148,164,172,175,188,204,223,212,172,140,156,207,244,252,252,247,228,228,244,244,231,212,191,164,140,124,116,103,92,76,68,52,36,15,12,12,20,20,12,0,0,0,0,0,0,0,0,0,7,20,20,15,15,28,28,15,0,0,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,36,68,100,132,156,167,188,228,244,239,204,180,199,236,252,252,252,247,247,252,247,228,212,191,172,156,140,124,124,108,92,68,47,44,36,36,28,23,28,31,36,44,52,47,47,44,44,36,31,36,36,28,20,15,28,28,15,0,0,12,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,15,23,20,12,12,20,20,12,7,0,12,36,68,100,140,180,228,244,247,228,220,236,252,252,252,252,252,252,252,239,220,196,180,175,188,183,167,140,116,103,100,100,103,100,87,79,68,63,52,52,52,47,44,44,44,36,31,36,36,28,20,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,23,23,28,28,28,15,0,15,31,52,60,76,68,60,76,103,119,116,108,100,92,87,79,84,108,148,188,212,236,247,247,247,252,252,252,252,252,252,252,239,236,236,236,220,204,196,196,180,159,132,108,84,68,60,60,60,55,55,52,52,47,39,31,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,23,23,28,28,28,12,0,15,31,44,52,63,68,60,76,103,124,119,116,116,116,116,116,124,132,156,183,212,236,252,252,252,252,252,252,252,252,252,247,244,244,244,244,236,215,188,164,132,100,76,60,52,39,28,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,36,44,52,60,68,76,84,84,92,92,100,100,108,116,132,156,191,228,244,252,252,252,252,252,252,252,252,252,252,252,239,220,188,151,116,100,63,31,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,20,44,60,71,76,76,76,76,84,84,84,84,84,84,84,84,84,84,84,87,95,116,132,156,164,167,180,207,236,252,252,252,252,252,252,252,252,252,252,252,247,239,212,164,108,71,68,52,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,23,47,47,23,0,20,52,76,84,79,76,76,76,84,84,84,84,84,76,76,68,76,92,100,100,92,92,87,100,124,151,183,212,236,252,252,252,252,252,252,236,236,252,252,252,247,228,204,191,172,132,87,63,52,52,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,23,47,47,23,0,20,44,60,55,47,36,31,20,20,12,7,20,36,52,55,60,60,60,63,68,92,116,132,135,127,140,180,212,220,231,239,244,252,252,228,204,204,252,244,244,244,228,204,172,151,140,116,92,71,60,44,44,36,36,31,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,47,52,52,52,55,60,60,68,84,100,92,76,68,68,95,132,151,167,183,196,204,220,239,252,244,183,159,188,244,223,223,228,228,212,180,148,132,124,108,71,31,20,31,36,36,31,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,44,44,44,44,47,52,52,52,52,55,55,52,52,55,60,60,76,84,92,95,124,148,148,156,188,220,239,247,220,127,127,188,228,188,196,191,204,220,220,172,124,100,92,95,84,52,12,0,0,12,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,20,23,23,28,36,36,36,44,44,44,36,20,20,28,44,44,47,52,52,52,44,44,60,68,76,100,116,108,92,116,156,196,223,244,236,167,79,103,172,188,172,172,164,172,172,212,231,188,119,76,68,76,79,71,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,20,20,23,23,28,36,28,15,0,0,12,28,36,36,44,44,44,44,36,20,28,52,68,71,84,92,92,76,63,84,124,180,196,212,223,204,108,52,95,143,140,172,156,143,151,135,148,215,244,204,132,68,44,60,76,76,68,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,12,20,20,20,20,15,12,0,0,0,0,12,23,23,28,36,36,36,36,23,0,0,28,52,63,68,71,76,71,63,60,44,55,108,164,188,172,191,212,156,52,44,95,108,84,164,164,116,132,127,100,148,220,244,212,156,84,31,31,60,76,71,68,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,15,20,28,28,23,28,28,15,0,0,0,28,52,60,63,60,60,52,52,55,44,28,44,103,143,164,148,156,199,188,95,12,55,87,84,60,148,180,108,84,124,84,76,148,191,207,199,159,92,36,7,28,55,68,68,60,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,15,20,23,20,12,0,0,0,0,28,52,60,60,55,44,36,44,52,44,23,20,47,103,124,140,124,132,172,204,140,44,0,60,71,44,31,124,196,116,44,76,87,60,63,124,156,180,188,151,108,52,12,0,23,52,63,60,60,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,12,7,0,0,0,0,0,12,28,47,52,55,52,31,28,36,44,44,28,7,15,52,100,116,116,103,108,148,204,180,84,7,0,55,55,12,12,100,188,140,52,28,71,68,39,52,108,132,148,164,148,116,60,20,0,0,20,44,60,60,60,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,47,52,44,20,12,23,36,36,28,12,0,20,52,100,100,100,92,92,116,180,204,124,28,0,12,52,52,0,0,79,167,156,76,0,36,68,52,23,52,95,116,124,143,140,124,76,31,0,0,0,20,44,60,55,52,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,44,44,36,15,0,12,28,31,28,15,0,0,28,60,92,92,84,79,84,92,148,204,156,68,0,0,28,44,44,0,0,60,132,164,103,12,7,52,68,31,15,47,87,100,108,124,127,124,84,44,7,0,0,0,20,44,52,47,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,44,44,31,12,0,7,20,28,28,15,0,0,0,28,60,84,76,68,68,76,76,116,180,180,100,20,0,0,36,44,44,0,0,47,100,175,119,36,0,23,60,60,15,20,44,76,84,92,103,116,119,95,60,20,0,0,0,0,15,36,47,47,44,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,36,36,28,7,0,0,7,20,20,20,0,0,0,0,31,60,76,68,52,60,68,60,84,140,172,119,44,0,0,0,39,39,28,0,0,28,71,164,124,68,0,0,36,60,39,0,20,44,68,68,76,87,103,111,103,68,28,0,0,0,0,0,15,36,47,44,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,31,31,31,20,0,0,0,0,0,7,15,7,0,0,0,7,36,60,68,60,44,52,60,52,68,116,156,135,76,12,0,0,0,36,36,12,0,0,12,60,132,124,84,0,0,12,52,60,23,0,20,44,60,55,68,76,92,100,103,76,39,0,0,0,0,0,0,15,36,44,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,28,28,28,15,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,60,44,31,44,55,52,52,92,135,140,92,36,0,0,0,0,36,36,0,0,0,0,52,103,127,100,23,0,0,28,52,44,7,0,20,39,52,44,60,68,79,92,100,84,52,12,0,0,0,0,0,0,12,28,36,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,44,36,20,31,47,52,36,68,116,132,103,60,7,0,0,0,12,28,28,0,0,0,0,52,76,124,103,44,0,0,0,36,52,28,0,0,20,36,36,36,52,60,68,76,92,87,60,23,0,0,0,0,0,0,0,12,28,36,36,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,15,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,52,36,20,12,23,39,52,28,47,100,119,111,76,28,0,0,0,0,15,20,20,0,0,0,0,44,55,103,92,52,0,0,0,12,44,44,15,0,0,12,20,20,28,44,52,60,68,84,87,68,36,0,0,0,0,0,0,0,0,12,23,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,44,28,12,7,12,36,52,28,36,84,108,108,84,44,0,0,0,0,0,15,15,12,0,0,0,0,28,44,87,84,68,15,0,0,0,28,44,36,0,0,0,0,15,28,28,36,39,52,60,71,84,68,36,0,0,0,0,0,0,0,0,0,7,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,39,20,0,0,12,28,47,28,20,63,100,100,87,63,20,0,0,0,0,0,15,15,0,0,0,0,0,12,44,76,76,76,36,0,0,0,0,36,44,20,0,0,0,15,28,20,20,20,31,44,52,60,68,63,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,36,15,0,0,0,20,44,28,12,44,92,92,84,68,36,0,0,0,0,0,0,7,7,0,0,0,0,0,0,36,55,60,76,39,0,0,0,0,15,36,36,7,0,0,0,12,23,20,12,12,23,36,44,44,52,60,44,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,36,28,12,0,0,0,20,36,28,12,31,76,92,71,71,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,44,55,71,44,12,0,0,0,0,23,36,20,0,0,0,0,12,12,0,0,0,15,28,39,36,44,52,44,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,23,7,0,0,0,12,36,28,12,15,63,92,68,63,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,36,60,63,55,23,0,0,0,0,7,28,31,12,0,0,0,0,0,0,0,0,0,7,23,28,23,28,44,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,23,20,0,0,0,0,12,28,28,12,0,47,84,68,52,60,44,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,52,47,60,31,0,0,0,0,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,12,23,20,28,28,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,7,0,0,0,0,0,12,20,12,0,36,76,71,39,52,52,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,23,36,23,52,28,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,12,20,23,20,20,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,0,0,0,7,12,0,0,20,63,76,44,31,52,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,15,36,28,7,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,7,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,12,20,7,0,0,47,76,52,20,44,44,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,28,28,20,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,7,7,12,0,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,15,12,0,0,36,71,60,15,28,44,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,36,20,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,20,60,68,28,12,36,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,68,44,0,28,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,60,52,12,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,44,52,20,0,28,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,44,28,0,12,28,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,28,0,0,23,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,31,31,12,0,12,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,15,0,0,23,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,12,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t SHINE_TEXTURE_9[16384] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,0,0,0,0,12,7,0,0,0,0,0,0,0,0,0,7,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,23,20,0,0,0,7,0,0,0,0,0,0,0,0,0,0,12,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,23,0,0,0,28,28,20,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,12,0,0,36,36,28,0,0,0,0,0,0,0,0,20,23,12,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,36,15,0,0,36,31,23,0,0,0,0,0,0,0,0,20,20,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,28,0,12,23,20,12,0,0,0,0,0,0,0,0,36,36,0,0,0,7,20,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,36,0,12,20,36,28,0,0,0,0,0,0,0,12,52,52,7,0,12,12,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,12,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,39,20,0,20,71,60,12,0,0,0,0,0,0,20,52,52,7,0,12,12,23,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,12,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,52,28,0,23,84,68,12,0,0,0,0,0,0,36,60,52,0,0,20,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,60,47,12,23,55,31,7,0,0,0,0,0,0,63,79,60,7,15,12,28,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,12,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,76,71,12,15,52,36,20,0,0,0,0,0,0,60,68,44,12,28,28,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,20,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,15,76,79,28,20,76,68,44,0,0,0,0,0,0,68,68,36,15,36,39,28,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,7,28,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,76,84,52,31,100,100,68,0,0,0,0,0,20,92,92,39,28,36,60,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,15,20,0,0,0,12,36,31,12,0,0,0,0,0,0,0,0,0,0,0,0,60,92,68,28,84,108,76,0,0,0,0,0,36,92,92,36,39,36,60,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,36,23,0,0,0,20,44,28,0,0,0,0,0,0,0,0,0,7,7,0,39,92,87,36,68,111,79,0,0,0,0,0,55,108,103,31,52,44,52,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,23,31,36,20,0,0,0,28,44,20,0,0,0,0,0,0,0,0,12,12,7,20,92,95,52,68,124,84,0,0,0,0,0,76,116,108,28,60,68,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,28,31,39,28,0,0,12,39,44,15,0,0,0,0,0,0,0,12,20,15,0,92,100,63,60,127,84,12,0,0,0,0,92,124,108,44,60,76,39,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,36,39,44,23,0,0,20,44,36,12,0,0,0,0,0,0,0,23,23,0,68,100,79,60,140,100,23,0,0,0,12,108,132,103,68,76,76,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,36,36,44,23,0,0,28,52,31,0,0,0,0,0,0,0,20,20,7,44,100,92,68,140,108,36,0,0,0,23,124,140,92,79,84,63,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,28,23,36,44,28,0,7,39,52,28,0,0,0,0,0,0,12,23,20,20,100,100,76,132,116,47,0,0,0,39,127,156,84,92,92,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,28,39,52,47,23,0,20,52,52,20,0,0,0,0,0,0,31,31,12,92,100,95,132,140,60,0,0,0,60,127,156,92,108,92,44,0,0,0,0,0,0,0,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,44,52,52,55,52,23,0,28,60,44,12,0,0,0,0,0,28,36,23,76,100,111,132,151,71,0,0,0,76,127,148,95,116,84,36,0,0,0,0,0,0,7,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,28,60,63,60,68,52,20,0,36,60,36,7,15,15,0,0,15,39,39,52,100,116,124,151,79,0,0,12,100,143,140,103,108,68,20,0,0,0,0,0,12,23,23,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,63,71,71,71,52,28,12,52,60,28,20,28,15,0,0,44,44,44,100,108,124,148,92,0,0,28,127,164,140,127,108,60,0,0,0,0,0,15,28,28,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,76,84,84,76,52,20,23,60,60,28,28,28,12,0,44,55,52,92,100,140,151,108,0,0,39,140,188,148,148,108,47,0,0,0,0,12,28,44,39,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,76,92,92,84,52,23,36,68,52,31,36,28,0,28,63,60,84,100,156,151,119,20,0,44,132,180,151,148,103,36,7,0,0,7,28,52,52,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,84,100,108,92,52,28,44,71,52,44,44,20,0,68,68,84,100,156,156,132,28,0,71,156,188,164,140,92,23,0,0,0,28,52,60,39,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,20,15,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,7,0,0,0,0,0,12,36,87,111,116,100,55,39,60,68,60,52,44,7,52,76,87,100,151,164,140,39,0,100,183,204,191,148,76,23,0,0,20,52,71,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,28,23,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,20,20,15,12,12,0,0,0,36,92,124,127,108,60,52,68,76,76,60,36,23,76,84,108,140,180,140,52,20,119,204,220,196,148,60,12,0,12,44,76,71,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,44,36,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,20,28,36,36,36,28,12,0,0,36,92,132,135,111,60,60,76,84,84,68,20,68,79,124,124,196,140,63,31,132,228,236,199,132,44,0,0,36,76,84,44,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,55,52,44,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,52,60,47,28,7,0,36,92,132,148,116,71,76,87,108,92,52,47,79,127,116,199,140,76,44,148,244,244,196,108,28,0,28,76,95,68,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,60,60,52,31,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,63,76,76,52,23,0,31,87,148,180,148,92,84,108,116,84,44,76,116,127,196,156,87,60,164,252,244,188,87,12,15,68,103,92,44,12,0,0,0,0,0,0,0,0,0,0,0,0,28,52,68,68,60,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,60,92,100,84,44,20,28,95,175,204,156,108,103,132,116,68,76,92,151,183,180,100,76,180,252,244,164,68,7,55,111,116,68,28,7,0,0,0,0,0,0,0,0,0,0,36,68,84,76,68,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,52,87,116,116,84,36,36,100,180,207,164,124,132,143,100,79,84,164,188,204,111,87,188,252,228,132,36,36,108,140,92,39,12,0,0,0,0,0,0,0,0,7,39,76,92,92,79,47,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,36,76,124,148,124,68,55,100,180,204,172,140,156,143,95,100,148,196,220,124,100,204,252,199,100,36,100,159,132,60,12,0,0,0,0,0,0,0,12,44,84,100,100,87,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,20,60,116,156,151,108,76,108,180,196,180,164,172,132,116,132,212,228,132,116,220,244,164,71,76,156,156,92,28,0,0,0,0,0,0,12,47,84,103,108,92,63,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,15,12,0,36,92,140,164,135,116,132,180,204,196,196,191,148,140,204,231,148,132,231,223,127,76,140,180,124,39,0,0,0,0,0,12,52,87,103,103,92,60,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,20,20,20,31,36,31,44,76,124,151,159,151,156,196,212,223,236,188,172,196,244,159,156,244,188,116,119,188,156,60,12,0,0,0,20,55,92,103,103,92,55,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,20,36,39,36,44,60,71,84,116,148,164,172,196,228,244,252,228,196,199,239,180,180,236,164,135,172,172,84,20,0,0,20,60,92,103,103,87,52,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,20,20,12,12,12,0,0,0,0,0,0,0,0,0,12,12,12,20,23,47,68,76,76,84,100,127,159,180,196,228,244,252,252,228,228,244,204,204,220,172,167,172,100,23,0,23,63,92,108,108,87,47,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,20,20,12,15,28,28,28,28,36,39,36,28,12,0,0,0,0,12,20,31,36,52,76,103,124,124,140,172,207,228,244,252,252,244,244,244,228,228,212,199,172,116,39,28,68,100,124,124,100,52,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,28,28,28,28,36,39,44,44,44,47,52,52,52,47,36,31,36,44,44,52,76,108,151,175,196,220,244,252,252,252,252,252,244,244,231,196,132,76,79,119,140,140,116,63,28,0,0,0,0,0,0,0,7,20,20,28,28,31,23,15,12,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,7,20,36,44,44,44,47,52,52,52,55,60,60,68,76,84,95,108,103,111,132,164,204,231,252,252,252,252,252,252,252,231,188,140,132,148,148,124,76,44,31,28,31,44,52,52,52,52,52,52,52,47,52,52,52,28,15,20,44,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,28,15,0,0,0,20,36,44,44,55,71,92,103,108,116,124,127,124,132,127,127,143,164,180,204,228,244,252,252,252,252,252,252,236,207,188,188,180,156,124,108,103,116,124,132,132,124,116,108,103,100,92,87,76,68,60,39,12,0,7,15,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,23,28,39,36,20,15,28,47,68,71,76,84,87,95,95,100,124,159,180,188,207,220,223,223,220,212,212,228,244,252,252,252,252,252,252,244,244,236,212,188,175,180,196,204,215,220,228,223,196,167,148,132,124,108,100,95,92,92,76,60,36,20,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,20,28,28,36,36,44,52,60,68,76,84,100,108,124,127,140,172,207,228,228,236,244,239,244,236,236,247,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,236,212,175,156,124,100,87,84,84,84,84,84,84,84,84,68,44,36,23,0,0,0,0,0,0,0,0,12,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,44,68,84,60,31,47,84,100,100,92,92,92,92,92,92,100,108,124,143,164,164,172,172,172,196,207,220,223,236,244,244,252,252,252,252,252,252,252,252,252,236,212,204,215,236,252,236,215,180,159,140,116,87,76,76,71,60,44,36,28,28,28,28,31,20,0,0,0,0,0,0,0,0,12,20,20,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,28,60,76,44,12,28,60,84,84,84,84,84,87,84,76,76,68,63,60,68,92,127,148,143,140,148,172,188,196,215,244,252,252,252,252,252,252,252,252,252,236,212,183,164,156,164,188,212,223,223,191,156,119,103,92,79,60,52,52,47,44,44,36,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,20,44,52,31,7,12,20,20,12,0,0,0,7,7,12,20,28,52,68,79,79,84,95,119,140,140,124,140,167,204,236,247,236,236,231,236,252,252,247,247,244,228,191,156,140,119,108,116,135,148,151,143,124,111,100,92,76,76,71,68,60,52,44,39,36,28,28,31,23,15,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,52,60,60,63,68,79,100,108,92,79,100,124,140,159,204,228,231,207,212,207,212,244,244,236,220,212,220,212,196,180,148,111,76,60,60,60,63,60,68,76,76,60,44,28,28,39,52,47,44,39,28,28,31,23,15,12,23,12,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,39,52,52,52,55,60,60,60,68,68,63,68,76,92,95,116,140,167,188,228,196,172,196,188,180,223,228,220,191,156,167,172,172,180,188,180,156,116,68,28,15,28,39,39,36,36,36,39,36,31,20,12,0,0,0,0,12,12,12,12,23,12,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,39,44,44,47,52,52,47,36,31,44,52,60,60,60,60,68,84,108,124,140,156,212,215,148,148,204,172,148,204,196,188,180,124,100,132,140,140,148,164,180,188,159,111,68,31,0,0,12,28,31,28,20,15,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,28,36,36,39,44,44,44,36,20,7,20,39,52,52,55,47,36,44,68,84,100,108,116,132,180,215,172,108,143,228,156,124,188,164,156,167,124,76,68,111,116,111,111,132,148,175,180,148,100,68,36,7,0,0,0,0,12,12,12,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,28,36,36,36,36,23,0,0,0,20,39,44,47,52,36,15,23,52,76,79,84,92,100,116,159,204,188,108,92,164,244,143,100,156,140,124,124,143,84,44,55,95,103,100,87,95,111,127,156,156,132,92,68,36,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,28,20,20,23,23,12,0,0,0,7,20,36,39,44,44,28,0,0,36,68,76,68,68,79,84,95,140,188,191,132,60,92,188,215,116,76,119,143,92,95,132,116,60,20,52,84,92,84,76,76,76,87,108,132,140,116,84,60,39,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,15,15,20,20,12,0,0,0,0,0,12,20,28,36,36,36,20,0,0,20,52,68,68,52,60,76,76,76,124,175,188,143,76,36,108,196,180,84,44,100,143,68,84,92,124,84,31,7,44,76,76,71,68,68,60,60,76,95,116,119,108,76,55,36,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,12,0,0,0,0,0,0,0,0,15,23,28,28,23,12,0,0,0,36,60,68,52,39,47,76,68,63,103,164,180,148,92,28,52,124,180,151,55,20,92,124,76,68,68,100,92,60,12,0,36,68,68,60,55,63,52,44,44,63,84,95,100,87,63,44,31,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,20,23,15,0,0,0,0,20,44,63,60,36,28,47,76,60,47,76,148,164,148,108,52,0,68,140,148,116,23,0,87,100,92,52,52,68,92,68,36,0,0,28,60,68,52,44,60,60,36,28,36,60,68,71,76,68,52,36,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,31,52,60,47,20,20,52,76,52,36,60,132,143,132,116,76,12,15,76,156,124,92,12,0,79,92,100,47,44,52,76,76,60,23,0,0,23,52,68,44,36,44,52,44,23,15,28,44,55,52,52,52,44,28,20,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,20,44,55,52,31,7,20,52,71,47,28,47,116,132,124,116,92,36,0,44,79,164,116,63,7,0,63,92,100,44,39,31,52,68,60,47,12,0,0,20,47,63,39,28,31,52,47,28,12,12,28,28,36,36,44,44,28,20,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,52,52,44,20,0,23,52,68,39,12,36,95,124,108,108,100,60,0,0,60,92,151,111,39,0,0,44,92,92,44,39,20,44,55,60,55,36,0,0,0,12,39,60,44,20,20,44,44,31,12,0,0,12,20,36,28,28,28,23,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,36,52,44,28,0,0,28,55,60,36,0,20,68,116,108,92,100,76,20,0,12,68,111,127,108,12,0,0,23,87,87,44,36,20,23,44,52,47,52,20,0,0,0,12,36,60,44,15,12,28,44,36,20,0,0,0,12,15,20,20,20,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,44,44,36,12,0,0,28,60,55,28,0,12,44,92,100,76,92,92,44,0,0,36,68,124,108,92,0,0,0,7,87,87,47,31,28,0,36,39,39,47,39,12,0,0,0,0,28,52,44,20,0,20,36,36,23,7,0,0,0,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,31,44,39,20,0,0,0,31,55,52,23,0,0,28,76,92,68,79,92,63,7,0,0,52,63,119,100,76,0,0,0,0,84,87,52,28,28,0,20,36,36,31,44,28,0,0,0,0,0,28,44,44,20,0,12,28,36,28,12,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,36,39,28,12,0,0,12,36,55,44,20,0,0,20,60,84,68,60,84,79,28,0,0,0,60,63,100,87,52,0,0,0,0,68,87,68,20,23,12,0,28,36,28,36,44,20,0,0,0,0,0,20,39,39,20,0,0,15,23,20,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,28,36,31,20,0,0,0,12,36,52,36,12,0,0,12,36,68,71,47,68,84,44,0,0,0,28,60,63,84,79,36,0,0,0,0,47,87,79,12,15,15,0,12,28,28,20,36,31,7,0,0,0,0,0,12,36,39,20,0,0,0,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,28,31,20,7,0,0,0,12,36,52,31,12,0,0,12,28,52,68,39,44,76,63,12,0,0,0,44,60,52,71,71,15,0,0,0,0,28,84,84,12,12,12,0,0,20,23,12,20,36,20,0,0,0,0,0,0,12,28,36,20,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,28,23,12,0,0,0,0,15,36,44,28,0,0,0,7,23,52,63,44,28,55,63,28,0,0,0,0,52,55,36,60,60,0,0,0,0,0,12,84,84,20,0,0,0,0,7,20,20,12,28,31,12,0,0,0,0,0,0,7,23,36,20,0,0,0,7,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,23,15,0,0,0,0,0,15,36,44,20,0,0,0,0,7,31,60,47,20,31,55,39,0,0,0,0,20,52,52,20,44,44,0,0,0,0,0,0,84,84,44,0,0,0,0,0,12,20,12,12,28,28,0,0,0,0,0,0,0,0,20,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,20,7,0,0,0,0,0,12,31,36,20,0,0,0,0,0,15,44,52,28,15,44,44,12,0,0,0,0,36,52,44,20,36,31,0,0,0,0,0,0,68,84,60,0,0,0,0,0,0,12,12,0,12,20,12,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,7,0,0,0,0,0,0,12,28,20,12,0,0,0,0,0,12,31,47,28,0,28,44,28,0,0,0,0,0,47,52,20,23,28,20,0,0,0,0,0,0,52,84,76,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,20,28,20,0,0,0,0,0,0,7,28,47,36,12,15,39,36,0,0,0,0,0,12,47,47,0,20,20,7,0,0,0,0,0,0,31,84,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,31,20,7,0,0,0,0,0,0,15,39,36,12,0,31,36,15,0,0,0,0,0,28,44,44,0,12,12,0,0,0,0,0,0,0,12,76,76,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,28,36,20,0,20,36,23,0,0,0,0,0,0,44,44,23,0,12,12,0,0,0,0,0,0,0,0,60,60,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,7,0,0,0,0,0,0,0,0,20,36,20,0,7,28,28,7,0,0,0,0,0,0,44,44,0,0,7,7,0,0,0,0,0,0,0,0,44,55,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,12,0,0,0,0,0,0,0,0,0,12,31,23,0,0,20,28,12,0,0,0,0,0,0,20,39,36,0,0,0,0,0,0,0,0,0,0,0,0,28,44,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,0,0,0,0,0,0,0,0,0,12,28,28,7,0,12,28,20,0,0,0,0,0,0,0,36,36,23,0,0,0,0,0,0,0,0,0,0,0,0,12,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,12,0,0,20,23,7,0,0,0,0,0,0,0,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,7,0,0,12,20,12,0,0,0,0,0,0,0,15,36,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,28,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,12,0,0,0,12,12,0,0,0,0,0,0,0,0,28,36,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,23,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,7,12,0,0,0,0,0,0,0,0,0,28,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,15,20,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,15,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,}; + const uint8_t * SHINE_TEXTURES[10] = { + SHINE_TEXTURE_0, + SHINE_TEXTURE_1, + SHINE_TEXTURE_2, + SHINE_TEXTURE_3, + SHINE_TEXTURE_4, + SHINE_TEXTURE_5, + SHINE_TEXTURE_6, + SHINE_TEXTURE_7, + SHINE_TEXTURE_8, + SHINE_TEXTURE_9, + }; + const int SHINE_TEXTURE_WIDTHS[10] = { + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + }; + const int SHINE_TEXTURE_HEIGHTS[10] = { + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + 128, + }; + } +} +#endif diff --git a/src/igl/opengl2/sort_triangles.cpp b/src/igl/opengl2/sort_triangles.cpp new file mode 100644 index 000000000..ae9671099 --- /dev/null +++ b/src/igl/opengl2/sort_triangles.cpp @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sort_triangles.h" +#include "project.h" +#include "../sort_triangles.h" +#include "gl.h" +#include "../sort.h" +#include "../slice.h" +#include "../barycenter.h" +#include +template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> +void igl::opengl2::sort_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I) +{ + using namespace Eigen; + using namespace std; + // Put model, projection, and viewport matrices into double arrays + Matrix4d MV; + Matrix4d P; + glGetDoublev(GL_MODELVIEW_MATRIX, MV.data()); + glGetDoublev(GL_PROJECTION_MATRIX, P.data()); + if(V.cols() == 3) + { + Matrix hV; + hV.resize(V.rows(),4); + hV.block(0,0,V.rows(),V.cols()) = V; + hV.col(3).setConstant(1); + return igl::sort_triangles(hV,F,MV,P,FF,I); + }else + { + return igl::sort_triangles(V,F,MV,P,FF,I); + } +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> +void igl::opengl2::sort_triangles_slow( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I) +{ + using namespace Eigen; + using namespace std; + // Barycenter, centroid + Eigen::Matrix D,sD; + Eigen::Matrix BC; + D.resize(F.rows(),3); + barycenter(V,F,BC); + for(int f = 0;f bc,pbc; + bc = BC.row(f); + project(bc,pbc); + D(f) = pbc(2); + } + sort(D,1,false,sD,I); + slice(F,I,1,FF); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::opengl2::sort_triangles, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::opengl2::sort_triangles_slow, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/opengl2/sort_triangles.h b/src/igl/opengl2/sort_triangles.h new file mode 100644 index 000000000..53fd3e809 --- /dev/null +++ b/src/igl/opengl2/sort_triangles.h @@ -0,0 +1,70 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_SORT_TRIANGLES_H +#define IGL_OPENGL2_SORT_TRIANGLES_H + +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace opengl2 + { + template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> + IGL_INLINE void sort_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I); + template < + typename DerivedV, + typename DerivedF, + typename DerivedFF, + typename DerivedI> + IGL_INLINE void sort_triangles_slow( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I); + //template < + // typename DerivedV, + // typename DerivedF, + // typename DerivedMV, + // typename DerivedP, + // typename DerivedFF, + // typename DerivedI> + //IGL_INLINE void sort_triangles_robust( + // const Eigen::PlainObjectBase & V, + // const Eigen::PlainObjectBase & F, + // const Eigen::PlainObjectBase & MV, + // const Eigen::PlainObjectBase & P, + // Eigen::PlainObjectBase & FF, + // Eigen::PlainObjectBase & I); + //template < + // typename DerivedV, + // typename DerivedF, + // typename DerivedFF, + // typename DerivedI> + //IGL_INLINE void sort_triangles_robust( + // const Eigen::PlainObjectBase & V, + // const Eigen::PlainObjectBase & F, + // Eigen::PlainObjectBase & FF, + // Eigen::PlainObjectBase & I); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "sort_triangles.cpp" +#endif + +#endif + diff --git a/src/igl/opengl2/unproject.cpp b/src/igl/opengl2/unproject.cpp new file mode 100644 index 000000000..f6b0013fe --- /dev/null +++ b/src/igl/opengl2/unproject.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject.h" +#include "model_proj_viewport.h" +#include "../unproject.h" +#include "gl.h" + +#include +#include + +IGL_INLINE void igl::opengl2::unproject( + const double winX, + const double winY, + const double winZ, + double* objX, + double* objY, + double* objZ) +{ + Eigen::Vector3d obj; + igl::opengl2::unproject(Eigen::Vector3d(winX,winY,winZ),obj); + *objX = obj(0); + *objY = obj(1); + *objZ = obj(2); +} + +template +IGL_INLINE void igl::opengl2::unproject( + const Eigen::PlainObjectBase & win, + Eigen::PlainObjectBase & obj) +{ + const auto ret = igl::opengl2::unproject(win); + obj = ret.template cast(); +} + + +template +IGL_INLINE Derivedwin igl::opengl2::unproject( + const Eigen::PlainObjectBase & win) +{ + using namespace Eigen; + typedef typename Derivedwin::Scalar Scalar; + Matrix4d MV,P; + Vector4d VPd; + model_proj_viewport(MV,P,VPd); + Vector3d wind = win.template cast(); + Vector3d objd = igl::unproject(wind,MV,P,VPd); + return objd.template cast(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::opengl2::unproject, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::opengl2::unproject, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix igl::opengl2::unproject >(Eigen::PlainObjectBase > const&); +template Eigen::Matrix igl::opengl2::unproject >(Eigen::PlainObjectBase > const&); +template void igl::opengl2::unproject, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/opengl2/unproject.h b/src/igl/opengl2/unproject.h new file mode 100644 index 000000000..00de13077 --- /dev/null +++ b/src/igl/opengl2/unproject.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_UNPROJECT_H +#define IGL_OPENGL2_UNPROJECT_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace opengl2 + { + // Wrapper for gluUnproject that uses the current GL_MODELVIEW_MATRIX, + // GL_PROJECTION_MATRIX, and GL_VIEWPORT + // Inputs: + // win* screen space x, y, and z coordinates respectively + // Outputs: + // obj* pointers to 3D objects' x, y, and z coordinates respectively + // Returns return value of gluUnProject call + IGL_INLINE void unproject( + const double winX, + const double winY, + const double winZ, + double* objX, + double* objY, + double* objZ); + template + IGL_INLINE void unproject( + const Eigen::PlainObjectBase & win, + Eigen::PlainObjectBase & obj); + template + IGL_INLINE Derivedwin unproject( + const Eigen::PlainObjectBase & win); + } +} + + +#ifndef IGL_STATIC_LIBRARY +# include "unproject.cpp" +#endif + +#endif + diff --git a/src/igl/opengl2/unproject_to_zero_plane.cpp b/src/igl/opengl2/unproject_to_zero_plane.cpp new file mode 100644 index 000000000..1c2945886 --- /dev/null +++ b/src/igl/opengl2/unproject_to_zero_plane.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_to_zero_plane.h" + +#include "gl.h" + +#include "project.h" +#include "unproject.h" + +IGL_INLINE void igl::opengl2::unproject_to_zero_plane( + const double winX, + const double winY, + double* objX, + double* objY, + double* objZ) +{ + double winOrigin[3]; + igl::opengl2::project(0,0,0,&winOrigin[0],&winOrigin[1],&winOrigin[2]); + return igl::opengl2::unproject(winX, winY, winOrigin[2], objX, objY, objZ); +} + +template +IGL_INLINE void igl::opengl2::unproject_to_zero_plane( + const Eigen::PlainObjectBase & win, + Eigen::PlainObjectBase & obj) +{ + return unproject_to_zero_plane(win(0),win(1), + &obj.data()[0], + &obj.data()[1], + &obj.data()[2]); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::opengl2::unproject_to_zero_plane, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::opengl2::unproject_to_zero_plane, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::opengl2::unproject_to_zero_plane, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/opengl2/unproject_to_zero_plane.h b/src/igl/opengl2/unproject_to_zero_plane.h new file mode 100644 index 000000000..d76337e20 --- /dev/null +++ b/src/igl/opengl2/unproject_to_zero_plane.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_UNPROJECT_TO_ZERO_PLANE_H +#define IGL_OPENGL2_UNPROJECT_TO_ZERO_PLANE_H +#include "../igl_inline.h" +#include +namespace igl +{ + namespace opengl2 + { + // Wrapper for gluUnproject that uses the current GL_MODELVIEW_MATRIX, + // GL_PROJECTION_MATRIX, and GL_VIEWPORT to unproject a screen position + // (winX,winY) to a 3d location at same depth as the current origin. + // Inputs: + // win* screen space x, y, and z coordinates respectively + // Outputs: + // obj* pointers to 3D objects' x, y, and z coordinates respectively + // Returns return value of gluUnProject call + IGL_INLINE void unproject_to_zero_plane( + const double winX, + const double winY, + double* objX, + double* objY, + double* objZ); + template + IGL_INLINE void unproject_to_zero_plane( + const Eigen::PlainObjectBase & win, + Eigen::PlainObjectBase & obj); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "unproject_to_zero_plane.cpp" +#endif + +#endif + diff --git a/src/igl/opengl2/up_axis.cpp b/src/igl/opengl2/up_axis.cpp new file mode 100644 index 000000000..c6b3c02a0 --- /dev/null +++ b/src/igl/opengl2/up_axis.cpp @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "up_axis.h" +#include "gl.h" + +IGL_INLINE void igl::opengl2::up_axis(double * x, double * y, double * z) +{ + double mv[16]; + glGetDoublev(GL_MODELVIEW_MATRIX, mv); + igl::opengl2::up_axis(mv,x,y,z); +} + +IGL_INLINE void igl::opengl2::up_axis(const double *mv, double * x, double * y, double * z) +{ + *x = -mv[0*4+1]; + *y = -mv[1*4+1]; + *z = -mv[2*4+1]; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +#endif diff --git a/src/igl/opengl2/up_axis.h b/src/igl/opengl2/up_axis.h new file mode 100644 index 000000000..2c042fb2c --- /dev/null +++ b/src/igl/opengl2/up_axis.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_UP_AXIS_H +#define IGL_OPENGL2_UP_AXIS_H +#include "../igl_inline.h" +namespace igl +{ + namespace opengl2 + { + // Determines the up axis or depth axis of the current gl matrix + // Outputs: + // x pointer to x-coordinate in scene coordinates of the un-normalized + // up axis + // y pointer to y-coordinate in scene coordinates of the un-normalized + // up axis + // z pointer to z-coordinate in scene coordinates of the un-normalized + // up axis + // mv pointer to modelview matrix + // + // Note: Up axis is returned *UN-normalized* + IGL_INLINE void up_axis(double * x, double * y, double * z); + IGL_INLINE void up_axis(const double * mv, double * x, double * y, double * z); + } +}; + +#ifndef IGL_STATIC_LIBRARY +# include "up_axis.cpp" +#endif +#endif + + diff --git a/src/igl/opengl2/view_axis.cpp b/src/igl/opengl2/view_axis.cpp new file mode 100644 index 000000000..5145cd207 --- /dev/null +++ b/src/igl/opengl2/view_axis.cpp @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "view_axis.h" +#include "gl.h" + +IGL_INLINE void igl::opengl2::view_axis(double * x, double * y, double * z) +{ + double mv[16]; + glGetDoublev(GL_MODELVIEW_MATRIX, mv); + igl::opengl2::view_axis(mv,x,y,z); +} + +IGL_INLINE void igl::opengl2::view_axis(const double * mv, double * x, double * y, double * z) +{ + *x = -mv[0*4+2]; + *y = -mv[1*4+2]; + *z = -mv[2*4+2]; +} + +template +IGL_INLINE void igl::opengl2::view_axis(Eigen::PlainObjectBase & V) +{ + double x,y,z; + view_axis(&x,&y,&z); + V(0) = x; + V(1) = y; + V(2) = z; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::opengl2::view_axis >(Eigen::PlainObjectBase >&); +template void igl::opengl2::view_axis >(Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/opengl2/view_axis.h b/src/igl/opengl2/view_axis.h new file mode 100644 index 000000000..ab324cbad --- /dev/null +++ b/src/igl/opengl2/view_axis.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OPENGL2_VIEW_AXIS_H +#define IGL_OPENGL2_VIEW_AXIS_H +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace opengl2 + { + // Determines the view axis or depth axis of the current gl matrix + // Inputs: + // mv pointer to modelview matrix + // Outputs: + // x pointer to x-coordinate in scene coordinates of the un-normalized + // viewing axis + // y pointer to y-coordinate in scene coordinates of the un-normalized + // viewing axis + // z pointer to z-coordinate in scene coordinates of the un-normalized + // viewing axis + // + // Note: View axis is returned *UN-normalized* + IGL_INLINE void view_axis(const double * mv, double * x, double * y, double * z); + // Extract mv from current GL state. + IGL_INLINE void view_axis(double * x, double * y, double * z); + template + IGL_INLINE void view_axis(Eigen::PlainObjectBase & V); + } +}; + + +#ifndef IGL_STATIC_LIBRARY +# include "view_axis.cpp" +#endif + +#endif + diff --git a/src/igl/orient_outward.cpp b/src/igl/orient_outward.cpp new file mode 100644 index 000000000..522745bc4 --- /dev/null +++ b/src/igl/orient_outward.cpp @@ -0,0 +1,95 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "orient_outward.h" +#include "per_face_normals.h" +#include "barycenter.h" +#include "doublearea.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedC, + typename DerivedFF, + typename DerivedI> +IGL_INLINE void igl::orient_outward( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I) +{ + using namespace Eigen; + using namespace std; + assert(C.rows() == F.rows()); + assert(F.cols() == 3); + assert(V.cols() == 3); + + // number of faces + const int m = F.rows(); + // number of patches + const int num_cc = C.maxCoeff()+1; + I.resize(num_cc); + if(&FF != &F) + { + FF = F; + } + DerivedV N,BC,BCmean; + Matrix A; + VectorXd totA(num_cc), dot(num_cc); + Matrix Z(1,1,1); + per_face_normals(V,F,Z.normalized(),N); + barycenter(V,F,BC); + doublearea(V,F,A); + BCmean.setConstant(num_cc,3,0); + dot.setConstant(num_cc,1,0); + totA.setConstant(num_cc,1,0); + // loop over faces + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/orient_outward.h b/src/igl/orient_outward.h new file mode 100644 index 000000000..197c8b950 --- /dev/null +++ b/src/igl/orient_outward.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ORIENT_OUTWARD_H +#define IGL_ORIENT_OUTWARD_H +#include "igl_inline.h" +#include +namespace igl +{ + // Orient each component (identified by C) of a mesh (V,F) so the normals on + // average point away from the patch's centroid. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // C #F list of components (output of orientable_patches) + // Outputs: + // FF #F by 3 list of new triangle indices such that FF(~I,:) = F(~I,:) and + // FF(I,:) = fliplr(F(I,:)) (OK if &FF = &F) + // I max(C)+1 list of whether face has been flipped + template < + typename DerivedV, + typename DerivedF, + typename DerivedC, + typename DerivedFF, + typename DerivedI> + IGL_INLINE void orient_outward( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "orient_outward.cpp" +#endif + +#endif diff --git a/src/igl/orientable_patches.cpp b/src/igl/orientable_patches.cpp new file mode 100644 index 000000000..fa2ea1bd4 --- /dev/null +++ b/src/igl/orientable_patches.cpp @@ -0,0 +1,105 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "orientable_patches.h" +#include "components.h" +#include "sort.h" +#include "unique_rows.h" +#include +#include + +template +IGL_INLINE void igl::orientable_patches( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C, + Eigen::SparseMatrix & A) +{ + using namespace Eigen; + using namespace std; + + // simplex size + assert(F.cols() == 3); + + // List of all "half"-edges: 3*#F by 2 + Matrix allE,sortallE,uE; + allE.resize(F.rows()*3,2); + Matrix IX; + VectorXi IA,IC; + allE.block(0*F.rows(),0,F.rows(),1) = F.col(1); + allE.block(0*F.rows(),1,F.rows(),1) = F.col(2); + allE.block(1*F.rows(),0,F.rows(),1) = F.col(2); + allE.block(1*F.rows(),1,F.rows(),1) = F.col(0); + allE.block(2*F.rows(),0,F.rows(),1) = F.col(0); + allE.block(2*F.rows(),1,F.rows(),1) = F.col(1); + // Sort each row + sort(allE,2,true,sortallE,IX); + //IC(i) tells us where to find sortallE(i,:) in uE: + // so that sortallE(i,:) = uE(IC(i),:) + unique_rows(sortallE,uE,IA,IC); + // uE2FT(e,f) = 1 means face f is adjacent to unique edge e + vector > uE2FTijv(IC.rows()); + for(int e = 0;e(e%F.rows(),IC(e),1); + } + SparseMatrix uE2FT(F.rows(),uE.rows()); + uE2FT.setFromTriplets(uE2FTijv.begin(),uE2FTijv.end()); + // kill non-manifold edges + for(int j=0; j<(int)uE2FT.outerSize();j++) + { + int degree = 0; + for(typename SparseMatrix::InnerIterator it (uE2FT,j); it; ++it) + { + degree++; + } + // Iterate over inside + if(degree > 2) + { + for(typename SparseMatrix::InnerIterator it (uE2FT,j); it; ++it) + { + uE2FT.coeffRef(it.row(),it.col()) = 0; + } + } + } + // Face-face Adjacency matrix + SparseMatrix uE2F; + uE2F = uE2FT.transpose().eval(); + A = uE2FT*uE2F; + // All ones + for(int j=0; j::InnerIterator it (A,j); it; ++it) + { + if(it.value() > 1) + { + A.coeffRef(it.row(),it.col()) = 1; + } + } + } + //% Connected components are patches + //%C = components(A); % alternative to graphconncomp from matlab_bgl + //[~,C] = graphconncomp(A); + // graph connected components + components(A,C); + +} + +template +IGL_INLINE void igl::orientable_patches( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C) +{ + Eigen::SparseMatrix A; + return orientable_patches(F,C,A); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::orientable_patches, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::SparseMatrix&); +template void igl::orientable_patches, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/orientable_patches.h b/src/igl/orientable_patches.h new file mode 100644 index 000000000..a74284ac5 --- /dev/null +++ b/src/igl/orientable_patches.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ORIENTABLE_PATCHES_H +#define IGL_ORIENTABLE_PATCHES_H +#include +#include +#include +namespace igl +{ + // Compute connected components of facets connected by manifold edges. + // + // Known bugs: This will detect a moebius strip as a single patch (manifold, + // non-orientable) and also non-manfiold, yet orientable patches. + // + // Q: Does this find exactly (manifold || orientable) patches? + // + // Inputs: + // F #F by simplex-size list of facets + // Outputs: + // C #F list of component ids + // A #F by #F adjacency matrix + // + template + IGL_INLINE void orientable_patches( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C, + Eigen::SparseMatrix & A); + template + IGL_INLINE void orientable_patches( + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & C); +}; +#ifndef IGL_STATIC_LIBRARY +# include "orientable_patches.cpp" +#endif +#endif diff --git a/src/igl/oriented_facets.cpp b/src/igl/oriented_facets.cpp new file mode 100644 index 000000000..722679335 --- /dev/null +++ b/src/igl/oriented_facets.cpp @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "oriented_facets.h" + +template +IGL_INLINE void igl::oriented_facets( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E) +{ + E.resize(F.rows()*F.cols(),F.cols()-1); + typedef typename DerivedE::Scalar EScalar; + switch(F.cols()) + { + case 4: + E.block(0*F.rows(),0,F.rows(),1) = F.col(1).template cast(); + E.block(0*F.rows(),1,F.rows(),1) = F.col(3).template cast(); + E.block(0*F.rows(),2,F.rows(),1) = F.col(2).template cast(); + + E.block(1*F.rows(),0,F.rows(),1) = F.col(0).template cast(); + E.block(1*F.rows(),1,F.rows(),1) = F.col(2).template cast(); + E.block(1*F.rows(),2,F.rows(),1) = F.col(3).template cast(); + + E.block(2*F.rows(),0,F.rows(),1) = F.col(0).template cast(); + E.block(2*F.rows(),1,F.rows(),1) = F.col(3).template cast(); + E.block(2*F.rows(),2,F.rows(),1) = F.col(1).template cast(); + + E.block(3*F.rows(),0,F.rows(),1) = F.col(0).template cast(); + E.block(3*F.rows(),1,F.rows(),1) = F.col(1).template cast(); + E.block(3*F.rows(),2,F.rows(),1) = F.col(2).template cast(); + return; + case 3: + E.block(0*F.rows(),0,F.rows(),1) = F.col(1).template cast(); + E.block(0*F.rows(),1,F.rows(),1) = F.col(2).template cast(); + E.block(1*F.rows(),0,F.rows(),1) = F.col(2).template cast(); + E.block(1*F.rows(),1,F.rows(),1) = F.col(0).template cast(); + E.block(2*F.rows(),0,F.rows(),1) = F.col(0).template cast(); + E.block(2*F.rows(),1,F.rows(),1) = F.col(1).template cast(); + return; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::oriented_facets, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/oriented_facets.h b/src/igl/oriented_facets.h new file mode 100644 index 000000000..b0a66ef08 --- /dev/null +++ b/src/igl/oriented_facets.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ORIENTED_FACETS_H +#define IGL_ORIENTED_FACETS_H +#include "igl_inline.h" +#include +namespace igl +{ + // ORIENTED_FACETS Determines all "directed + // [facets](https://en.wikipedia.org/wiki/Simplex#Elements)" of a given set + // of simplicial elements. For a manifold triangle mesh, this computes all + // half-edges. For a manifold tetrahedral mesh, this computes all half-faces. + // + // Inputs: + // F #F by simplex_size list of simplices + // Outputs: + // E #E by simplex_size-1 list of facets + // + // Note: this is not the same as igl::edges because this includes every + // directed edge including repeats (meaning interior edges on a surface will + // show up once for each direction and non-manifold edges may appear more than + // once for each direction). + // + // Note: This replaces the deprecated `all_edges` function + template + IGL_INLINE void oriented_facets( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E); +} + +#ifndef IGL_STATIC_LIBRARY +# include "oriented_facets.cpp" +#endif + +#endif + diff --git a/src/igl/orth.cpp b/src/igl/orth.cpp new file mode 100644 index 000000000..467a4993e --- /dev/null +++ b/src/igl/orth.cpp @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "orth.h" + +// Broken Implementation +IGL_INLINE void igl::orth(const Eigen::MatrixXd &A, Eigen::MatrixXd &Q) +{ + + //perform svd on A = U*S*V' (V is not computed and only the thin U is computed) + Eigen::JacobiSVD svd(A, Eigen::ComputeThinU ); + Eigen::MatrixXd U = svd.matrixU(); + const Eigen::VectorXd S = svd.singularValues(); + + //get rank of A + int m = A.rows(); + int n = A.cols(); + double tol = std::max(m,n) * S.maxCoeff() * 2.2204e-16; + int r = 0; + for (int i = 0; i < S.rows(); ++r,++i) + { + if (S[i] < tol) + break; + } + + //keep r first columns of U + Q = U.block(0,0,U.rows(),r); +} diff --git a/src/igl/orth.h b/src/igl/orth.h new file mode 100644 index 000000000..a77cd6593 --- /dev/null +++ b/src/igl/orth.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ORTH_H +#define IGL_ORTH_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // ORTH Orthogonalization. + // ORTH(A,Q) produces Q as an orthonormal basis for the range of A. + // That is, Q'*Q = I, the columns of Q span the same space as + // the columns of A, and the number of columns of Q is the + // rank of A. + // + // + // The algorithm uses singular value decomposition, SVD, instead of orthogonal + // factorization, QR. This doubles the computation time, but + // provides more reliable and consistent rank determination. + // Closely follows MATLAB implementation in orth.m + // + // Inputs: + // A m by n matrix + // Outputs: + // Q m by n matrix with orthonormal columns spanning same column space as + // A + // + // Known bugs: Implementation listed as "Broken" + IGL_INLINE void orth(const Eigen::MatrixXd &A, Eigen::MatrixXd &Q); +} + + +#ifndef IGL_STATIC_LIBRARY +# include "orth.cpp" +#endif +#endif + diff --git a/src/igl/ortho.cpp b/src/igl/ortho.cpp new file mode 100644 index 000000000..e634eed74 --- /dev/null +++ b/src/igl/ortho.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ortho.h" + +template < typename DerivedP> +IGL_INLINE void igl::ortho( + const typename DerivedP::Scalar left, + const typename DerivedP::Scalar right, + const typename DerivedP::Scalar bottom, + const typename DerivedP::Scalar top, + const typename DerivedP::Scalar nearVal, + const typename DerivedP::Scalar farVal, + Eigen::PlainObjectBase & P) +{ + P.setIdentity(); + P(0,0) = 2. / (right - left); + P(1,1) = 2. / (top - bottom); + P(2,2) = - 2./ (farVal - nearVal); + P(0,3) = - (right + left) / (right - left); + P(1,3) = - (top + bottom) / (top - bottom); + P(2,3) = - (farVal + nearVal) / (farVal - nearVal); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::ortho >(Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ortho.h b/src/igl/ortho.h new file mode 100644 index 000000000..70b9cab4d --- /dev/null +++ b/src/igl/ortho.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ORTHO_H +#define IGL_ORTHO_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Implementation of the deprecated glOrtho function. + // + // Inputs: + // left coordinate of left vertical clipping plane + // right coordinate of right vertical clipping plane + // bottom coordinate of bottom vertical clipping plane + // top coordinate of top vertical clipping plane + // nearVal distance to near plane + // farVal distance to far plane + // Outputs: + // P 4x4 perspective matrix + template < typename DerivedP> + IGL_INLINE void ortho( + const typename DerivedP::Scalar left, + const typename DerivedP::Scalar right, + const typename DerivedP::Scalar bottom, + const typename DerivedP::Scalar top, + const typename DerivedP::Scalar nearVal, + const typename DerivedP::Scalar farVal, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "ortho.cpp" +#endif + +#endif diff --git a/src/igl/outer_element.cpp b/src/igl/outer_element.cpp new file mode 100644 index 000000000..2ce837780 --- /dev/null +++ b/src/igl/outer_element.cpp @@ -0,0 +1,280 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "outer_element.h" +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > +IGL_INLINE void igl::outer_vertex( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v_index, + Eigen::PlainObjectBase & A) +{ + // Algorithm: + // Find an outer vertex (i.e. vertex reachable from infinity) + // Return the vertex with the largest X value. + // If there is a tie, pick the one with largest Y value. + // If there is still a tie, pick the one with the largest Z value. + // If there is still a tie, then there are duplicated vertices within the + // mesh, which violates the precondition. + typedef typename DerivedF::Scalar Index; + const Index INVALID = std::numeric_limits::max(); + const size_t num_selected_faces = I.rows(); + std::vector candidate_faces; + Index outer_vid = INVALID; + typename DerivedV::Scalar outer_val = 0; + for (size_t i=0; i outer_val) + { + outer_val = vx; + outer_vid = v; + candidate_faces = {f}; + } else if (v == outer_vid) + { + candidate_faces.push_back(f); + } else if (vx == outer_val) + { + // Break tie. + auto vy = V(v,1); + auto vz = V(v, 2); + auto outer_y = V(outer_vid, 1); + auto outer_z = V(outer_vid, 2); + assert(!(vy == outer_y && vz == outer_z)); + bool replace = (vy > outer_y) || + ((vy == outer_y) && (vz > outer_z)); + if (replace) + { + outer_val = vx; + outer_vid = v; + candidate_faces = {f}; + } + } + } + } + + assert(outer_vid != INVALID); + assert(candidate_faces.size() > 0); + v_index = outer_vid; + A.resize(candidate_faces.size()); + std::copy(candidate_faces.begin(), candidate_faces.end(), A.data()); +} + +template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > +IGL_INLINE void igl::outer_edge( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v1, + IndexType & v2, + Eigen::PlainObjectBase & A) { + // Algorithm: + // Find an outer vertex first. + // Find the incident edge with largest abs slope when projected onto XY plane. + // If there is a tie, check the signed slope and use the positive one. + // If there is still a tie, break it using the projected slope onto ZX plane. + // If there is still a tie, again check the signed slope and use the positive one. + // If there is still a tie, then there are multiple overlapping edges, + // which violates the precondition. + typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedV::Index Index; + typedef typename Eigen::Matrix ScalarArray3; + typedef typename Eigen::Matrix IndexArray3; + const Index INVALID = std::numeric_limits::max(); + + Index outer_vid; + Eigen::Matrix candidate_faces; + outer_vertex(V, F, I, outer_vid, candidate_faces); + const ScalarArray3& outer_v = V.row(outer_vid); + assert(candidate_faces.size() > 0); + + auto get_vertex_index = [&](const IndexArray3& f, Index vid) -> Index + { + if (f[0] == vid) return 0; + if (f[1] == vid) return 1; + if (f[2] == vid) return 2; + assert(false); + return -1; + }; + + auto unsigned_value = [](Scalar v) -> Scalar { + if (v < 0) return v * -1; + else return v; + }; + + Scalar outer_slope_YX = 0; + Scalar outer_slope_ZX = 0; + Index outer_opp_vid = INVALID; + bool infinite_slope_detected = false; + std::vector incident_faces; + auto check_and_update_outer_edge = [&](Index opp_vid, Index fid) { + if (opp_vid == outer_opp_vid) + { + incident_faces.push_back(fid); + return; + } + + const ScalarArray3 opp_v = V.row(opp_vid); + if (!infinite_slope_detected && outer_v[0] != opp_v[0]) + { + // Finite slope + const ScalarArray3 diff = opp_v - outer_v; + const Scalar slope_YX = diff[1] / diff[0]; + const Scalar slope_ZX = diff[2] / diff[0]; + const Scalar u_slope_YX = unsigned_value(slope_YX); + const Scalar u_slope_ZX = unsigned_value(slope_ZX); + bool update = false; + if (outer_opp_vid == INVALID) { + update = true; + } else { + const Scalar u_outer_slope_YX = unsigned_value(outer_slope_YX); + if (u_slope_YX > u_outer_slope_YX) { + update = true; + } else if (u_slope_YX == u_outer_slope_YX && + slope_YX > outer_slope_YX) { + update = true; + } else if (slope_YX == outer_slope_YX) { + const Scalar u_outer_slope_ZX = + unsigned_value(outer_slope_ZX); + if (u_slope_ZX > u_outer_slope_ZX) { + update = true; + } else if (u_slope_ZX == u_outer_slope_ZX && + slope_ZX > outer_slope_ZX) { + update = true; + } else if (slope_ZX == u_outer_slope_ZX) { + assert(false); + } + } + } + + if (update) { + outer_opp_vid = opp_vid; + outer_slope_YX = slope_YX; + outer_slope_ZX = slope_ZX; + incident_faces = {fid}; + } + } else if (!infinite_slope_detected) + { + // Infinite slope + outer_opp_vid = opp_vid; + infinite_slope_detected = true; + incident_faces = {fid}; + } + }; + + const size_t num_candidate_faces = candidate_faces.size(); + for (size_t i=0; i +IGL_INLINE void igl::outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped) { + // Algorithm: + // Find an outer edge. + // Find the incident facet with the largest absolute X normal component. + // If there is a tie, keep the one with positive X component. + // If there is still a tie, pick the face with the larger signed index + // (flipped face has negative index). + typedef typename DerivedV::Scalar Scalar; + typedef typename DerivedV::Index Index; + const size_t INVALID = std::numeric_limits::max(); + + Index v1,v2; + Eigen::Matrix incident_faces; + outer_edge(V, F, I, v1, v2, incident_faces); + assert(incident_faces.size() > 0); + + auto generic_fabs = [&](const Scalar& val) -> const Scalar { + if (val >= 0) return val; + else return -val; + }; + + Scalar max_nx = 0; + size_t outer_fid = INVALID; + const size_t num_incident_faces = incident_faces.size(); + for (size_t i=0; i generic_fabs(max_nx)) { + max_nx = nx; + outer_fid = fid; + } else if (nx == -max_nx && nx > 0) { + max_nx = nx; + outer_fid = fid; + } else if (nx == max_nx) { + if ((max_nx >= 0 && outer_fid < fid) || + (max_nx < 0 && outer_fid > fid)) { + max_nx = nx; + outer_fid = fid; + } + } + } + } + + assert(outer_fid != INVALID); + f = outer_fid; + flipped = max_nx < 0; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, unsigned long&, bool&); +template void igl::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +template void igl::outer_facet, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, bool&); +#endif diff --git a/src/igl/outer_element.h b/src/igl/outer_element.h new file mode 100644 index 000000000..6fbaf9c5d --- /dev/null +++ b/src/igl/outer_element.h @@ -0,0 +1,110 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_OUTER_ELEMENT_H +#define IGL_OUTER_ELEMENT_H +#include "igl_inline.h" +#include +namespace igl +{ + // Find a vertex that is reachable from infinite without crossing any faces. + // Such vertex is called "outer vertex." + // + // Precondition: The input mesh must have all self-intersection resolved and + // no duplicated vertices. See cgal::remesh_self_intersections.h for how to + // obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // I #I list of facets to consider + // Outputs: + // v_index index of outer vertex + // A #A list of facets incident to the outer vertex + template < + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > + IGL_INLINE void outer_vertex( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v_index, + Eigen::PlainObjectBase & A); + + + // Find an edge that is reachable from infinity without crossing any faces. + // Such edge is called "outer edge." + // + // Precondition: The input mesh must have all self-intersection resolved and + // no duplicated vertices. The correctness of the output depends on the fact + // that there is no edge overlap. See cgal::remesh_self_intersections.h for + // how to obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // I #I list of facets to consider + // Outputs: + // v1 index of the first end point of outer edge + // v2 index of the second end point of outer edge + // A #A list of facets incident to the outer edge + template< + typename DerivedV, + typename DerivedF, + typename DerivedI, + typename IndexType, + typename DerivedA + > + IGL_INLINE void outer_edge( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & I, + IndexType & v1, + IndexType & v2, + Eigen::PlainObjectBase & A); + + + // Find a facet that is reachable from infinity without crossing any faces. + // Such facet is called "outer facet." + // + // Precondition: The input mesh must have all self-intersection resolved. I.e + // there is no duplicated vertices, no overlapping edge and no intersecting + // faces (the only exception is there could be topologically duplicated faces). + // See cgal::remesh_self_intersections.h for how to obtain such input. + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices into V + // N #N by 3 list of face normals + // I #I list of facets to consider + // Outputs: + // f Index of the outer facet. + // flipped true iff the normal of f points inwards. + template< + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedI, + typename IndexType + > + IGL_INLINE void outer_facet( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const Eigen::PlainObjectBase & I, + IndexType & f, + bool & flipped); +} + +#ifndef IGL_STATIC_LIBRARY +# include "outer_element.cpp" +#endif +#endif diff --git a/src/igl/parallel_for.h b/src/igl/parallel_for.h new file mode 100644 index 000000000..458adace5 --- /dev/null +++ b/src/igl/parallel_for.h @@ -0,0 +1,188 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PARALLEL_FOR_H +#define IGL_PARALLEL_FOR_H +#include "igl_inline.h" +#include + +//#warning "Defining IGL_PARALLEL_FOR_FORCE_SERIAL" +//#define IGL_PARALLEL_FOR_FORCE_SERIAL + +namespace igl +{ + // PARALLEL_FOR Functional implementation of a basic, open-mp style, parallel + // for loop. If the inner block of a for-loop can be rewritten/encapsulated in + // a single (anonymous/lambda) function call `func` so that the serial code + // looks like: + // + // for(int i = 0;i + inline bool parallel_for( + const Index loop_size, + const FunctionType & func, + const size_t min_parallel=0); + // PARALLEL_FOR Functional implementation of an open-mp style, parallel for + // loop with accumulation. For example, serial code separated into n chunks + // (each to be parallelized with a thread) might look like: + // + // Eigen::VectorXd S; + // const auto & prep_func = [&S](int n){ S = Eigen:VectorXd::Zero(n); }; + // const auto & func = [&X,&S](int i, int t){ S(t) += X(i); }; + // const auto & accum_func = [&S,&sum](int t){ sum += S(t); }; + // prep_func(n); + // for(int i = 0;i= number of threads as only + // argument + // func function handle taking iteration index i and thread id t as only + // arguments to compute inner block of for loop I.e. + // for(int i ...){ func(i,t); } + // accum_func function handle taking thread index as only argument, to be + // called after all calls of func, e.g., for serial accumulation across + // all n (potential) threads, see n in description of prep_func. + // min_parallel min size of loop_size such that parallel (non-serial) + // thread pooling should be attempted {0} + // Returns true iff thread pool was invoked + template< + typename Index, + typename PrepFunctionType, + typename FunctionType, + typename AccumFunctionType + > + inline bool parallel_for( + const Index loop_size, + const PrepFunctionType & prep_func, + const FunctionType & func, + const AccumFunctionType & accum_func, + const size_t min_parallel=0); +} + +// Implementation + +#include +#include +#include +#include +#include + +template +inline bool igl::parallel_for( + const Index loop_size, + const FunctionType & func, + const size_t min_parallel) +{ + using namespace std; + // no op preparation/accumulation + const auto & no_op = [](const size_t /*n/t*/){}; + // two-parameter wrapper ignoring thread id + const auto & wrapper = [&func](Index i,size_t /*t*/){ func(i); }; + return parallel_for(loop_size,no_op,wrapper,no_op,min_parallel); +} + +template< + typename Index, + typename PreFunctionType, + typename FunctionType, + typename AccumFunctionType> +inline bool igl::parallel_for( + const Index loop_size, + const PreFunctionType & prep_func, + const FunctionType & func, + const AccumFunctionType & accum_func, + const size_t min_parallel) +{ + assert(loop_size>=0); + if(loop_size==0) return false; + // Estimate number of threads in the pool + // http://ideone.com/Z7zldb + const static size_t sthc = std::thread::hardware_concurrency(); + const size_t nthreads = +#ifdef IGL_PARALLEL_FOR_FORCE_SERIAL + 0; +#else + loop_size(nthreads)),(Index)1); + + // [Helper] Inner loop + const auto & range = [&func](const Index k1, const Index k2, const size_t t) + { + for(Index k = k1; k < k2; k++) func(k,t); + }; + prep_func(nthreads); + // Create pool and launch jobs + std::vector pool; + pool.reserve(nthreads); + // Inner range extents + Index i1 = 0; + Index i2 = std::min(0 + slice, loop_size); + { + size_t t = 0; + for (; t+1 < nthreads && i1 < loop_size; ++t) + { + pool.emplace_back(range, i1, i2, t); + i1 = i2; + i2 = std::min(i2 + slice, loop_size); + } + if (i1 < loop_size) + { + pool.emplace_back(range, i1, loop_size, t); + } + } + // Wait for jobs to finish + for (std::thread &t : pool) if (t.joinable()) t.join(); + // Accumulate across threads + for(size_t t = 0;t +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include +#include + +template +IGL_INLINE void igl::parallel_transport_angles( +const Eigen::PlainObjectBase& V, +const Eigen::PlainObjectBase& F, +const Eigen::PlainObjectBase& FN, +const Eigen::MatrixXi &E2F, +const Eigen::MatrixXi &F2E, +Eigen::PlainObjectBase &K) +{ + int numE = E2F.rows(); + + Eigen::VectorXi isBorderEdge; + isBorderEdge.setZero(numE,1); + for(unsigned i=0; i N0 = FN.row(fid0); +// Eigen::Matrix N1 = FN.row(fid1); + + // find common edge on triangle 0 and 1 + int fid0_vc = -1; + int fid1_vc = -1; + for (unsigned i=0;i<3;++i) + { + if (F2E(fid0,i) == eid) + fid0_vc = i; + if (F2E(fid1,i) == eid) + fid1_vc = i; + } + assert(fid0_vc != -1); + assert(fid1_vc != -1); + + Eigen::Matrix common_edge = V.row(F(fid0,(fid0_vc+1)%3)) - V.row(F(fid0,fid0_vc)); + common_edge.normalize(); + + // Map the two triangles in a new space where the common edge is the x axis and the N0 the z axis + Eigen::Matrix P; + Eigen::Matrix o = V.row(F(fid0,fid0_vc)); + Eigen::Matrix tmp = -N0.cross(common_edge); + P << common_edge, tmp, N0; + // P.transposeInPlace(); + + + Eigen::Matrix V0; + V0.row(0) = V.row(F(fid0,0)) -o; + V0.row(1) = V.row(F(fid0,1)) -o; + V0.row(2) = V.row(F(fid0,2)) -o; + + V0 = (P*V0.transpose()).transpose(); + + // assert(V0(0,2) < 1e-10); + // assert(V0(1,2) < 1e-10); + // assert(V0(2,2) < 1e-10); + + Eigen::Matrix V1; + V1.row(0) = V.row(F(fid1,0)) -o; + V1.row(1) = V.row(F(fid1,1)) -o; + V1.row(2) = V.row(F(fid1,2)) -o; + V1 = (P*V1.transpose()).transpose(); + + // assert(V1(fid1_vc,2) < 10e-10); + // assert(V1((fid1_vc+1)%3,2) < 10e-10); + + // compute rotation R such that R * N1 = N0 + // i.e. map both triangles to the same plane + double alpha = -atan2(V1((fid1_vc+2)%3,2),V1((fid1_vc+2)%3,1)); + + Eigen::Matrix R; + R << 1, 0, 0, + 0, cos(alpha), -sin(alpha) , + 0, sin(alpha), cos(alpha); + V1 = (R*V1.transpose()).transpose(); + + // assert(V1(0,2) < 1e-10); + // assert(V1(1,2) < 1e-10); + // assert(V1(2,2) < 1e-10); + + // measure the angle between the reference frames + // k_ij is the angle between the triangle on the left and the one on the right + Eigen::Matrix ref0 = V0.row(1) - V0.row(0); + Eigen::Matrix ref1 = V1.row(1) - V1.row(0); + + ref0.normalize(); + ref1.normalize(); + + double ktemp = atan2(ref1(1),ref1(0)) - atan2(ref0(1),ref0(0)); + + // just to be sure, rotate ref0 using angle ktemp... + Eigen::Matrix R2; + R2 << cos(ktemp), -sin(ktemp), sin(ktemp), cos(ktemp); + +// Eigen::Matrix tmp1 = R2*(ref0.head(2)).transpose(); + + // assert(tmp1(0) - ref1(0) < 1e-10); + // assert(tmp1(1) - ref1(1) < 1e-10); + + K[eid] = ktemp; + } + } + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::parallel_transport_angles, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/parallel_transport_angles.h b/src/igl/parallel_transport_angles.h new file mode 100644 index 000000000..e37d7f373 --- /dev/null +++ b/src/igl/parallel_transport_angles.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_PARALLEL_TRANSPORT_ANGLE +#define IGL_PARALLEL_TRANSPORT_ANGLE +#include "igl_inline.h" + +#include +#include + +namespace igl { + // Given the per-face local bases computed via igl::local_basis, this function + // computes the angle between the two reference frames across each edge. + // Any two vectors across the edge whose 2D representation only differs by + // this angle are considered to be parallel. + + // Inputs: + // V #V by 3 list of mesh vertex coordinates + // F #F by 3 list of mesh faces (must be triangles) + // FN #F by 3 list of face normals + // E2F #E by 2 list of the edge-to-face relation (e.g. computed + // via igl::edge_topology) + // F2E #F by 3 list of the face-to-edge relation (e.g. computed + // via igl::edge_topology) + // Output: + // K #E by 1 list of the parallel transport angles (zero + // for all boundary edges) + // +template +IGL_INLINE void parallel_transport_angles( +const Eigen::PlainObjectBase&V, +const Eigen::PlainObjectBase&F, +const Eigen::PlainObjectBase&FN, +const Eigen::MatrixXi &E2F, +const Eigen::MatrixXi &F2E, +Eigen::PlainObjectBase&K); + +}; + + +#ifndef IGL_STATIC_LIBRARY +#include "parallel_transport_angles.cpp" +#endif + + +#endif /* defined(IGL_PARALLEL_TRANSPORT_ANGLE) */ diff --git a/src/igl/partition.cpp b/src/igl/partition.cpp new file mode 100644 index 000000000..d2ef34c94 --- /dev/null +++ b/src/igl/partition.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "partition.h" +#include "mat_min.h" + +IGL_INLINE void igl::partition( + const Eigen::MatrixXd & W, + const int k, + Eigen::Matrix & G, + Eigen::Matrix & S, + Eigen::Matrix & D) +{ + // number of mesh vertices + int n = W.rows(); + + // Resize output + G.resize(n); + S.resize(k); + + // "Randomly" choose first seed + // Pick a vertex farthest from 0 + int s; + (W.array().square().matrix()).rowwise().sum().maxCoeff(&s); + + S(0) = s; + // Initialize distance to closest seed + D = ((W.rowwise() - W.row(s)).array().square()).matrix().rowwise().sum(); + G.setZero(); + + // greedily choose the remaining k-1 seeds + for(int i = 1;i Ds = + ((W.rowwise() - W.row(s)).array().square()).matrix().rowwise().sum(); + // Concatenation of D and Ds: DDs = [D Ds]; + Eigen::Matrix DDs; + // Make space for two columns + DDs.resize(D.rows(),2); + DDs.col(0) = D; + DDs.col(1) = Ds; + // Update D + // get minimum of old D and distance to this seed, C == 1 if new distance + // was smaller + Eigen::Matrix C; + igl::mat_min(DDs,2,D,C); + G = (C.array() ==0).select(G,i); + } + + +} diff --git a/src/igl/partition.h b/src/igl/partition.h new file mode 100644 index 000000000..167ebd0d7 --- /dev/null +++ b/src/igl/partition.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PARTITION_H +#define IGL_PARTITION_H +#include "igl_inline.h" +#include + +namespace igl +{ + // PARTITION partition vertices into groups based on each + // vertex's vector: vertices with similar coordinates (close in + // space) will be put in the same group. + // + // Inputs: + // W #W by dim coordinate matrix + // k desired number of groups default is dim + // Output: + // G #W list of group indices (1 to k) for each vertex, such that vertex i + // is assigned to group G(i) + // S k list of seed vertices + // D #W list of squared distances for each vertex to it's corresponding + // closest seed + IGL_INLINE void partition( + const Eigen::MatrixXd & W, + const int k, + Eigen::Matrix & G, + Eigen::Matrix & S, + Eigen::Matrix & D); +} + +#ifndef IGL_STATIC_LIBRARY +#include "partition.cpp" +#endif +#endif diff --git a/src/igl/parula.cpp b/src/igl/parula.cpp new file mode 100644 index 000000000..b3daaa5e7 --- /dev/null +++ b/src/igl/parula.cpp @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "parula.h" +#include "colormap.h" + +template +IGL_INLINE void igl::parula(const T x, T * rgb) +{ + igl::colormap(igl::COLOR_MAP_TYPE_PARULA,x, rgb); +} + +template +IGL_INLINE void igl::parula(const T f, T & r, T & g, T & b) +{ + igl::colormap(igl::COLOR_MAP_TYPE_PARULA, f, r, g, b); +} + + +template +IGL_INLINE void igl::parula( + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C) +{ + igl::colormap(igl::COLOR_MAP_TYPE_PARULA, Z, normalize, C); +} +template +IGL_INLINE void igl::parula( + const Eigen::MatrixBase & Z, + const double min_z, + const double max_z, + Eigen::PlainObjectBase & C) +{ + igl::colormap(igl::COLOR_MAP_TYPE_PARULA, Z, min_z, max_z, C); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::parula, Eigen::Matrix >(Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::parula, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::parula(double, double*); +template void igl::parula(double, double&, double&, double&); +template void igl::parula, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::parula, Eigen::Matrix >(Eigen::MatrixBase > const&, bool, Eigen::PlainObjectBase >&); +template void igl::parula, Eigen::Matrix >(Eigen::MatrixBase > const&, double, double, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/parula.h b/src/igl/parula.h new file mode 100644 index 000000000..55cbd447f --- /dev/null +++ b/src/igl/parula.h @@ -0,0 +1,62 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PARULA_H +#define IGL_PARULA_H +#include "igl_inline.h" +//#ifndef IGL_NO_EIGEN +# include +//#endif +namespace igl +{ + // PARULA like MATLAB's parula + // + // Inputs: + // m number of colors + // Outputs: + // J m by list of RGB colors between 0 and 1 + // + // Wrapper for directly computing [r,g,b] values for a given factor f between + // 0 and 1 + // + // Inputs: + // f factor determining color value as if 0 was min and 1 was max + // Outputs: + // r red value + // g green value + // b blue value + template + IGL_INLINE void parula(const T f, T * rgb); + template + IGL_INLINE void parula(const T f, T & r, T & g, T & b); + // Inputs: + // Z #Z list of factors + // normalize whether to normalize Z to be tightly between [0,1] + // Outputs: + // C #C by 3 list of rgb colors + template + IGL_INLINE void parula( + const Eigen::MatrixBase & Z, + const bool normalize, + Eigen::PlainObjectBase & C); + // Inputs: + // min_Z value at blue + // max_Z value at red + template + IGL_INLINE void parula( + const Eigen::MatrixBase & Z, + const double min_Z, + const double max_Z, + Eigen::PlainObjectBase & C); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "parula.cpp" +#endif + +#endif + diff --git a/src/igl/path_to_executable.cpp b/src/igl/path_to_executable.cpp new file mode 100644 index 000000000..509a763d6 --- /dev/null +++ b/src/igl/path_to_executable.cpp @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "path_to_executable.h" +#ifdef __APPLE__ +# include +#endif +#if defined(_WIN32) +# include +#endif +#include +IGL_INLINE std::string igl::path_to_executable() +{ + // http://pastebin.com/ffzzxPzi + using namespace std; + std::string path; + char buffer[1024]; + uint32_t size = sizeof(buffer); +#if defined (WIN32) + GetModuleFileName(nullptr,buffer,size); + path = buffer; +#elif defined (__APPLE__) + if(_NSGetExecutablePath(buffer, &size) == 0) + { + path = buffer; + } +#elif defined(UNIX) + if (readlink("/proc/self/exe", buffer, sizeof(buffer)) == -1) + { + path = buffer; + } +#elif defined(__FreeBSD__) + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + sysctl(mib, 4, buffer, sizeof(buffer), NULL, 0); + path = buffer; +#elif defined(SUNOS) + path = getexecname(); +#endif + return path; +} + diff --git a/src/igl/path_to_executable.h b/src/igl/path_to_executable.h new file mode 100644 index 000000000..169c3400b --- /dev/null +++ b/src/igl/path_to_executable.h @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PATH_TO_EXECUTABLE_H +#define IGL_PATH_TO_EXECUTABLE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Return the path of the current executable. + // Note: Tested for Mac OS X + IGL_INLINE std::string path_to_executable(); +} +#ifndef IGL_STATIC_LIBRARY +# include "path_to_executable.cpp" +#endif +#endif diff --git a/src/igl/pathinfo.cpp b/src/igl/pathinfo.cpp new file mode 100644 index 000000000..110535cd8 --- /dev/null +++ b/src/igl/pathinfo.cpp @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "pathinfo.h" + +#include "dirname.h" +#include "basename.h" +// Verbose should be removed once everything working correctly +#include "verbose.h" +#include + +IGL_INLINE void igl::pathinfo( + const std::string & path, + std::string & dirname, + std::string & basename, + std::string & extension, + std::string & filename) +{ + dirname = igl::dirname(path); + basename = igl::basename(path); + std::string::reverse_iterator last_dot = + std::find( + basename.rbegin(), + basename.rend(), '.'); + // Was a dot found? + if(last_dot == basename.rend()) + { + // filename is same as basename + filename = basename; + // no extension + extension = ""; + }else + { + // extension is substring of basename + extension = std::string(last_dot.base(),basename.end()); + // filename is substring of basename + filename = std::string(basename.begin(),last_dot.base()-1); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/pathinfo.h b/src/igl/pathinfo.h new file mode 100644 index 000000000..b59e48157 --- /dev/null +++ b/src/igl/pathinfo.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PATHINFO_H +#define IGL_PATHINFO_H +#include "igl_inline.h" + +#include + +namespace igl +{ + //// Decided not to use these + //const int PATHINFO_DIRNAME 01 + //const int PATHINFO_BASENAME 02 + //const int PATHINFO_EXTENSION 04 + //const int PATHINFO_FILENAME 08 + + // Function like PHP's pathinfo + // returns information about path + // Input: + // path string containing input path + // Outputs: + // dirname string containing dirname (see dirname.h) + // basename string containing basename (see basename.h) + // extension string containing extension (characters after last '.') + // filename string containing filename (characters of basename before last + // '.') + // + // + // Examples: + // + // input | dirname basename ext filename + // "/" | "/" "" "" "" + // "//" | "/" "" "" "" + // "/foo" | "/" "foo" "" "foo" + // "/foo/" | "/" "foo" "" "foo" + // "/foo//" | "/" "foo" "" "foo" + // "/foo/./" | "/foo" "." "" "" + // "/foo/bar" | "/foo" "bar" "" "bar" + // "/foo/bar." | "/foo" "bar." "" "bar" + // "/foo/bar.txt" | "/foo" "bar.txt" "txt" "bar" + // "/foo/bar.txt.zip" | "/foo" "bar.txt.zip" "zip" "bar.txt" + // "/foo/bar.dir/" | "/foo" "bar.dir" "dir" "bar" + // "/foo/bar.dir/file" | "/foo/bar.dir" "file" "" "file" + // "/foo/bar.dir/file.txt" | "/foo/bar.dir" "file.txt" "txt" "file" + // See also: basename, dirname + IGL_INLINE void pathinfo( + const std::string & path, + std::string & dirname, + std::string & basename, + std::string & extension, + std::string & filename); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "pathinfo.cpp" +#endif + +#endif diff --git a/src/igl/per_corner_normals.cpp b/src/igl/per_corner_normals.cpp new file mode 100644 index 000000000..5d2aa6aff --- /dev/null +++ b/src/igl/per_corner_normals.cpp @@ -0,0 +1,111 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "per_corner_normals.h" + +#include "vertex_triangle_adjacency.h" +#include "per_face_normals.h" +#include "PI.h" + +template +IGL_INLINE void igl::per_corner_normals( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double corner_threshold, + Eigen::PlainObjectBase & CN) +{ + using namespace Eigen; + using namespace std; + Eigen::Matrix FN; + per_face_normals(V,F,FN); + vector > VF,VFi; + vertex_triangle_adjacency(V,F,VF,VFi); + return per_corner_normals(V,F,FN,VF,corner_threshold,CN); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedCN> +IGL_INLINE void igl::per_corner_normals( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FN, + const double corner_threshold, + Eigen::PlainObjectBase & CN) +{ + using namespace Eigen; + using namespace std; + vector > VF,VFi; + vertex_triangle_adjacency(V,F,VF,VFi); + return per_corner_normals(V,F,FN,VF,corner_threshold,CN); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename IndexType, + typename DerivedCN> +IGL_INLINE void igl::per_corner_normals( + const Eigen::PlainObjectBase& /*V*/, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FN, + const std::vector >& VF, + const double corner_threshold, + Eigen::PlainObjectBase & CN) +{ + using namespace Eigen; + using namespace std; + + // number of faces + const int m = F.rows(); + // valence of faces + const int n = F.cols(); + + // initialize output to ***zero*** + CN.setZero(m*n,3); + + // loop over faces + for(size_t i = 0;int(i) fn = FN.row(i); + // loop over corners + for(size_t j = 0;int(j) &incident_faces = VF[F(i,j)]; + // loop over faces sharing vertex of this corner + for(int k = 0;k<(int)incident_faces.size();k++) + { + Eigen::Matrix ifn = FN.row(incident_faces[k]); + // dot product between face's normal and other face's normal + double dp = fn.dot(ifn); + // if difference in normal is slight then add to average + if(dp > cos(corner_threshold*PI/180)) + { + // add to running sum + CN.row(i*n+j) += ifn; + // else ignore + }else + { + } + } + // normalize to take average + CN.row(i*n+j).normalize(); + } + } +} +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::per_corner_normals, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double, Eigen::PlainObjectBase >&); +template void igl::per_corner_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double, Eigen::PlainObjectBase >&); +template void igl::per_corner_normals, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double, Eigen::PlainObjectBase >&); +template void igl::per_corner_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/per_corner_normals.h b/src/igl/per_corner_normals.h new file mode 100644 index 000000000..f93f7eb46 --- /dev/null +++ b/src/igl/per_corner_normals.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_CORNER_NORMALS_H +#define IGL_PER_CORNER_NORMALS_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Compute vertex normals via vertex position list, face list + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // corner_threshold threshold in degrees on sharp angles + // Output: + // CN #F*3 by 3 eigen Matrix of mesh vertex 3D normals, where the normal + // for corner F(i,j) is at CN(i*3+j,:) + template + IGL_INLINE void per_corner_normals( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const double corner_threshold, + Eigen::PlainObjectBase & CN); + // Other Inputs: + // FN #F by 3 eigen Matrix of face normals + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedCN> + IGL_INLINE void per_corner_normals( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FN, + const double corner_threshold, + Eigen::PlainObjectBase & CN); + // Other Inputs: + // VF map from vertices to list of incident faces + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename IndexType, + typename DerivedCN> + IGL_INLINE void per_corner_normals( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FN, + const std::vector >& VF, + const double corner_threshold, + Eigen::PlainObjectBase & CN); +} + +#ifndef IGL_STATIC_LIBRARY +# include "per_corner_normals.cpp" +#endif + +#endif diff --git a/src/igl/per_edge_normals.cpp b/src/igl/per_edge_normals.cpp new file mode 100644 index 000000000..8aa71791d --- /dev/null +++ b/src/igl/per_edge_normals.cpp @@ -0,0 +1,130 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "oriented_facets.h" +#include "doublearea.h" +#include "per_edge_normals.h" +#include "get_seconds.h" +#include "per_face_normals.h" +#include "unique_simplices.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedN, + typename DerivedE, + typename DerivedEMAP> +IGL_INLINE void igl::per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const PerEdgeNormalsWeightingType weighting, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) + +{ + using namespace Eigen; + using namespace std; + assert(F.cols() == 3 && "Faces must be triangles"); + // number of faces + const int m = F.rows(); + // All occurrences of directed edges + MatrixXi allE; + oriented_facets(F,allE); + // Find unique undirected edges and mapping + VectorXi _; + unique_simplices(allE,E,_,EMAP); + // now sort(allE,2) == E(EMAP,:), that is, if EMAP(i) = j, then E.row(j) is + // the undirected edge corresponding to the directed edge allE.row(i). + + Eigen::VectorXd W; + switch(weighting) + { + case PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM: + // Do nothing + break; + default: + assert(false && "Unknown weighting type"); + case PER_EDGE_NORMALS_WEIGHTING_TYPE_DEFAULT: + case PER_EDGE_NORMALS_WEIGHTING_TYPE_AREA: + { + doublearea(V,F,W); + break; + } + } + + N.setZero(E.rows(),3); + for(int f = 0;f +IGL_INLINE void igl::per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const PerEdgeNormalsWeightingType weighting, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) +{ + Eigen::Matrix FN; + per_face_normals(V,F,FN); + return per_edge_normals(V,F,weighting,FN,N,E,EMAP); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedE, + typename DerivedEMAP> +IGL_INLINE void igl::per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP) +{ + return + per_edge_normals(V,F,PER_EDGE_NORMALS_WEIGHTING_TYPE_DEFAULT,N,E,EMAP); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerEdgeNormalsWeightingType, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::per_edge_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/per_edge_normals.h b/src/igl/per_edge_normals.h new file mode 100644 index 000000000..b40d72976 --- /dev/null +++ b/src/igl/per_edge_normals.h @@ -0,0 +1,81 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_EDGE_NORMALS_H +#define IGL_PER_EDGE_NORMALS_H +#include "igl_inline.h" +#include +namespace igl +{ + enum PerEdgeNormalsWeightingType + { + // Incident face normals have uniform influence on edge normal + PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM = 0, + // Incident face normals are averaged weighted by area + PER_EDGE_NORMALS_WEIGHTING_TYPE_AREA = 1, + // Area weights + PER_EDGE_NORMALS_WEIGHTING_TYPE_DEFAULT = 2, + NUM_PER_EDGE_NORMALS_WEIGHTING_TYPE = 3 + }; + // Compute face normals via vertex position list, face list + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // weight weighting type + // FN #F by 3 matrix of 3D face normals per face + // Output: + // N #2 by 3 matrix of mesh edge 3D normals per row + // E #E by 2 matrix of edge indices per row + // EMAP #E by 1 matrix of indices from all edges to E + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedN, + typename DerivedE, + typename DerivedEMAP> + IGL_INLINE void per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const PerEdgeNormalsWeightingType weight, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); + template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedE, + typename DerivedEMAP> + IGL_INLINE void per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const PerEdgeNormalsWeightingType weight, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); + template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedE, + typename DerivedEMAP> + IGL_INLINE void per_edge_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & EMAP); +} + +#ifndef IGL_STATIC_LIBRARY +# include "per_edge_normals.cpp" +#endif + +#endif diff --git a/src/igl/per_face_normals.cpp b/src/igl/per_face_normals.cpp new file mode 100644 index 000000000..d3de7ff95 --- /dev/null +++ b/src/igl/per_face_normals.cpp @@ -0,0 +1,128 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "per_face_normals.h" +#include + +#define SQRT_ONE_OVER_THREE 0.57735026918962573 +template +IGL_INLINE void igl::per_face_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase & Z, + Eigen::PlainObjectBase & N) +{ + N.resize(F.rows(),3); + // loop over faces + int Frows = F.rows(); +#pragma omp parallel for if (Frows>10000) + for(int i = 0; i < Frows;i++) + { + const Eigen::Matrix v1 = V.row(F(i,1)) - V.row(F(i,0)); + const Eigen::Matrix v2 = V.row(F(i,2)) - V.row(F(i,0)); + N.row(i) = v1.cross(v2);//.normalized(); + typename DerivedV::Scalar r = N.row(i).norm(); + if(r == 0) + { + N.row(i) = Z; + }else + { + N.row(i) /= r; + } + } +} + +template +IGL_INLINE void igl::per_face_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N) +{ + using namespace Eigen; + Matrix Z(0,0,0); + return per_face_normals(V,F,Z,N); +} + +template +IGL_INLINE void igl::per_face_normals_stable( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N) +{ + using namespace Eigen; + typedef Matrix RowVectorV3; + typedef typename DerivedV::Scalar Scalar; + + const size_t m = F.rows(); + + N.resize(F.rows(),3); + // Grad all points + for(size_t f = 0;f sum3 = + [&sum3](Scalar a, Scalar b, Scalar c)->Scalar + { + if(fabs(c)>fabs(a)) + { + return sum3(c,b,a); + } + // c < a + if(fabs(c)>fabs(b)) + { + return sum3(a,c,b); + } + // c < a, c < b + if(fabs(b)>fabs(a)) + { + return sum3(b,a,c); + } + return (a+b)+c; + }; + + N(f,d) = sum3(n0(d),n1(d),n2(d)); + } + // sum better not be sure, or else NaN + N.row(f) /= N.row(f).norm(); + } + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals,class Eigen::Matrix,class Eigen::Matrix >(class Eigen::MatrixBase > const &,class Eigen::MatrixBase > const &,class Eigen::PlainObjectBase > &); +template void igl::per_face_normals_stable, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals_stable, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_face_normals_stable, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/per_face_normals.h b/src/igl/per_face_normals.h new file mode 100644 index 000000000..220e5e9a6 --- /dev/null +++ b/src/igl/per_face_normals.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_FACE_NORMALS_H +#define IGL_PER_FACE_NORMALS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute face normals via vertex position list, face list + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigen Matrix of face (triangle) indices + // Z 3 vector normal given to faces with degenerate normal. + // Output: + // N #F by 3 eigen Matrix of mesh face (triangle) 3D normals + // + // Example: + // // Give degenerate faces (1/3,1/3,1/3)^0.5 + // per_face_normals(V,F,Vector3d(1,1,1).normalized(),N); + template + IGL_INLINE void per_face_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase & Z, + Eigen::PlainObjectBase & N); + // Wrapper with Z = (0,0,0). Note that this means that row norms will be zero + // (i.e. not 1) for degenerate normals. + template + IGL_INLINE void per_face_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N); + // Special version where order of face indices is guaranteed not to effect + // output. + template + IGL_INLINE void per_face_normals_stable( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N); +} + +#ifndef IGL_STATIC_LIBRARY +# include "per_face_normals.cpp" +#endif + +#endif diff --git a/src/igl/per_vertex_attribute_smoothing.cpp b/src/igl/per_vertex_attribute_smoothing.cpp new file mode 100644 index 000000000..ecf2b4e82 --- /dev/null +++ b/src/igl/per_vertex_attribute_smoothing.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "per_vertex_attribute_smoothing.h" +#include + +template +IGL_INLINE void igl::per_vertex_attribute_smoothing( + const Eigen::PlainObjectBase& Ain, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & Aout) +{ + std::vector denominator(Ain.rows(), 0); + Aout = DerivedV::Zero(Ain.rows(), Ain.cols()); + for (int i = 0; i < F.rows(); ++i) { + for (int j = 0; j < 3; ++j) { + int j1 = (j + 1) % 3; + int j2 = (j + 2) % 3; + Aout.row(F(i, j)) += Ain.row(F(i, j1)) + Ain.row(F(i, j2)); + denominator[F(i, j)] += 2; + } + } + for (int i = 0; i < Ain.rows(); ++i) + Aout.row(i) /= denominator[i]; +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::per_vertex_attribute_smoothing, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/per_vertex_attribute_smoothing.h b/src/igl/per_vertex_attribute_smoothing.h new file mode 100644 index 000000000..662810236 --- /dev/null +++ b/src/igl/per_vertex_attribute_smoothing.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_VERTEX_ATTRIBUTE_SMOOTHING_H +#define IGL_PER_VERTEX_ATTRIBUTE_SMOOTHING_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Smooth vertex attributes using uniform Laplacian + // Inputs: + // Ain #V by #A eigen Matrix of mesh vertex attributes (each vertex has #A attributes) + // F #F by 3 eigne Matrix of face (triangle) indices + // Output: + // Aout #V by #A eigen Matrix of mesh vertex attributes + template + IGL_INLINE void per_vertex_attribute_smoothing( + const Eigen::PlainObjectBase& Ain, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & Aout); +} + +#ifndef IGL_STATIC_LIBRARY +# include "per_vertex_attribute_smoothing.cpp" +#endif + +#endif diff --git a/src/igl/per_vertex_normals.cpp b/src/igl/per_vertex_normals.cpp new file mode 100644 index 000000000..c3bbda193 --- /dev/null +++ b/src/igl/per_vertex_normals.cpp @@ -0,0 +1,133 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "per_vertex_normals.h" + +#include "get_seconds.h" +#include "per_face_normals.h" +#include "doublearea.h" +#include "parallel_for.h" +#include "internal_angles.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedN> +IGL_INLINE void igl::per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const igl::PerVertexNormalsWeightingType weighting, + Eigen::PlainObjectBase & N) +{ + Eigen::Matrix PFN; + igl::per_face_normals(V,F,PFN); + return per_vertex_normals(V,F,weighting,PFN,N); +} + +template +IGL_INLINE void igl::per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N) +{ + return per_vertex_normals(V,F,PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT,N); +} + +template +IGL_INLINE void igl::per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const igl::PerVertexNormalsWeightingType weighting, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N) +{ + using namespace std; + // Resize for output + N.setZero(V.rows(),3); + + Eigen::Matrix + W(F.rows(),3); + switch(weighting) + { + case PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM: + W.setConstant(1.); + break; + default: + assert(false && "Unknown weighting type"); + case PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT: + case PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA: + { + Eigen::Matrix A; + doublearea(V,F,A); + W = A.replicate(1,3); + break; + } + case PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE: + internal_angles(V,F,W); + break; + } + + // loop over faces + for(int i = 0;i NN; + //parallel_for( + // F.rows(), + // [&NN,&N](const size_t n){ NN.resize(n,DerivedN::Zero(N.rows(),3));}, + // [&F,&W,&FN,&NN,&critical](const int i, const size_t t) + // { + // // throw normal at each corner + // for(int j = 0; j < 3;j++) + // { + // // Q: Does this need to be critical? + // // A: Yes. Different (i,j)'s could produce the same F(i,j) + // NN[t].row(F(i,j)) += W(i,j) * FN.row(i); + // } + // }, + // [&N,&NN](const size_t t){ N += NN[t]; }, + // 1000l); + + // take average via normalization + N.rowwise().normalize(); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedN> +IGL_INLINE void igl::per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N) +{ + return + per_vertex_normals(V,F,PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT,FN,N); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::PerVertexNormalsWeightingType, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::per_vertex_normals, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/per_vertex_normals.h b/src/igl/per_vertex_normals.h new file mode 100644 index 000000000..be4ba0461 --- /dev/null +++ b/src/igl/per_vertex_normals.h @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_VERTEX_NORMALS_H +#define IGL_PER_VERTEX_NORMALS_H +#include "igl_inline.h" +#include +// Note: It would be nice to support more or all of the methods here: +// "A comparison of algorithms for vertex normal computation" +namespace igl +{ + enum PerVertexNormalsWeightingType + { + // Incident face normals have uniform influence on vertex normal + PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM = 0, + // Incident face normals are averaged weighted by area + PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA = 1, + // Incident face normals are averaged weighted by incident angle of vertex + PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE = 2, + // Area weights + PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT = 3, + NUM_PER_VERTEX_NORMALS_WEIGHTING_TYPE = 4 + }; + // Compute vertex normals via vertex position list, face list + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 3 eigne Matrix of face (triangle) indices + // weighting Weighting type + // Output: + // N #V by 3 eigen Matrix of mesh vertex 3D normals + template < + typename DerivedV, + typename DerivedF, + typename DerivedN> + IGL_INLINE void per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const igl::PerVertexNormalsWeightingType weighting, + Eigen::PlainObjectBase & N); + // Without weighting + template < + typename DerivedV, + typename DerivedF, + typename DerivedN> + IGL_INLINE void per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase & N); + // Inputs: + // FN #F by 3 matrix of face (triangle) normals + template + IGL_INLINE void per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const PerVertexNormalsWeightingType weighting, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N); + // Without weighting + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedN> + IGL_INLINE void per_vertex_normals( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& FN, + Eigen::PlainObjectBase & N); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "per_vertex_normals.cpp" +#endif + +#endif diff --git a/src/igl/per_vertex_point_to_plane_quadrics.cpp b/src/igl/per_vertex_point_to_plane_quadrics.cpp new file mode 100644 index 000000000..6d1be71b0 --- /dev/null +++ b/src/igl/per_vertex_point_to_plane_quadrics.cpp @@ -0,0 +1,157 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "per_vertex_point_to_plane_quadrics.h" +#include "quadric_binary_plus_operator.h" +#include +#include +#include + + +IGL_INLINE void igl::per_vertex_point_to_plane_quadrics( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + std::vector< + std::tuple > & quadrics) +{ + using namespace std; + typedef std::tuple Quadric; + const int dim = V.cols(); + //// Quadrics per face + //std::vector face_quadrics(F.rows()); + // Initialize each vertex quadric to zeros + quadrics.resize( + V.rows(), + // gcc <=4.8 can't handle initializer lists correctly + Quadric{Eigen::MatrixXd::Zero(dim,dim),Eigen::RowVectorXd::Zero(dim),0}); + Eigen::MatrixXd I = Eigen::MatrixXd::Identity(dim,dim); + // Rather initial with zeros, initial with a small amount of energy pull + // toward original vertex position + const double w = 1e-10; + for(int v = 0;v(quadrics[v]) = w*I; + Eigen::RowVectorXd Vv = V.row(v); + std::get<1>(quadrics[v]) = w*-Vv; + std::get<2>(quadrics[v]) = w*Vv.dot(Vv); + } + // Generic nD qslim from "Simplifying Surfaces with Color and Texture + // using Quadric Error Metric" (follow up to original QSlim) + for(int f = 0;fQuadric + { + // Dimension of subspace + const int m = S.rows(); + // Weight face's quadric (v'*A*v + 2*b'*v + c) by area + // e1 and e2 should be perpendicular + Eigen::MatrixXd A = I; + Eigen::RowVectorXd b = -p; + double c = p.dot(p); + for(int i = 0;i edge opposite cth corner is boundary + // Boundary edge vector + const Eigen::RowVectorXd p = V.row(F(f,(infinite_corner+1)%3)); + Eigen::RowVectorXd ev = V.row(F(f,(infinite_corner+2)%3)) - p; + const double length = ev.norm(); + ev /= length; + // Face neighbor across boundary edge + int e = EMAP(f+F.rows()*infinite_corner); + int opp = EF(e,0) == f ? 1 : 0; + int n = EF(e,opp); + int nc = EI(e,opp); + assert( + ((F(f,(infinite_corner+1)%3) == F(n,(nc+1)%3) && + F(f,(infinite_corner+2)%3) == F(n,(nc+2)%3)) || + (F(f,(infinite_corner+1)%3) == F(n,(nc+2)%3) + && F(f,(infinite_corner+2)%3) == F(n,(nc+1)%3))) && + "Edge flaps not agreeing on shared edge"); + // Edge vector on opposite face + const Eigen::RowVectorXd eu = V.row(F(n,nc)) - p; + assert(!std::isinf(eu(0))); + // Matrix with vectors spanning plane as columns + Eigen::MatrixXd A(ev.size(),2); + A< qr(A); + const Eigen::MatrixXd Q = qr.householderQ(); + const Eigen::MatrixXd N = + Q.topRightCorner(ev.size(),ev.size()-2).transpose(); + assert(N.cols() == ev.size()); + assert(N.rows() == ev.size()-2); + Eigen::MatrixXd S(N.rows()+1,ev.size()); + S< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PER_VERTEX_POINT_TO_PLANE_QUADRICS_H +#define IGL_PER_VERTEX_POINT_TO_PLANE_QUADRICS_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Compute quadrics per vertex of a "closed" triangle mesh (V,F). Rather than + // follow the qslim paper, this implements the lesser-known _follow up_ + // "Simplifying Surfaces with Color and Texture using Quadric Error Metrics". + // This allows V to be n-dimensional (where the extra coordiantes store + // texture UVs, color RGBs, etc. + // + // Inputs: + // V #V by n list of vertex positions. Assumes that vertices with + // infinite coordinates are "points at infinity" being used to close up + // boundary edges with faces. This allows special subspace quadrice for + // boundary edges: There should never be more than one "point at + // infinity" in a single triangle. + // F #F by 3 list of triangle indices into V + // E #E by 2 list of edge indices into V. + // EMAP #F*3 list of indices into E, mapping each directed edge to unique + // unique edge in E + // EF #E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of + // F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) " + // e=(j->i) + // EI #E by 2 list of edge flap corners (see above). + // Outputs: + // quadrics #V list of quadrics, where a quadric is a tuple {A,b,c} such + // that the quadratic energy of moving this vertex to position x is + // given by x'Ax - 2b + c + // + IGL_INLINE void per_vertex_point_to_plane_quadrics( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + std::vector< + std::tuple > & quadrics); +} +#ifndef IGL_STATIC_LIBRARY +# include "per_vertex_point_to_plane_quadrics.cpp" +#endif +#endif diff --git a/src/igl/piecewise_constant_winding_number.cpp b/src/igl/piecewise_constant_winding_number.cpp new file mode 100644 index 000000000..4bf7d76f8 --- /dev/null +++ b/src/igl/piecewise_constant_winding_number.cpp @@ -0,0 +1,82 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "piecewise_constant_winding_number.h" +#include "unique_edge_map.h" +#include "PI.h" + +template < + typename DerivedF, + typename DeriveduE, + typename uE2EType> +IGL_INLINE bool igl::piecewise_constant_winding_number( + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& uE, + const std::vector >& uE2E) +{ + const size_t num_faces = F.rows(); + const size_t num_edges = uE.rows(); + const auto edge_index_to_face_index = [&](size_t ei) + { + return ei % num_faces; + }; + const auto is_consistent = [&](size_t fid, size_t s, size_t d) + { + if ((size_t)F(fid, 0) == s && (size_t)F(fid, 1) == d) return true; + if ((size_t)F(fid, 1) == s && (size_t)F(fid, 2) == d) return true; + if ((size_t)F(fid, 2) == s && (size_t)F(fid, 0) == d) return true; + + if ((size_t)F(fid, 0) == d && (size_t)F(fid, 1) == s) return false; + if ((size_t)F(fid, 1) == d && (size_t)F(fid, 2) == s) return false; + if ((size_t)F(fid, 2) == d && (size_t)F(fid, 0) == s) return false; + throw "Invalid face!!"; + }; + for (size_t i=0; i +IGL_INLINE bool igl::piecewise_constant_winding_number( + const Eigen::MatrixBase& F) +{ + Eigen::Matrix E, uE; + Eigen::Matrix EMAP; + std::vector > uE2E; + unique_edge_map(F, E, uE, EMAP, uE2E); + return piecewise_constant_winding_number(F,uE,uE2E); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::piecewise_constant_winding_number, Eigen::Matrix, unsigned long>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&); +template bool igl::piecewise_constant_winding_number, Eigen::Matrix, unsigned long>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&); +#ifdef WIN32 +template bool igl::piecewise_constant_winding_number, class Eigen::Matrix, unsigned __int64>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>> const &); +template bool igl::piecewise_constant_winding_number, class Eigen::Matrix, unsigned __int64>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>> const &); +#endif +#endif diff --git a/src/igl/piecewise_constant_winding_number.h b/src/igl/piecewise_constant_winding_number.h new file mode 100644 index 000000000..ebbeb6e98 --- /dev/null +++ b/src/igl/piecewise_constant_winding_number.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PIECEWISE_CONSTANT_WINDING_NUMBER_H +#define IGL_PIECEWISE_CONSTANT_WINDING_NUMBER_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // PIECEWISE_CONSTANT_WINDING_NUMBER Determine if a given mesh induces a + // piecewise constant winding number field: Is this mesh valid input to solid + // set operations. **Assumes** that `(V,F)` contains no self-intersections + // (including degeneracies and co-incidences). If there are co-planar and + // co-incident vertex placements, a mesh could _fail_ this combinatorial test + // but still induce a piecewise-constant winding number _geometrically_. For + // example, consider a hemisphere with boundary and then pinch the boundary + // "shut" along a line segment. The **_bullet-proof_** check is to first + // resolve all self-intersections in `(V,F) -> (SV,SF)` (i.e. what the + // `igl::copyleft::cgal::piecewise_constant_winding_number` overload does). + // + // Inputs: + // F #F by 3 list of triangle indices into some (abstract) list of + // vertices V + // uE #uE by 2 list of unique edges indices into V + // uE2E #uE list of lists of indices into directed edges (#F * 3) + // Returns true if the mesh _combinatorially_ induces a piecewise constant + // winding number field. + // + template < + typename DerivedF, + typename DeriveduE, + typename uE2EType> + IGL_INLINE bool piecewise_constant_winding_number( + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& uE, + const std::vector >& uE2E); + template + IGL_INLINE bool piecewise_constant_winding_number( + const Eigen::MatrixBase& F); +} +#ifndef IGL_STATIC_LIBRARY +# include "piecewise_constant_winding_number.cpp" +#endif +#endif diff --git a/src/igl/pinv.cpp b/src/igl/pinv.cpp new file mode 100644 index 000000000..acf6a1913 --- /dev/null +++ b/src/igl/pinv.cpp @@ -0,0 +1,35 @@ +#include "pinv.h" +#include +#include + +template +void igl::pinv( + const Eigen::MatrixBase & A, + typename DerivedA::Scalar tol, + Eigen::PlainObjectBase & X) +{ + Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV ); + typedef typename DerivedA::Scalar Scalar; + const Eigen::Matrix & U = svd.matrixU(); + const Eigen::Matrix & V = svd.matrixV(); + const Eigen::Matrix & S = svd.singularValues(); + if(tol < 0) + { + const Scalar smax = S.array().abs().maxCoeff(); + tol = + (Scalar)(std::max(A.rows(),A.cols())) * + (smax-std::nextafter(smax,std::numeric_limits::epsilon())); + } + const int rank = (S.array()>0).count(); + X = (V.leftCols(rank).array().rowwise() * + (1.0/S.head(rank).array()).transpose()).matrix()* + U.leftCols(rank).transpose(); +} + +template +void igl::pinv( + const Eigen::MatrixBase & A, + Eigen::PlainObjectBase & X) +{ + return pinv(A,-1,X); +} diff --git a/src/igl/pinv.h b/src/igl/pinv.h new file mode 100644 index 000000000..045a17da2 --- /dev/null +++ b/src/igl/pinv.h @@ -0,0 +1,29 @@ +#ifndef IGL_PINV_H +#define IGL_PINV_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the Moore-Penrose pseudoinverse + // + // Inputs: + // A m by n matrix + // tol tolerance (if negative then default is used) + // Outputs: + // X n by m matrix so that A*X*A = A and X*A*X = X and A*X = (A*X)' and + // (X*A) = (X*A)' + template + void pinv( + const Eigen::MatrixBase & A, + typename DerivedA::Scalar tol, + Eigen::PlainObjectBase & X); + // Wrapper using default tol + template + void pinv( + const Eigen::MatrixBase & A, + Eigen::PlainObjectBase & X); +} +#ifndef IGL_STATIC_LIBRARY +# include "pinv.cpp" +#endif +#endif diff --git a/src/igl/planarize_quad_mesh.cpp b/src/igl/planarize_quad_mesh.cpp new file mode 100644 index 000000000..d3c06be62 --- /dev/null +++ b/src/igl/planarize_quad_mesh.cpp @@ -0,0 +1,245 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "planarize_quad_mesh.h" +#include "quad_planarity.h" +#include +#include +#include + +namespace igl +{ + template + class PlanarizerShapeUp + { + protected: + // number of faces, number of vertices + long numV, numF; + // references to the input faces and vertices + const Eigen::PlainObjectBase &Vin; + const Eigen::PlainObjectBase &Fin; + + // vector consisting of the vertex positions stacked: [x;y;z;x;y;z...] + // vector consisting of a weight per face (currently all set to 1) + // vector consisting of the projected face vertices (might be different for the same vertex belonging to different faces) + Eigen::Matrix Vv, weightsSqrt, P; + + // Matrices as in the paper + // Q: lhs matrix + // Ni: matrix that subtracts the mean of a face from the 4 vertices of a face + Eigen::SparseMatrix Q, Ni; + Eigen::SimplicialLDLT > solver; + + int maxIter; + double threshold; + const int ni = 4; + + // Matrix assemblers + inline void assembleQ(); + inline void assembleP(); + inline void assembleNi(); + + // Selects out of Vv the 4 vertices belonging to face fi + inline void assembleSelector(int fi, + Eigen::SparseMatrix &S); + + + public: + // Init - assemble stacked vector and lhs matrix, factorize + inline PlanarizerShapeUp(const Eigen::PlainObjectBase &V_, + const Eigen::PlainObjectBase &F_, + const int maxIter_, + const double &threshold_); + // Planarization - output to Vout + inline void planarize(Eigen::PlainObjectBase &Vout); + }; +} + +//Implementation + +template +inline igl::PlanarizerShapeUp::PlanarizerShapeUp(const Eigen::PlainObjectBase &V_, + const Eigen::PlainObjectBase &F_, + const int maxIter_, + const double &threshold_): +numV(V_.rows()), +numF(F_.rows()), +Vin(V_), +Fin(F_), +weightsSqrt(Eigen::Matrix::Ones(numF,1)), +maxIter(maxIter_), +threshold(threshold_) +{ + // assemble stacked vertex position vector + Vv.setZero(3*numV,1); + for (int i =0;i +inline void igl::PlanarizerShapeUp::assembleQ() +{ + std::vector > tripletList; + + // assemble the Ni matrix + assembleNi(); + + for (int fi = 0; fi< numF; fi++) + { + Eigen::SparseMatrix Sfi; + assembleSelector(fi, Sfi); + + // the final matrix per face + Eigen::SparseMatrix Qi = weightsSqrt(fi)*Ni*Sfi; + // put it in the correct block of Q + // todo: this can be made faster by omitting the selector matrix + for (int k=0; k::InnerIterator it(Qi,k); it; ++it) + { + typename DerivedV::Scalar val = it.value(); + int row = it.row(); + int col = it.col(); + tripletList.push_back(Eigen::Triplet(row+3*ni*fi,col,val)); + } + } + + Q.resize(3*ni*numF,3*numV); + Q.setFromTriplets(tripletList.begin(), tripletList.end()); + // the actual lhs matrix is Q'*Q + // prefactor that matrix + solver.compute(Q.transpose()*Q); + if(solver.info()!=Eigen::Success) + { + std::cerr << "Cholesky failed - PlanarizerShapeUp.cpp" << std::endl; + assert(0); + } +} + +template +inline void igl::PlanarizerShapeUp::assembleNi() +{ + std::vector> tripletList; + for (int ii = 0; ii< ni; ii++) + { + for (int jj = 0; jj< ni; jj++) + { + tripletList.push_back(Eigen::Triplet(3*ii+0,3*jj+0,-1./ni)); + tripletList.push_back(Eigen::Triplet(3*ii+1,3*jj+1,-1./ni)); + tripletList.push_back(Eigen::Triplet(3*ii+2,3*jj+2,-1./ni)); + } + tripletList.push_back(Eigen::Triplet(3*ii+0,3*ii+0,1.)); + tripletList.push_back(Eigen::Triplet(3*ii+1,3*ii+1,1.)); + tripletList.push_back(Eigen::Triplet(3*ii+2,3*ii+2,1.)); + } + Ni.resize(3*ni,3*ni); + Ni.setFromTriplets(tripletList.begin(), tripletList.end()); +} + +//assumes V stacked [x;y;z;x;y;z...]; +template +inline void igl::PlanarizerShapeUp::assembleSelector(int fi, + Eigen::SparseMatrix &S) +{ + + std::vector> tripletList; + for (int fvi = 0; fvi< ni; fvi++) + { + int vi = Fin(fi,fvi); + tripletList.push_back(Eigen::Triplet(3*fvi+0,3*vi+0,1.)); + tripletList.push_back(Eigen::Triplet(3*fvi+1,3*vi+1,1.)); + tripletList.push_back(Eigen::Triplet(3*fvi+2,3*vi+2,1.)); + } + + S.resize(3*ni,3*numV); + S.setFromTriplets(tripletList.begin(), tripletList.end()); + +} + +//project all faces to their closest planar face +template +inline void igl::PlanarizerShapeUp::assembleP() +{ + P.setZero(3*ni*numF); + for (int fi = 0; fi< numF; fi++) + { + // todo: this can be made faster by omitting the selector matrix + Eigen::SparseMatrix Sfi; + assembleSelector(fi, Sfi); + Eigen::SparseMatrix NSi = Ni*Sfi; + + Eigen::Matrix Vi = NSi*Vv; + Eigen::Matrix CC(3,ni); + for (int i = 0; i C = CC*CC.transpose(); + + // Alec: Doesn't compile + Eigen::EigenSolver> es(C); + // the real() is for compilation purposes + Eigen::Matrix lambda = es.eigenvalues().real(); + Eigen::Matrix U = es.eigenvectors().real(); + int min_i; + lambda.cwiseAbs().minCoeff(&min_i); + U.col(min_i).setZero(); + Eigen::Matrix PP = U*U.transpose()*CC; + for (int i = 0; i +inline void igl::PlanarizerShapeUp::planarize(Eigen::PlainObjectBase &Vout) +{ + Eigen::Matrix planarity; + Vout = Vin; + + for (int iter =0; iter oldMean, newMean; + oldMean = Vin.colwise().mean(); + newMean = Vout.colwise().mean(); + Vout.rowwise() += (oldMean - newMean); + +}; + + + +template +IGL_INLINE void igl::planarize_quad_mesh(const Eigen::PlainObjectBase &Vin, + const Eigen::PlainObjectBase &Fin, + const int maxIter, + const double &threshold, + Eigen::PlainObjectBase &Vout) +{ + PlanarizerShapeUp planarizer(Vin, Fin, maxIter, threshold); + planarizer.planarize(Vout); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::planarize_quad_mesh, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, double const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/planarize_quad_mesh.h b/src/igl/planarize_quad_mesh.h new file mode 100644 index 000000000..0de7bf389 --- /dev/null +++ b/src/igl/planarize_quad_mesh.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PLANARIZE_QUAD_MESH_H +#define IGL_PLANARIZE_QUAD_MESH_H +#include "igl_inline.h" +#include +namespace igl +{ + // Planarizes a given quad mesh using the algorithm described in the paper + // "Shape-Up: Shaping Discrete Geometry with Projections" by S. Bouaziz, + // M. Deuss, Y. Schwartzburg, T. Weise, M. Pauly, Computer Graphics Forum, + // Volume 31, Issue 5, August 2012, p. 1657-1667 + // (http://dl.acm.org/citation.cfm?id=2346802). + // The algorithm iterates between projecting each quad to its closest planar + // counterpart and stitching those quads together via a least squares + // optimization. It stops whenever all quads' non-planarity is less than a + // given threshold (suggested value: 0.01), or a maximum number of iterations + // is reached. + + + // Inputs: + // Vin #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 4 eigen Matrix of face (quad) indices + // maxIter maximum numbers of iterations + // threshold minimum allowed threshold for non-planarity + // Output: + // Vout #V by 3 eigen Matrix of planar mesh vertex 3D positions + // + + template + IGL_INLINE void planarize_quad_mesh(const Eigen::PlainObjectBase &Vin, + const Eigen::PlainObjectBase &F, + const int maxIter, + const double &threshold, + Eigen::PlainObjectBase &Vout); +} +#ifndef IGL_STATIC_LIBRARY +# include "planarize_quad_mesh.cpp" +#endif + +#endif diff --git a/src/igl/ply.h b/src/igl/ply.h new file mode 100644 index 000000000..60f2b29de --- /dev/null +++ b/src/igl/ply.h @@ -0,0 +1,3168 @@ +#ifndef IGL_PLY_H +#define IGL_PLY_H +/* + +Header for PLY polygon files. + +- Greg Turk, March 1994 + +A PLY file contains a single polygonal _object_. + +An object is composed of lists of _elements_. Typical elements are +vertices, faces, edges and materials. + +Each type of element for a given object has one or more _properties_ +associated with the element type. For instance, a vertex element may +have as properties three floating-point values x,y,z and three unsigned +chars for red, green and blue. + +--------------------------------------------------------------- + +Copyright (c) 1994 The Board of Trustees of The Leland Stanford +Junior University. All rights reserved. + +Permission to use, copy, modify and distribute this software and its +documentation for any purpose is hereby granted without fee, provided +that the above copyright notice and this permission notice appear in +all copies of this software and that you do not sell the software. + +THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +/* +-------------------------------------------------------------------------------- +Joao Fradinho Oliveira, July 2005 +Copyright (c) 2005 University College London +copyright conditions as above + +update for ply reading of multi OS ply files, in any OS (Unix, Macintosh, PC) +-------------------------------------------------------------------------------- + +ply_open_for_reading + +* was changed to always open files in binary mode, files written in ascii can also be +read with this binary mode. + +* allows opening of filenames that are alias files in macintosh + +* code tested on pc and mac + + +get_words + +* was changed to handle line breaks in UNIX, MACINTOSH, PC, it resets the file pointer +accordingly for the next read. + + +NOTES: +The ply file, has always an ascii part for the header, and a binary or ascii +part for the data. +The header part in ascii, dictates that linebreaks are used, this make models +operating system dependent, as a line break in unix is indicated with the escape character \n, +on a macintosh, with \r, and on a pc with \r\n <--2 unsigned chars, 2 bytes, instead of 1 byte. + +get_words allows reading of any OS, text editors such as BBEdit do not save the linebreaks +properly to target OSs with binary files. + +*/ + +#ifndef __PLY_H__ +#define __PLY_H__ + + + +#include +#include +#include +#include + +namespace igl { + namespace ply { + +#define PLY_ASCII 1 /* ascii PLY file */ +#define PLY_BINARY_BE 2 /* binary PLY file, big endian */ +#define PLY_BINARY_LE 3 /* binary PLY file, little endian */ +#define PLY_BINARY_NATIVE 4 /* binary PLY file, same endianness as + current architecture */ + +#define PLY_OKAY 0 /* ply routine worked okay */ +#define PLY_ERROR -1 /* error in ply routine */ + +/* scalar data types supported by PLY format */ + +#define PLY_START_TYPE 0 +#define PLY_CHAR 1 +#define PLY_SHORT 2 +#define PLY_INT 3 +#define PLY_UCHAR 4 +#define PLY_USHORT 5 +#define PLY_UINT 6 +#define PLY_FLOAT 7 +#define PLY_DOUBLE 8 +#define PLY_END_TYPE 9 + +#define PLY_SCALAR 0 +#define PLY_LIST 1 + + + + +typedef struct PlyProperty { /* description of a property */ + + const char *name; /* property name */ + int external_type; /* file's data type */ + int internal_type; /* program's data type */ + int offset; /* offset bytes of prop in a struct */ + + int is_list; /* 1 = list, 0 = scalar */ + int count_external; /* file's count type */ + int count_internal; /* program's count type */ + int count_offset; /* offset byte for list count */ + +} PlyProperty; + +typedef struct PlyElement { /* description of an element */ + const char *name; /* element name */ + int num; /* number of elements in this object */ + int size; /* size of element (bytes) or -1 if variable */ + int nprops; /* number of properties for this element */ + PlyProperty **props; /* list of properties in the file */ + char *store_prop; /* flags: property wanted by user? */ + int other_offset; /* offset to un-asked-for props, or -1 if none*/ + int other_size; /* size of other_props structure */ +} PlyElement; + +typedef struct PlyOtherProp { /* describes other properties in an element */ + const char *name; /* element name */ + int size; /* size of other_props */ + int nprops; /* number of properties in other_props */ + PlyProperty **props; /* list of properties in other_props */ +} PlyOtherProp; + +typedef struct OtherData { /* for storing other_props for an other element */ + void *other_props; +} OtherData; + +typedef struct OtherElem { /* data for one "other" element */ + char *elem_name; /* names of other elements */ + int elem_count; /* count of instances of each element */ + OtherData **other_data; /* actual property data for the elements */ + PlyOtherProp *other_props; /* description of the property data */ +} OtherElem; + +typedef struct PlyOtherElems { /* "other" elements, not interpreted by user */ + int num_elems; /* number of other elements */ + OtherElem *other_list; /* list of data for other elements */ +} PlyOtherElems; + +typedef struct PlyFile { /* description of PLY file */ + FILE *fp; /* file pointer */ + int file_type; /* ascii or binary */ + float version; /* version number of file */ + int nelems; /* number of elements of object */ + PlyElement **elems; /* list of elements */ + int num_comments; /* number of comments */ + char **comments; /* list of comments */ + int num_obj_info; /* number of items of object information */ + char **obj_info; /* list of object info items */ + PlyElement *which_elem; /* which element we're currently writing */ + PlyOtherElems *other_elems; /* "other" elements from a PLY file */ +} PlyFile; + +/* memory allocation */ +extern char *my_alloc(); +#define myalloc(mem_size) my_alloc((mem_size), __LINE__, __FILE__) + +#ifndef ALLOCN +#define REALLOCN(PTR,TYPE,OLD_N,NEW_N) \ + { \ + if ((OLD_N) == 0) \ + { ALLOCN((PTR),TYPE,(NEW_N));} \ + else \ + { \ + (PTR) = (TYPE *)realloc((PTR),(NEW_N)*sizeof(TYPE)); \ + if (((PTR) == NULL) && ((NEW_N) != 0)) \ + { \ + fprintf(stderr, "Memory reallocation failed on line %d in %s\n", \ + __LINE__, __FILE__); \ + fprintf(stderr, " tried to reallocate %d->%d\n", \ + (OLD_N), (NEW_N)); \ + exit(-1); \ + } \ + if ((NEW_N)>(OLD_N)) \ + memset((char *)(PTR)+(OLD_N)*sizeof(TYPE), 0, \ + ((NEW_N)-(OLD_N))*sizeof(TYPE)); \ + } \ + } + +#define ALLOCN(PTR,TYPE,N) \ + { (PTR) = (TYPE *) calloc(((unsigned)(N)),sizeof(TYPE));\ + if ((PTR) == NULL) { \ + fprintf(stderr, "Memory allocation failed on line %d in %s\n", \ + __LINE__, __FILE__); \ + exit(-1); \ + } \ + } + + +#define FREE(PTR) { free((PTR)); (PTR) = NULL; } +#endif + + +/*** delcaration of routines ***/ + +inline int get_native_binary_type2(); + +inline PlyFile *ply_write(FILE *, int,const char **, int); +inline PlyFile *ply_open_for_writing(char *, int,const char **, int, float *); +inline void ply_describe_element(PlyFile *, const char *, int, int, PlyProperty *); +inline void ply_describe_property(PlyFile *, const char *, PlyProperty *); +inline void ply_element_count(PlyFile *, const char *, int); +inline void ply_header_complete(PlyFile *); +inline void ply_put_element_setup(PlyFile *, const char *); +inline void ply_put_element(PlyFile *, void *, int*); +inline void ply_put_comment(PlyFile *, char *); +inline void ply_put_obj_info(PlyFile *, char *); +inline PlyFile *ply_read(FILE *, int *, char ***); +inline PlyFile *ply_open_for_reading( const char *, int *, char ***, int *, float *); +inline PlyProperty **ply_get_element_description(PlyFile *, const char *, int*, int*); +inline void ply_get_element_setup( PlyFile *, const char *, int, PlyProperty *); +inline void ply_get_property(PlyFile *, const char *, PlyProperty *); +inline PlyOtherProp *ply_get_other_properties(PlyFile *, const char *, int); +inline void ply_get_element(PlyFile *, void *, int *); +inline char **ply_get_comments(PlyFile *, int *); +inline char **ply_get_obj_info(PlyFile *, int *); +inline void ply_close(PlyFile *); +inline void ply_get_info(PlyFile *, float *, int *); +inline PlyOtherElems *ply_get_other_element (PlyFile *, const char *, int); +inline void ply_describe_other_elements ( PlyFile *, PlyOtherElems *); +inline void ply_put_other_elements (PlyFile *); +inline void ply_free_other_elements (PlyOtherElems *); +inline void ply_describe_other_properties(PlyFile *, PlyOtherProp *, int); + +inline int equal_strings(const char *, const char *); + + +} +} +#endif /* !__PLY_H__ */ +/* + +The interface routines for reading and writing PLY polygon files. + +Greg Turk, February 1994 + +--------------------------------------------------------------- + +A PLY file contains a single polygonal _object_. + +An object is composed of lists of _elements_. Typical elements are +vertices, faces, edges and materials. + +Each type of element for a given object has one or more _properties_ +associated with the element type. For instance, a vertex element may +have as properties the floating-point values x,y,z and the three unsigned +chars representing red, green and blue. + +--------------------------------------------------------------- + +Copyright (c) 1994 The Board of Trustees of The Leland Stanford +Junior University. All rights reserved. + +Permission to use, copy, modify and distribute this software and its +documentation for any purpose is hereby granted without fee, provided +that the above copyright notice and this permission notice appear in +all copies of this software and that you do not sell the software. + +THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +/* +-------------------------------------------------------------------------------- +Joao Fradinho Oliveira, July 2005 +University College London + +update for ply reading of multi OS ply files, in any OS (Unix, Macintosh, PC) +-------------------------------------------------------------------------------- + +ply_open_for_reading + +* was changed to always open files in binary mode, files written in ascii can also be +read with this binary mode. + +* allows opening of filenames that are alias files in macintosh + +* code tested on pc and mac + + +get_words + +* was changed to handle line breaks in UNIX, MACINTOSH, PC, it resets the file pointer +accordingly for the next read. + + +NOTES: +The ply file, has always an ascii part for the header, and a binary or ascii +part for the data. +The header part in ascii, dictates that linebreaks are used, this make models +operating system dependent, as a line break in unix is indicated with the escape character \n, +on a macintosh, with \r, and on a pc with \r\n <--2 unsigned chars, 2 bytes, instead of 1 byte. + +get_words allows reading of any OS, text editors such as BBEdit do not save the linebreaks +properly to target OSs with binary files. + +*/ + +#include +#include +#include +#include +//#include "ply.h" + + +namespace igl { + namespace ply { + + +// Use unnamed namespace to avoid duplicate symbols +/* +namespace +{ +const char *type_names[] = { +"invalid", +"char", "short", "int", +"uchar", "ushort", "uint", +"float", "double", +}; + +// names of scalar types +const char *alt_type_names[] = { +"invalid", +"int8", "int16", "int32", "uint8", "uint16", "uint32", "float32", "float64", +}; + +int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 +}; +} + +typedef union +{ + int int_value; + char byte_values[sizeof(int)]; +} endian_test_type; + + +namespace +{ +static int native_binary_type = -1; +static int types_checked = 0; +} +*/ + +#define NO_OTHER_PROPS -1 + +#define DONT_STORE_PROP 0 +#define STORE_PROP 1 + +#define OTHER_PROP 0 +#define NAMED_PROP 1 + +/* returns 1 if strings are equal, 0 if not */ +inline int equal_strings(const char *, const char *); + +/* find an element in a plyfile's list */ +inline PlyElement *find_element(PlyFile *, const char *); + +/* find a property in an element's list */ +inline PlyProperty *find_property(PlyElement *, const char *, int *); + +/* write to a file the word describing a PLY file data type */ +inline void write_scalar_type (FILE *, int); + +/* read a line from a file and break it up into separate words */ +inline char **get_words(FILE *, int *, char **); +inline char **old_get_words(FILE *, int *); + +/* write an item to a file */ +inline void write_binary_item(FILE *, int, int, unsigned int, double, int, int*); +inline void write_ascii_item(FILE *, int, unsigned int, double, int); +inline double old_write_ascii_item(FILE *, char *, int); + +/* add information to a PLY file descriptor */ +inline void add_element(PlyFile *, char **); +inline void add_property(PlyFile *, char **); +inline void add_comment(PlyFile *, char *); +inline void add_obj_info(PlyFile *, char *); + +/* copy a property */ +inline void copy_property(PlyProperty *, PlyProperty *); + +/* store a value into where a pointer and a type specify */ +inline void store_item(char *, int, int, unsigned int, double); + +/* return the value of a stored item */ +inline void get_stored_item( void *, int, int *, unsigned int *, double *); + +/* return the value stored in an item, given ptr to it and its type */ +inline double get_item_value(char *, int); + +/* get binary or ascii item and store it according to ptr and type */ +inline void get_ascii_item(char *, int, int *, unsigned int *, double *); +inline void get_binary_item(FILE *, int, int, int *, unsigned int *, double *, int*); + +/* get a bunch of elements from a file */ +inline void ascii_get_element(PlyFile *, char *); +inline void binary_get_element(PlyFile *, char *, int*); + +/* memory allocation */ +inline char *my_alloc(int, int, const char *); + +/* byte ordering */ +inline void get_native_binary_type(int*); +inline void swap_bytes(char *, int); + +inline int check_types(); + + +/*************/ +/* Writing */ +/*************/ + + +/****************************************************************************** +Given a file pointer, get ready to write PLY data to the file. + +Entry: + fp - the given file pointer + nelems - number of elements in object + elem_names - list of element names + file_type - file type, either ascii or binary + +Exit: + returns a pointer to a PlyFile, used to refer to this file, or NULL if error +******************************************************************************/ + +inline PlyFile *ply_write( + FILE *fp, + int nelems, + const char **elem_names, + int file_type +) +{ + int i; + PlyFile *plyfile; + PlyElement *elem; + + /* check for NULL file pointer */ + if (fp == NULL) + return (NULL); + + int native_binary_type = -1; + int types_checked = 0; + if (native_binary_type == -1) + native_binary_type = get_native_binary_type2(); + if (!types_checked) + types_checked = check_types(); + + /* create a record for this object */ + + plyfile = (PlyFile *) myalloc (sizeof (PlyFile)); + if (file_type == PLY_BINARY_NATIVE) + plyfile->file_type = native_binary_type; + else + plyfile->file_type = file_type; + plyfile->num_comments = 0; + plyfile->num_obj_info = 0; + plyfile->nelems = nelems; + plyfile->version = 1.0; + plyfile->fp = fp; + plyfile->other_elems = NULL; + + /* tuck aside the names of the elements */ + + plyfile->elems = (PlyElement **) myalloc (sizeof (PlyElement *) * nelems); + for (i = 0; i < nelems; i++) { + elem = (PlyElement *) myalloc (sizeof (PlyElement)); + plyfile->elems[i] = elem; + elem->name = strdup (elem_names[i]); + elem->num = 0; + elem->nprops = 0; + } + + /* return pointer to the file descriptor */ + return (plyfile); +} + + +/****************************************************************************** +Open a polygon file for writing. + +Entry: + filename - name of file to read from + nelems - number of elements in object + elem_names - list of element names + file_type - file type, either ascii or binary + +Exit: + version - version number of PLY file + returns a file identifier, used to refer to this file, or NULL if error +******************************************************************************/ + +inline PlyFile *ply_open_for_writing( + const char *filename, + int nelems, + const char **elem_names, + int file_type, + float *version +) +{ + PlyFile *plyfile; + char *name; + FILE *fp; + + /* tack on the extension .ply, if necessary */ + + name = (char *) myalloc (sizeof (char) * (strlen (filename) + 5)); + strcpy (name, filename); + if (strlen (name) < 4 || + strcmp (name + strlen (name) - 4, ".ply") != 0) + strcat (name, ".ply"); + + /* open the file for writing */ + + fp = fopen (name, "w"); + if (fp == NULL) { + return (NULL); + } + + /* create the actual PlyFile structure */ + + plyfile = ply_write (fp, nelems, elem_names, file_type); + if (plyfile == NULL) + return (NULL); + + /* say what PLY file version number we're writing */ + *version = plyfile->version; + + /* return pointer to the file descriptor */ + return (plyfile); +} + + +/****************************************************************************** +Describe an element, including its properties and how many will be written +to the file. + +Entry: + plyfile - file identifier + elem_name - name of element that information is being specified about + nelems - number of elements of this type to be written + nprops - number of properties contained in the element + prop_list - list of properties +******************************************************************************/ + +inline void ply_describe_element( + PlyFile *plyfile, + const char *elem_name, + int nelems, + int nprops, + PlyProperty *prop_list +) +{ + int i; + PlyElement *elem; + PlyProperty *prop; + + /* look for appropriate element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf(stderr,"ply_describe_element: can't find element '%s'\n",elem_name); + exit (-1); + } + + elem->num = nelems; + + /* copy the list of properties */ + + elem->nprops = nprops; + elem->props = (PlyProperty **) myalloc (sizeof (PlyProperty *) * nprops); + elem->store_prop = (char *) myalloc (sizeof (char) * nprops); + + for (i = 0; i < nprops; i++) { + prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + elem->props[i] = prop; + elem->store_prop[i] = NAMED_PROP; + copy_property (prop, &prop_list[i]); + } +} + + +/****************************************************************************** +Describe a property of an element. + +Entry: + plyfile - file identifier + elem_name - name of element that information is being specified about + prop - the new property +******************************************************************************/ + +inline void ply_describe_property( + PlyFile *plyfile, + const char *elem_name, + PlyProperty *prop +) +{ + PlyElement *elem; + PlyProperty *elem_prop; + + /* look for appropriate element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf(stderr, "ply_describe_property: can't find element '%s'\n", + elem_name); + return; + } + + /* create room for new property */ + + if (elem->nprops == 0) { + elem->props = (PlyProperty **) myalloc (sizeof (PlyProperty *)); + elem->store_prop = (char *) myalloc (sizeof (char)); + elem->nprops = 1; + } + else { + elem->nprops++; + elem->props = (PlyProperty **) + realloc (elem->props, sizeof (PlyProperty *) * elem->nprops); + elem->store_prop = (char *) + realloc (elem->store_prop, sizeof (char) * elem->nprops); + } + + /* copy the new property */ + + elem_prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + elem->props[elem->nprops - 1] = elem_prop; + elem->store_prop[elem->nprops - 1] = NAMED_PROP; + copy_property (elem_prop, prop); +} + + +/****************************************************************************** +Describe what the "other" properties are that are to be stored, and where +they are in an element. +******************************************************************************/ + +inline void ply_describe_other_properties( + PlyFile *plyfile, + PlyOtherProp *other, + int offset +) +{ + int i; + PlyElement *elem; + PlyProperty *prop; + + /* look for appropriate element */ + elem = find_element (plyfile, other->name); + if (elem == NULL) { + fprintf(stderr, "ply_describe_other_properties: can't find element '%s'\n", + other->name); + return; + } + + /* create room for other properties */ + + if (elem->nprops == 0) { + elem->props = (PlyProperty **) + myalloc (sizeof (PlyProperty *) * other->nprops); + elem->store_prop = (char *) myalloc (sizeof (char) * other->nprops); + elem->nprops = 0; + } + else { + int newsize; + newsize = elem->nprops + other->nprops; + elem->props = (PlyProperty **) + realloc (elem->props, sizeof (PlyProperty *) * newsize); + elem->store_prop = (char *) + realloc (elem->store_prop, sizeof (char) * newsize); + } + + /* copy the other properties */ + + for (i = 0; i < other->nprops; i++) { + prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + copy_property (prop, other->props[i]); + elem->props[elem->nprops] = prop; + elem->store_prop[elem->nprops] = OTHER_PROP; + elem->nprops++; + } + + /* save other info about other properties */ + elem->other_size = other->size; + elem->other_offset = offset; +} + + +/****************************************************************************** +State how many of a given element will be written. + +Entry: + plyfile - file identifier + elem_name - name of element that information is being specified about + nelems - number of elements of this type to be written +******************************************************************************/ + +inline void ply_element_count( + PlyFile *plyfile, + const char *elem_name, + int nelems +) +{ + PlyElement *elem; + + /* look for appropriate element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf(stderr,"ply_element_count: can't find element '%s'\n",elem_name); + exit (-1); + } + + elem->num = nelems; +} + + +/****************************************************************************** +Signal that we've described everything a PLY file's header and that the +header should be written to the file. + +Entry: + plyfile - file identifier +******************************************************************************/ + +inline void ply_header_complete(PlyFile *plyfile) +{ + int i,j; + FILE *fp = plyfile->fp; + PlyElement *elem; + PlyProperty *prop; + + fprintf (fp, "ply\n"); + + switch (plyfile->file_type) { + case PLY_ASCII: + fprintf (fp, "format ascii 1.0\n"); + break; + case PLY_BINARY_BE: + fprintf (fp, "format binary_big_endian 1.0\n"); + break; + case PLY_BINARY_LE: + fprintf (fp, "format binary_little_endian 1.0\n"); + break; + default: + fprintf (stderr, "ply_header_complete: bad file type = %d\n", + plyfile->file_type); + exit (-1); + } + + /* write out the comments */ + + for (i = 0; i < plyfile->num_comments; i++) + fprintf (fp, "comment %s\n", plyfile->comments[i]); + + /* write out object information */ + + for (i = 0; i < plyfile->num_obj_info; i++) + fprintf (fp, "obj_info %s\n", plyfile->obj_info[i]); + + /* write out information about each element */ + + for (i = 0; i < plyfile->nelems; i++) { + + elem = plyfile->elems[i]; + fprintf (fp, "element %s %d\n", elem->name, elem->num); + + /* write out each property */ + for (j = 0; j < elem->nprops; j++) { + prop = elem->props[j]; + if (prop->is_list) { + fprintf (fp, "property list "); + write_scalar_type (fp, prop->count_external); + fprintf (fp, " "); + write_scalar_type (fp, prop->external_type); + fprintf (fp, " %s\n", prop->name); + } + else { + fprintf (fp, "property "); + write_scalar_type (fp, prop->external_type); + fprintf (fp, " %s\n", prop->name); + } + } + } + + fprintf (fp, "end_header\n"); +} + + +/****************************************************************************** +Specify which elements are going to be written. This should be called +before a call to the routine ply_put_element(). + +Entry: + plyfile - file identifier + elem_name - name of element we're talking about +******************************************************************************/ + +inline void ply_put_element_setup(PlyFile *plyfile, const char *elem_name) +{ + PlyElement *elem; + + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf(stderr, "ply_elements_setup: can't find element '%s'\n", elem_name); + exit (-1); + } + + plyfile->which_elem = elem; +} + + +/****************************************************************************** +Write an element to the file. This routine assumes that we're +writing the type of element specified in the last call to the routine +ply_put_element_setup(). + +Entry: + plyfile - file identifier + elem_ptr - pointer to the element +******************************************************************************/ + +inline void ply_put_element(PlyFile *plyfile, void *elem_ptr, int *native_binary_type) +{ + int j,k; + FILE *fp = plyfile->fp; + PlyElement *elem; + PlyProperty *prop; + char *elem_data,*item; + char **item_ptr; + int list_count; + int item_size; + int int_val; + unsigned int uint_val; + double double_val; + char **other_ptr; + + elem = plyfile->which_elem; + elem_data = (char *)elem_ptr; + other_ptr = (char **) (((char *) elem_ptr) + elem->other_offset); + + /* write out either to an ascii or binary file */ + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + if (plyfile->file_type == PLY_ASCII) { + + /* write an ascii file */ + + /* write out each property of the element */ + for (j = 0; j < elem->nprops; j++) { + prop = elem->props[j]; + if (elem->store_prop[j] == OTHER_PROP) + elem_data = *other_ptr; + else + elem_data = (char *)elem_ptr; + if (prop->is_list) { + item = elem_data + prop->count_offset; + get_stored_item ((void *) item, prop->count_internal, + &int_val, &uint_val, &double_val); + write_ascii_item (fp, int_val, uint_val, double_val, + prop->count_external); + list_count = uint_val; + item_ptr = (char **) (elem_data + prop->offset); + item = item_ptr[0]; + item_size = ply_type_size[prop->internal_type]; + for (k = 0; k < list_count; k++) { + get_stored_item ((void *) item, prop->internal_type, + &int_val, &uint_val, &double_val); + write_ascii_item (fp, int_val, uint_val, double_val, + prop->external_type); + item += item_size; + } + } + else { + item = elem_data + prop->offset; + get_stored_item ((void *) item, prop->internal_type, + &int_val, &uint_val, &double_val); + write_ascii_item (fp, int_val, uint_val, double_val, + prop->external_type); + } + } + + fprintf (fp, "\n"); + } + else { + + /* write a binary file */ + + /* write out each property of the element */ + for (j = 0; j < elem->nprops; j++) { + prop = elem->props[j]; + if (elem->store_prop[j] == OTHER_PROP) + elem_data = *other_ptr; + else + elem_data = (char *)elem_ptr; + if (prop->is_list) { + item = elem_data + prop->count_offset; + item_size = ply_type_size[prop->count_internal]; + get_stored_item ((void *) item, prop->count_internal, + &int_val, &uint_val, &double_val); + write_binary_item (fp, plyfile->file_type, int_val, uint_val, + double_val, prop->count_external, native_binary_type); + list_count = uint_val; + item_ptr = (char **) (elem_data + prop->offset); + item = item_ptr[0]; + item_size = ply_type_size[prop->internal_type]; + for (k = 0; k < list_count; k++) { + get_stored_item ((void *) item, prop->internal_type, + &int_val, &uint_val, &double_val); + write_binary_item (fp, plyfile->file_type, int_val, uint_val, + double_val, prop->external_type, native_binary_type); + item += item_size; + } + } + else { + item = elem_data + prop->offset; + item_size = ply_type_size[prop->internal_type]; + get_stored_item ((void *) item, prop->internal_type, + &int_val, &uint_val, &double_val); + write_binary_item (fp, plyfile->file_type, int_val, uint_val, + double_val, prop->external_type, native_binary_type); + } + } + + } +} + + +/****************************************************************************** +Specify a comment that will be written in the header. + +Entry: + plyfile - file identifier + comment - the comment to be written +******************************************************************************/ + +inline void ply_put_comment(PlyFile *plyfile, char *comment) +{ + /* (re)allocate space for new comment */ + if (plyfile->num_comments == 0) + plyfile->comments = (char **) myalloc (sizeof (char *)); + else + plyfile->comments = (char **) realloc (plyfile->comments, + sizeof (char *) * (plyfile->num_comments + 1)); + + /* add comment to list */ + plyfile->comments[plyfile->num_comments] = strdup (comment); + plyfile->num_comments++; +} + + +/****************************************************************************** +Specify a piece of object information (arbitrary text) that will be written +in the header. + +Entry: + plyfile - file identifier + obj_info - the text information to be written +******************************************************************************/ + +inline void ply_put_obj_info(PlyFile *plyfile, char *obj_info) +{ + /* (re)allocate space for new info */ + if (plyfile->num_obj_info == 0) + plyfile->obj_info = (char **) myalloc (sizeof (char *)); + else + plyfile->obj_info = (char **) realloc (plyfile->obj_info, + sizeof (char *) * (plyfile->num_obj_info + 1)); + + /* add info to list */ + plyfile->obj_info[plyfile->num_obj_info] = strdup (obj_info); + plyfile->num_obj_info++; +} + + + + + + + +/*************/ +/* Reading */ +/*************/ + + + +/****************************************************************************** +Given a file pointer, get ready to read PLY data from the file. + +Entry: + fp - the given file pointer + +Exit: + nelems - number of elements in object + elem_names - list of element names + returns a pointer to a PlyFile, used to refer to this file, or NULL if error +******************************************************************************/ + +inline PlyFile *ply_read(FILE *fp, int *nelems, char ***elem_names) +{ + int i,j; + PlyFile *plyfile; + int nwords; + char **words; + char **elist; + PlyElement *elem; + char *orig_line; + + /* check for NULL file pointer */ + if (fp == NULL) + return (NULL); + + int native_binary_type = -1; + int types_checked = 0; + + if (native_binary_type == -1) + native_binary_type = get_native_binary_type2(); + if (!types_checked) + types_checked = check_types(); + + /* create record for this object */ + + plyfile = (PlyFile *) myalloc (sizeof (PlyFile)); + plyfile->nelems = 0; + plyfile->comments = NULL; + plyfile->num_comments = 0; + plyfile->obj_info = NULL; + plyfile->num_obj_info = 0; + plyfile->fp = fp; + plyfile->other_elems = NULL; + + /* read and parse the file's header */ + + words = get_words (plyfile->fp, &nwords, &orig_line); + if (nwords == 0 || !words || !equal_strings (words[0], "ply")) + { + if (words) + free(words); + + + return (NULL); + } + + while (words) { + + /* parse words */ + + if (equal_strings (words[0], "format")) { + if (nwords != 3) { + free(words); + return (NULL); + } + if (equal_strings (words[1], "ascii")) + plyfile->file_type = PLY_ASCII; + else if (equal_strings (words[1], "binary_big_endian")) + plyfile->file_type = PLY_BINARY_BE; + else if (equal_strings (words[1], "binary_little_endian")) + plyfile->file_type = PLY_BINARY_LE; + else { + free(words); + return (NULL); + } + plyfile->version = atof (words[2]); + } + else if (equal_strings (words[0], "element")) + add_element (plyfile, words); + else if (equal_strings (words[0], "property")) + add_property (plyfile, words); + else if (equal_strings (words[0], "comment")) + add_comment (plyfile, orig_line); + else if (equal_strings (words[0], "obj_info")) + add_obj_info (plyfile, orig_line); + else if (equal_strings (words[0], "end_header")) { + free(words); + break; + } + + /* free up words space */ + free (words); + + words = get_words (plyfile->fp, &nwords, &orig_line); + } + + /* create tags for each property of each element, to be used */ + /* later to say whether or not to store each property for the user */ + + for (i = 0; i < plyfile->nelems; i++) { + elem = plyfile->elems[i]; + elem->store_prop = (char *) myalloc (sizeof (char) * elem->nprops); + for (j = 0; j < elem->nprops; j++) + elem->store_prop[j] = DONT_STORE_PROP; + elem->other_offset = NO_OTHER_PROPS; /* no "other" props by default */ + } + + /* set return values about the elements */ + + elist = (char **) myalloc (sizeof (char *) * plyfile->nelems); + for (i = 0; i < plyfile->nelems; i++) + elist[i] = strdup (plyfile->elems[i]->name); + + *elem_names = elist; + *nelems = plyfile->nelems; + + /* return a pointer to the file's information */ + + return (plyfile); +} + + +/****************************************************************************** +Open a polygon file for reading. + +Entry: + filename - name of file to read from + +Exit: + nelems - number of elements in object + elem_names - list of element names + file_type - file type, either ascii or binary + version - version number of PLY file + returns a file identifier, used to refer to this file, or NULL if error +******************************************************************************/ + +inline PlyFile *ply_open_for_reading( + char *filename, + int *nelems, + char ***elem_names, + int *file_type, + float *version +) +{ + FILE *fp; + PlyFile *plyfile; + //char *name; + + + + /* tack on the extension .ply, if necessary */ + + // removing below, to handle also macintosh alias filenames + //name = (char *) myalloc (sizeof (char) * (strlen (filename) + 5)); + //strcpy (name, filename); + //if (strlen (name) < 4 || + // strcmp (name + strlen (name) - 4, ".ply") != 0) + // strcat (name, ".ply"); + + /* open the file for reading */ + + //fp = fopen (name, "r"); + + //opening file in binary, ascii data can be read in binary with get_words + fp = fopen (filename, "rb"); + + if (fp == NULL) + return (NULL); + + /* create the PlyFile data structure */ + + plyfile = ply_read (fp, nelems, elem_names); + + /* determine the file type and version */ + + *file_type = plyfile->file_type; + *version = plyfile->version; + + /* return a pointer to the file's information */ + + return (plyfile); +} + + +/****************************************************************************** +Get information about a particular element. + +Entry: + plyfile - file identifier + elem_name - name of element to get information about + +Exit: + nelems - number of elements of this type in the file + nprops - number of properties + returns a list of properties, or NULL if the file doesn't contain that elem +******************************************************************************/ + +inline PlyProperty **ply_get_element_description( + PlyFile *plyfile, + const char *elem_name, + int *nelems, + int *nprops +) +{ + int i; + PlyElement *elem; + PlyProperty *prop; + PlyProperty **prop_list; + + /* find information about the element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) + return (NULL); + + *nelems = elem->num; + *nprops = elem->nprops; + + /* make a copy of the element's property list */ + prop_list = (PlyProperty **) myalloc (sizeof (PlyProperty *) * elem->nprops); + for (i = 0; i < elem->nprops; i++) { + prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + copy_property (prop, elem->props[i]); + prop_list[i] = prop; + } + + /* return this duplicate property list */ + return (prop_list); +} + + +/****************************************************************************** +Specify which properties of an element are to be returned. This should be +called before a call to the routine ply_get_element(). + +Entry: + plyfile - file identifier + elem_name - which element we're talking about + nprops - number of properties + prop_list - list of properties +******************************************************************************/ + +inline void ply_get_element_setup( + PlyFile *plyfile, + const char *elem_name, + int nprops, + PlyProperty *prop_list +) +{ + int i; + PlyElement *elem; + PlyProperty *prop; + int index; + + /* find information about the element */ + elem = find_element (plyfile, elem_name); + plyfile->which_elem = elem; + + /* deposit the property information into the element's description */ + for (i = 0; i < nprops; i++) { + + /* look for actual property */ + prop = find_property (elem, prop_list[i].name, &index); + if (prop == NULL) { + fprintf (stderr, "Warning: Can't find property '%s' in element '%s'\n", + prop_list[i].name, elem_name); + continue; + } + + /* store its description */ + prop->internal_type = prop_list[i].internal_type; + prop->offset = prop_list[i].offset; + prop->count_internal = prop_list[i].count_internal; + prop->count_offset = prop_list[i].count_offset; + + /* specify that the user wants this property */ + elem->store_prop[index] = STORE_PROP; + } +} + + +/****************************************************************************** +Specify a property of an element that is to be returned. This should be +called (usually multiple times) before a call to the routine ply_get_element(). +This routine should be used in preference to the less flexible old routine +called ply_get_element_setup(). + +Entry: + plyfile - file identifier + elem_name - which element we're talking about + prop - property to add to those that will be returned +******************************************************************************/ + +inline void ply_get_property( + PlyFile *plyfile, + const char *elem_name, + PlyProperty *prop +) +{ + PlyElement *elem; + PlyProperty *prop_ptr; + int index; + + /* find information about the element */ + elem = find_element (plyfile, elem_name); + plyfile->which_elem = elem; + + /* deposit the property information into the element's description */ + + prop_ptr = find_property (elem, prop->name, &index); + if (prop_ptr == NULL) { + fprintf (stderr, "Warning: Can't find property '%s' in element '%s'\n", + prop->name, elem_name); + return; + } + prop_ptr->internal_type = prop->internal_type; + prop_ptr->offset = prop->offset; + prop_ptr->count_internal = prop->count_internal; + prop_ptr->count_offset = prop->count_offset; + + /* specify that the user wants this property */ + elem->store_prop[index] = STORE_PROP; +} + + +/****************************************************************************** +Read one element from the file. This routine assumes that we're reading +the type of element specified in the last call to the routine +ply_get_element_setup(). + +Entry: + plyfile - file identifier + elem_ptr - pointer to location where the element information should be put +******************************************************************************/ + +inline void ply_get_element(PlyFile *plyfile, void *elem_ptr, int *native_binary_type) +{ + if (plyfile->file_type == PLY_ASCII) + ascii_get_element (plyfile, (char *) elem_ptr); + else + binary_get_element (plyfile, (char *) elem_ptr, native_binary_type); +} + + +/****************************************************************************** +Extract the comments from the header information of a PLY file. + +Entry: + plyfile - file identifier + +Exit: + num_comments - number of comments returned + returns a pointer to a list of comments +******************************************************************************/ + +inline char **ply_get_comments(PlyFile *plyfile, int *num_comments) +{ + *num_comments = plyfile->num_comments; + return (plyfile->comments); +} + + +/****************************************************************************** +Extract the object information (arbitrary text) from the header information +of a PLY file. + +Entry: + plyfile - file identifier + +Exit: + num_obj_info - number of lines of text information returned + returns a pointer to a list of object info lines +******************************************************************************/ + +inline char **ply_get_obj_info(PlyFile *plyfile, int *num_obj_info) +{ + *num_obj_info = plyfile->num_obj_info; + return (plyfile->obj_info); +} + + +/****************************************************************************** +Make ready for "other" properties of an element-- those properties that +the user has not explicitly asked for, but that are to be stashed away +in a special structure to be carried along with the element's other +information. + +Entry: + plyfile - file identifier + elem - element for which we want to save away other properties +******************************************************************************/ + +inline void setup_other_props(PlyElement *elem) +{ + int i; + PlyProperty *prop; + int size = 0; + int type_size; + + /* Examine each property in decreasing order of size. */ + /* We do this so that all data types will be aligned by */ + /* word, half-word, or whatever within the structure. */ + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + for (type_size = 8; type_size > 0; type_size /= 2) { + + /* add up the space taken by each property, and save this information */ + /* away in the property descriptor */ + + for (i = 0; i < elem->nprops; i++) { + + /* don't bother with properties we've been asked to store explicitly */ + if (elem->store_prop[i]) + continue; + + prop = elem->props[i]; + + /* internal types will be same as external */ + prop->internal_type = prop->external_type; + prop->count_internal = prop->count_external; + + /* check list case */ + if (prop->is_list) { + + /* pointer to list */ + if (type_size == sizeof (void *)) { + prop->offset = size; + size += sizeof (void *); /* always use size of a pointer here */ + } + + /* count of number of list elements */ + if (type_size == ply_type_size[prop->count_external]) { + prop->count_offset = size; + size += ply_type_size[prop->count_external]; + } + } + /* not list */ + else if (type_size == ply_type_size[prop->external_type]) { + prop->offset = size; + size += ply_type_size[prop->external_type]; + } + } + + } + + /* save the size for the other_props structure */ + elem->other_size = size; +} + + +/****************************************************************************** +Specify that we want the "other" properties of an element to be tucked +away within the user's structure. The user needn't be concerned for how +these properties are stored. + +Entry: + plyfile - file identifier + elem_name - name of element that we want to store other_props in + offset - offset to where other_props will be stored inside user's structure + +Exit: + returns pointer to structure containing description of other_props +******************************************************************************/ + +inline PlyOtherProp *ply_get_other_properties( + PlyFile *plyfile, + const char *elem_name, + int offset +) +{ + int i; + PlyElement *elem; + PlyOtherProp *other; + PlyProperty *prop; + int nprops; + + /* find information about the element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf (stderr, "ply_get_other_properties: Can't find element '%s'\n", + elem_name); + return (NULL); + } + + /* remember that this is the "current" element */ + plyfile->which_elem = elem; + + /* save the offset to where to store the other_props */ + elem->other_offset = offset; + + /* place the appropriate pointers, etc. in the element's property list */ + setup_other_props (elem); + + /* create structure for describing other_props */ + other = (PlyOtherProp *) myalloc (sizeof (PlyOtherProp)); + other->name = strdup (elem_name); +#if 0 + if (elem->other_offset == NO_OTHER_PROPS) { + other->size = 0; + other->props = NULL; + other->nprops = 0; + return (other); + } +#endif + other->size = elem->other_size; + other->props = (PlyProperty **) myalloc (sizeof(PlyProperty) * elem->nprops); + + /* save descriptions of each "other" property */ + nprops = 0; + for (i = 0; i < elem->nprops; i++) { + if (elem->store_prop[i]) + continue; + prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + copy_property (prop, elem->props[i]); + other->props[nprops] = prop; + nprops++; + } + other->nprops = nprops; + +#if 1 + /* set other_offset pointer appropriately if there are NO other properties */ + if (other->nprops == 0) { + elem->other_offset = NO_OTHER_PROPS; + } +#endif + + /* return structure */ + return (other); +} + + + + +/*************************/ +/* Other Element Stuff */ +/*************************/ + + + + +/****************************************************************************** +Grab all the data for an element that a user does not want to explicitly +read in. + +Entry: + plyfile - pointer to file + elem_name - name of element whose data is to be read in + elem_count - number of instances of this element stored in the file + +Exit: + returns pointer to ALL the "other" element data for this PLY file +******************************************************************************/ + +inline PlyOtherElems *ply_get_other_element ( + PlyFile *plyfile, + char *elem_name, + int elem_count +) +{ + int i; + PlyElement *elem; + PlyOtherElems *other_elems; + OtherElem *other; + + /* look for appropriate element */ + elem = find_element (plyfile, elem_name); + if (elem == NULL) { + fprintf (stderr, + "ply_get_other_element: can't find element '%s'\n", elem_name); + exit (-1); + } + + /* create room for the new "other" element, initializing the */ + /* other data structure if necessary */ + + if (plyfile->other_elems == NULL) { + plyfile->other_elems = (PlyOtherElems *) myalloc (sizeof (PlyOtherElems)); + other_elems = plyfile->other_elems; + other_elems->other_list = (OtherElem *) myalloc (sizeof (OtherElem)); + other = &(other_elems->other_list[0]); + other_elems->num_elems = 1; + } + else { + other_elems = plyfile->other_elems; + other_elems->other_list = (OtherElem *) realloc (other_elems->other_list, + sizeof (OtherElem) * (other_elems->num_elems + 1)); + other = &(other_elems->other_list[other_elems->num_elems]); + other_elems->num_elems++; + } + + /* count of element instances in file */ + other->elem_count = elem_count; + + /* save name of element */ + other->elem_name = strdup (elem_name); + + /* create a list to hold all the current elements */ + other->other_data = (OtherData **) + malloc (sizeof (OtherData *) * other->elem_count); + + /* set up for getting elements */ + other->other_props = ply_get_other_properties (plyfile, elem_name, + offsetof(OtherData,other_props)); + + /* grab all these elements */ + int native_binary_type = get_native_binary_type2(); + for (i = 0; i < other->elem_count; i++) { + /* grab and element from the file */ + other->other_data[i] = (OtherData *) malloc (sizeof (OtherData)); + ply_get_element (plyfile, (void *) other->other_data[i], &native_binary_type); + } + + /* return pointer to the other elements data */ + return (other_elems); +} + + +/****************************************************************************** +Pass along a pointer to "other" elements that we want to save in a given +PLY file. These other elements were presumably read from another PLY file. + +Entry: + plyfile - file pointer in which to store this other element info + other_elems - info about other elements that we want to store +******************************************************************************/ + +inline void ply_describe_other_elements ( + PlyFile *plyfile, + PlyOtherElems *other_elems +) +{ + int i; + OtherElem *other; + PlyElement *elem; + + /* ignore this call if there is no other element */ + if (other_elems == NULL) + return; + + /* save pointer to this information */ + plyfile->other_elems = other_elems; + + /* describe the other properties of this element */ + /* store them in the main element list as elements with + only other properties */ + + REALLOCN(plyfile->elems, PlyElement *, + plyfile->nelems, plyfile->nelems + other_elems->num_elems); + for (i = 0; i < other_elems->num_elems; i++) { + other = &(other_elems->other_list[i]); + elem = (PlyElement *) myalloc (sizeof (PlyElement)); + plyfile->elems[plyfile->nelems++] = elem; + elem->name = strdup (other->elem_name); + elem->num = other->elem_count; + elem->nprops = 0; + ply_describe_other_properties (plyfile, other->other_props, + offsetof(OtherData,other_props)); + } +} + + +/****************************************************************************** +Write out the "other" elements specified for this PLY file. + +Entry: + plyfile - pointer to PLY file to write out other elements for +******************************************************************************/ + +inline void ply_put_other_elements (PlyFile *plyfile, int *native_binary_type) +{ + int i,j; + OtherElem *other; + + /* make sure we have other elements to write */ + if (plyfile->other_elems == NULL) + return; + + /* write out the data for each "other" element */ + + for (i = 0; i < plyfile->other_elems->num_elems; i++) { + + other = &(plyfile->other_elems->other_list[i]); + ply_put_element_setup (plyfile, other->elem_name); + + /* write out each instance of the current element */ + for (j = 0; j < other->elem_count; j++) + ply_put_element (plyfile, (void *) other->other_data[j], native_binary_type); + } +} + + +/****************************************************************************** +Free up storage used by an "other" elements data structure. + +Entry: + other_elems - data structure to free up +******************************************************************************/ + +inline void ply_free_other_elements (PlyOtherElems *other_elems) +{ + // Alec: + //other_elems = other_elems; + delete(other_elems); +} + + + +/*******************/ +/* Miscellaneous */ +/*******************/ + + + +/****************************************************************************** +Close a PLY file. + +Entry: + plyfile - identifier of file to close +******************************************************************************/ + +inline void ply_close(PlyFile *plyfile) +{ + fclose (plyfile->fp); + // Alec: + plyfile->fp = NULL; + + /* free up memory associated with the PLY file */ + free (plyfile); +} + + +/****************************************************************************** +Get version number and file type of a PlyFile. + +Entry: + ply - pointer to PLY file + +Exit: + version - version of the file + file_type - PLY_ASCII, PLY_BINARY_BE, or PLY_BINARY_LE +******************************************************************************/ + +inline void ply_get_info(PlyFile *ply, float *version, int *file_type) +{ + if (ply == NULL) + return; + + *version = ply->version; + *file_type = ply->file_type; +} + + +/****************************************************************************** +Compare two strings. Returns 1 if they are the same, 0 if not. +******************************************************************************/ + +inline int equal_strings(const char *s1, const char *s2) +{ + + while (*s1 && *s2) + if (*s1++ != *s2++) + return (0); + + if (*s1 != *s2) + return (0); + else + return (1); +} + + +/****************************************************************************** +Find an element from the element list of a given PLY object. + +Entry: + plyfile - file id for PLY file + element - name of element we're looking for + +Exit: + returns the element, or NULL if not found +******************************************************************************/ + +inline PlyElement *find_element(PlyFile *plyfile, const char *element) +{ + int i; + + for (i = 0; i < plyfile->nelems; i++) + if (equal_strings (element, plyfile->elems[i]->name)) + return (plyfile->elems[i]); + + return (NULL); +} + + +/****************************************************************************** +Find a property in the list of properties of a given element. + +Entry: + elem - pointer to element in which we want to find the property + prop_name - name of property to find + +Exit: + index - index to position in list + returns a pointer to the property, or NULL if not found +******************************************************************************/ + +inline PlyProperty *find_property(PlyElement *elem, const char *prop_name, int *index) +{ + int i; + + for (i = 0; i < elem->nprops; i++) + if (equal_strings (prop_name, elem->props[i]->name)) { + *index = i; + return (elem->props[i]); + } + + *index = -1; + return (NULL); +} + + +/****************************************************************************** +Read an element from an ascii file. + +Entry: + plyfile - file identifier + elem_ptr - pointer to element +******************************************************************************/ + +inline void ascii_get_element(PlyFile *plyfile, char *elem_ptr) +{ + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + int j,k; + PlyElement *elem; + PlyProperty *prop; + char **words; + int nwords; + int which_word; + char *elem_data,*item=NULL; + char *item_ptr; + int item_size; + int int_val; + unsigned int uint_val; + double double_val; + int list_count; + int store_it; + char **store_array; + char *orig_line; + char *other_data=NULL; + int other_flag; + + /* the kind of element we're reading currently */ + elem = plyfile->which_elem; + + /* do we need to setup for other_props? */ + + if (elem->other_offset != NO_OTHER_PROPS) { + char **ptr; + other_flag = 1; + /* make room for other_props */ + other_data = (char *) myalloc (elem->other_size); + /* store pointer in user's structure to the other_props */ + ptr = (char **) (elem_ptr + elem->other_offset); + *ptr = other_data; + } + else + other_flag = 0; + + /* read in the element */ + + words = get_words (plyfile->fp, &nwords, &orig_line); + if (words == NULL) { + fprintf (stderr, "ply_get_element: unexpected end of file\n"); + exit (-1); + } + + which_word = 0; + + for (j = 0; j < elem->nprops; j++) { + + prop = elem->props[j]; + store_it = (elem->store_prop[j] | other_flag); + + /* store either in the user's structure or in other_props */ + // if (elem->store_prop[j]) + elem_data = elem_ptr; + //else + //elem_data = other_data; + + if (prop->is_list) { /* a list */ + + /* get and store the number of items in the list */ + get_ascii_item (words[which_word++], prop->count_external, + &int_val, &uint_val, &double_val); + if (store_it) { + item = elem_data + prop->count_offset; + store_item(item, prop->count_internal, int_val, uint_val, double_val); + } + + /* allocate space for an array of items and store a ptr to the array */ + list_count = int_val; + item_size = ply_type_size[prop->internal_type]; + store_array = (char **) (elem_data + prop->offset); + + if (list_count == 0) { + if (store_it) + *store_array = NULL; + } + else { + if (store_it) { + item_ptr = (char *) myalloc (sizeof (char) * item_size * list_count); + + item = item_ptr; + *store_array = item_ptr; + } + + /* read items and store them into the array */ + for (k = 0; k < list_count; k++) { + get_ascii_item (words[which_word++], prop->external_type, + &int_val, &uint_val, &double_val); + if (store_it) { + store_item (item, prop->internal_type, + int_val, uint_val, double_val); + item += item_size; + } + } + } + + } + else { /* not a list */ + get_ascii_item (words[which_word++], prop->external_type, + &int_val, &uint_val, &double_val); + if (store_it) { + item = elem_data + prop->offset; + store_item (item, prop->internal_type, int_val, uint_val, double_val); + } + } + + } + + free (words); +} + + +/****************************************************************************** +Read an element from a binary file. + +Entry: + plyfile - file identifier + elem_ptr - pointer to an element +******************************************************************************/ + +inline void binary_get_element(PlyFile *plyfile, char *elem_ptr, int *native_binary_type) +{ + int j,k; + PlyElement *elem; + PlyProperty *prop; + FILE *fp = plyfile->fp; + char *elem_data,*item=NULL; + char *item_ptr; + int item_size; + int int_val; + unsigned int uint_val; + double double_val; + int list_count; + int store_it; + char **store_array; + char *other_data=NULL; + int other_flag; + + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + + /* the kind of element we're reading currently */ + elem = plyfile->which_elem; + + /* do we need to setup for other_props? */ + + if (elem->other_offset != NO_OTHER_PROPS) { + char **ptr; + other_flag = 1; + /* make room for other_props */ + other_data = (char *) myalloc (elem->other_size); + /* store pointer in user's structure to the other_props */ + ptr = (char **) (elem_ptr + elem->other_offset); + *ptr = other_data; + } + else + other_flag = 0; + + /* read in a number of elements */ + + for (j = 0; j < elem->nprops; j++) { + + prop = elem->props[j]; + store_it = (elem->store_prop[j] | other_flag); + + /* store either in the user's structure or in other_props */ +// if (elem->store_prop[j]) + elem_data = elem_ptr; +// else +// elem_data = other_data; + + if (prop->is_list) { /* a list */ + + /* get and store the number of items in the list */ + get_binary_item (fp, plyfile->file_type, prop->count_external, + &int_val, &uint_val, &double_val, native_binary_type); + if (store_it) { + item = elem_data + prop->count_offset; + store_item(item, prop->count_internal, int_val, uint_val, double_val); + } + + /* allocate space for an array of items and store a ptr to the array */ + list_count = int_val; + + item_size = ply_type_size[prop->internal_type]; + store_array = (char **) (elem_data + prop->offset); + if (list_count == 0) { + if (store_it) + *store_array = NULL; + } + else { + if (store_it) { + item_ptr = (char *) myalloc (sizeof (char) * item_size * list_count); + + item = item_ptr; + *store_array = item_ptr; + } + + // read items and store them into the array + for (k = 0; k < list_count; k++) { + get_binary_item (fp, plyfile->file_type, prop->external_type, + &int_val, &uint_val, &double_val, native_binary_type); + if (store_it) { + store_item (item, prop->internal_type, + int_val, uint_val, double_val); + item += item_size; + } + } + + + + } + + } + else { /* not a list */ + get_binary_item (fp, plyfile->file_type, prop->external_type, + &int_val, &uint_val, &double_val, native_binary_type); + if (store_it) { + item = elem_data + prop->offset; + store_item (item, prop->internal_type, int_val, uint_val, double_val); + } + } + + } +} + + +/****************************************************************************** +Write to a file the word that represents a PLY data type. + +Entry: + fp - file pointer + code - code for type +******************************************************************************/ + +inline void write_scalar_type (FILE *fp, int code) +{ + /* make sure this is a valid code */ + + if (code <= PLY_START_TYPE || code >= PLY_END_TYPE) { + fprintf (stderr, "write_scalar_type: bad data code = %d\n", code); + exit (-1); + } + + /* write the code to a file */ + const char *type_names[] = { + "invalid", + "char", "short", "int", + "uchar", "ushort", "uint", + "float", "double", + }; + + + fprintf (fp, "%s", type_names[code]); +} + +/****************************************************************************** + Reverse the order in an array of bytes. This is the conversion from big + endian to little endian and vice versa + +Entry: + bytes - array of bytes to reverse (in place) + num_bytes - number of bytes in array +******************************************************************************/ + +inline void swap_bytes(char *bytes, int num_bytes) +{ + int i; + char temp; + + for (i=0; i < num_bytes/2; i++) + { + temp = bytes[i]; + bytes[i] = bytes[(num_bytes-1)-i]; + bytes[(num_bytes-1)-i] = temp; + } +} + +/****************************************************************************** + Find out if this machine is big endian or little endian + + Exit: + set global variable, native_binary_type = + either PLY_BINARY_BE or PLY_BINARY_LE + +******************************************************************************/ + +inline void get_native_binary_type(int *native_binary_type) +{ + typedef union + { + int int_value; + char byte_values[sizeof(int)]; + } endian_test_type; + + + endian_test_type test; + + test.int_value = 0; + test.int_value = 1; + if (test.byte_values[0] == 1) + *native_binary_type = PLY_BINARY_LE; + else if (test.byte_values[sizeof(int)-1] == 1) + *native_binary_type = PLY_BINARY_BE; + else + { + fprintf(stderr, "ply: Couldn't determine machine endianness.\n"); + fprintf(stderr, "ply: Exiting...\n"); + exit(1); + } +} + +inline int get_native_binary_type2() +{ + typedef union + { + int int_value; + char byte_values[sizeof(int)]; + } endian_test_type; + + + endian_test_type test; + + test.int_value = 0; + test.int_value = 1; + if (test.byte_values[0] == 1) + return PLY_BINARY_LE; + else if (test.byte_values[sizeof(int)-1] == 1) + return PLY_BINARY_BE; + else + { + fprintf(stderr, "ply: Couldn't determine machine endianness.\n"); + fprintf(stderr, "ply: Exiting...\n"); + exit(1); + } +} + +/****************************************************************************** + Verify that all the native types are the sizes we need + + +******************************************************************************/ + +inline int check_types() +{ + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + if ((ply_type_size[PLY_CHAR] != sizeof(char)) || + (ply_type_size[PLY_SHORT] != sizeof(short)) || + (ply_type_size[PLY_INT] != sizeof(int)) || + (ply_type_size[PLY_UCHAR] != sizeof(unsigned char)) || + (ply_type_size[PLY_USHORT] != sizeof(unsigned short)) || + (ply_type_size[PLY_UINT] != sizeof(unsigned int)) || + (ply_type_size[PLY_FLOAT] != sizeof(float)) || + (ply_type_size[PLY_DOUBLE] != sizeof(double))) + { + fprintf(stderr, "ply: Type sizes do not match built-in types\n"); + fprintf(stderr, "ply: Exiting...\n"); + exit(1); + } + + return 1; +} + +/****************************************************************************** +Get a text line from a file and break it up into words. + +IMPORTANT: The calling routine call "free" on the returned pointer once +finished with it. + +Entry: + fp - file to read from + +Exit: + nwords - number of words returned + orig_line - the original line of characters + returns a list of words from the line, or NULL if end-of-file +******************************************************************************/ + +inline char **get_words(FILE *fp, int *nwords, char **orig_line) +{ + #define BIG_STRING 4096 + char str[BIG_STRING]; + char str_copy[BIG_STRING]; + char **words; + int max_words = 10; + int num_words = 0; + char *ptr,*ptr2; + char *result; + + fpos_t pos; //keep track of file pointer + int nbytes; + int nonUNIX; + nonUNIX=0; + nbytes=0; + fgetpos(fp, &pos); + + words = (char **) myalloc (sizeof (char *) * max_words); + + /* read in a line */ + result = fgets (str, BIG_STRING, fp); + if (result == NULL) { + *nwords = 0; + *orig_line = NULL; + return (NULL); + } + + /* convert line-feed and tabs into spaces */ + /* (this guarantees that there will be a space before the */ + /* null character at the end of the string) */ + + str[BIG_STRING-2] = ' '; + str[BIG_STRING-1] = '\0'; + + for (ptr = str, ptr2 = str_copy; *ptr != '\0'; ptr++, ptr2++) { + *ptr2 = *ptr; + nbytes++; + if (*ptr == '\t') { + *ptr = ' '; + *ptr2 = ' '; + } + else if (*ptr == '\n') { + *ptr = ' '; //has to have a space, to be caught later when grouping words + *ptr2 = '\0'; + break; + } + else if (*ptr == '\r') + { //MAC line break + nonUNIX=1; + if(*(ptr+1)=='\n') //actuall PC line break + { + nbytes++; + } + + *ptr = ' '; + + *(ptr+1) = '\0'; //when reading mac, best end string here + *ptr2 = '\0'; //note a pc \r is followed by \n + + break; + } + } + + + /*check to see if a PC or MAC header was detected instead of UNIX*/ + if(nonUNIX==1) + { + fsetpos(fp, &pos); + fseek(fp, nbytes, SEEK_CUR); + } + + /* find the words in the line */ + + ptr = str; + while (*ptr != '\0') { + + /* jump over leading spaces */ + while (*ptr == ' ') + ptr++; + + /* break if we reach the end */ + if (*ptr == '\0') + break; + + /* save pointer to beginning of word */ + if (num_words >= max_words) { + max_words += 10; + words = (char **) realloc (words, sizeof (char *) * max_words); + } + words[num_words++] = ptr; + + /* jump over non-spaces */ + while (*ptr != ' ') + ptr++; + + /* place a null character here to mark the end of the word */ + *ptr++ = '\0'; + } + + /* return the list of words */ + *nwords = num_words; + *orig_line = str_copy; + return (words); +} + +/* +char **get_words(FILE *fp, int *nwords, char **orig_line) +{ +#define BIG_STRING 4096 + static char str[BIG_STRING]; + static char str_copy[BIG_STRING]; + char **words; + int max_words = 10; + int num_words = 0; + char *ptr,*ptr2; + char *result; + + words = (char **) myalloc (sizeof (char *) * max_words); + + // read in a line + result = fgets (str, BIG_STRING, fp); + if (result == NULL) { + *nwords = 0; + *orig_line = NULL; + return (NULL); + } + + // convert line-feed and tabs into spaces + // (this guarantees that there will be a space before the + // null character at the end of the string) + + str[BIG_STRING-2] = ' '; + str[BIG_STRING-1] = '\0'; + + for (ptr = str, ptr2 = str_copy; *ptr != '\0'; ptr++, ptr2++) { + *ptr2 = *ptr; + if (*ptr == '\t') { + *ptr = ' '; + *ptr2 = ' '; + } + else if (*ptr == '\n') { + *ptr = ' '; + *ptr2 = '\0'; + break; + } + else if (*ptr == '\r') { + *ptr = '\0'; + *ptr2 = '\0'; //note don't break yet, on a pc \r is followed by \n + } + } + + // find the words in the line + + ptr = str; + while (*ptr != '\0') { + + // jump over leading spaces + while (*ptr == ' ') + ptr++; + + // break if we reach the end + if (*ptr == '\0') + break; + + // save pointer to beginning of word + if (num_words >= max_words) { + max_words += 10; + words = (char **) realloc (words, sizeof (char *) * max_words); + } + words[num_words++] = ptr; + + // jump over non-spaces + while (*ptr != ' ') + ptr++; + + // place a null character here to mark the end of the word + *ptr++ = '\0'; + } + + // return the list of words + *nwords = num_words; + *orig_line = str_copy; + return (words); +}*/ + +/****************************************************************************** +Return the value of an item, given a pointer to it and its type. + +Entry: + item - pointer to item + type - data type that "item" points to + +Exit: + returns a double-precision float that contains the value of the item +******************************************************************************/ + +inline double get_item_value(char *item, int type) +{ + unsigned char *puchar; + char *pchar; + short int *pshort; + unsigned short int *pushort; + int *pint; + unsigned int *puint; + float *pfloat; + double *pdouble; + int int_value; + unsigned int uint_value; + double double_value; + + switch (type) { + case PLY_CHAR: + pchar = (char *) item; + int_value = *pchar; + return ((double) int_value); + case PLY_UCHAR: + puchar = (unsigned char *) item; + int_value = *puchar; + return ((double) int_value); + case PLY_SHORT: + pshort = (short int *) item; + int_value = *pshort; + return ((double) int_value); + case PLY_USHORT: + pushort = (unsigned short int *) item; + int_value = *pushort; + return ((double) int_value); + case PLY_INT: + pint = (int *) item; + int_value = *pint; + return ((double) int_value); + case PLY_UINT: + puint = (unsigned int *) item; + uint_value = *puint; + return ((double) uint_value); + case PLY_FLOAT: + pfloat = (float *) item; + double_value = *pfloat; + return (double_value); + case PLY_DOUBLE: + pdouble = (double *) item; + double_value = *pdouble; + return (double_value); + default: + fprintf (stderr, "get_item_value: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Write out an item to a file as raw binary bytes. + +Entry: + fp - file to write to + int_val - integer version of item + uint_val - unsigned integer version of item + double_val - double-precision float version of item + type - data type to write out +******************************************************************************/ + +inline void write_binary_item( + FILE *fp, + int file_type, + int int_val, + unsigned int uint_val, + double double_val, + int type, + int *native_binary_type +) +{ + unsigned char uchar_val; + char char_val; + unsigned short ushort_val; + short short_val; + float float_val; + void *value; + + switch (type) { + case PLY_CHAR: + char_val = int_val; + value = &char_val; + break; + case PLY_SHORT: + short_val = int_val; + value = &short_val; + break; + case PLY_INT: + value = &int_val; + break; + case PLY_UCHAR: + uchar_val = uint_val; + value = &uchar_val; + break; + case PLY_USHORT: + ushort_val = uint_val; + value = &ushort_val; + break; + case PLY_UINT: + value = &uint_val; + break; + case PLY_FLOAT: + float_val = double_val; + value = &float_val; + break; + case PLY_DOUBLE: + value = &double_val; + break; + default: + fprintf (stderr, "write_binary_item: bad type = %d\n", type); + exit (-1); + } + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + if ((file_type != *native_binary_type) && (ply_type_size[type] > 1)) + swap_bytes((char *)value, ply_type_size[type]); + + if (fwrite (value, ply_type_size[type], 1, fp) != 1) + { + fprintf(stderr, "PLY ERROR: fwrite() failed -- aborting.\n"); + exit(1); + } +} + + +/****************************************************************************** +Write out an item to a file as ascii characters. + +Entry: + fp - file to write to + int_val - integer version of item + uint_val - unsigned integer version of item + double_val - double-precision float version of item + type - data type to write out +******************************************************************************/ + +inline void write_ascii_item( + FILE *fp, + int int_val, + unsigned int uint_val, + double double_val, + int type +) +{ + switch (type) { + case PLY_CHAR: + case PLY_SHORT: + case PLY_INT: + if (fprintf (fp, "%d ", int_val) <= 0) + { + fprintf(stderr, "PLY ERROR: fprintf() failed -- aborting.\n"); + exit(1); + } + break; + case PLY_UCHAR: + case PLY_USHORT: + case PLY_UINT: + if (fprintf (fp, "%u ", uint_val) <= 0) + { + fprintf(stderr, "PLY ERROR: fprintf() failed -- aborting.\n"); + exit(1); + } + break; + case PLY_FLOAT: + case PLY_DOUBLE: + if (fprintf (fp, "%g ", double_val) <= 0) + { + fprintf(stderr, "PLY ERROR: fprintf() failed -- aborting.\n"); + exit(1); + } + break; + default: + fprintf (stderr, "write_ascii_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Write out an item to a file as ascii characters. + +Entry: + fp - file to write to + item - pointer to item to write + type - data type that "item" points to + +Exit: + returns a double-precision float that contains the value of the written item +******************************************************************************/ + +inline double old_write_ascii_item(FILE *fp, char *item, int type) +{ + unsigned char *puchar; + char *pchar; + short int *pshort; + unsigned short int *pushort; + int *pint; + unsigned int *puint; + float *pfloat; + double *pdouble; + int int_value; + unsigned int uint_value; + double double_value; + + switch (type) { + case PLY_CHAR: + pchar = (char *) item; + int_value = *pchar; + fprintf (fp, "%d ", int_value); + return ((double) int_value); + case PLY_UCHAR: + puchar = (unsigned char *) item; + int_value = *puchar; + fprintf (fp, "%d ", int_value); + return ((double) int_value); + case PLY_SHORT: + pshort = (short int *) item; + int_value = *pshort; + fprintf (fp, "%d ", int_value); + return ((double) int_value); + case PLY_USHORT: + pushort = (unsigned short int *) item; + int_value = *pushort; + fprintf (fp, "%d ", int_value); + return ((double) int_value); + case PLY_INT: + pint = (int *) item; + int_value = *pint; + fprintf (fp, "%d ", int_value); + return ((double) int_value); + case PLY_UINT: + puint = (unsigned int *) item; + uint_value = *puint; + fprintf (fp, "%u ", uint_value); + return ((double) uint_value); + case PLY_FLOAT: + pfloat = (float *) item; + double_value = *pfloat; + fprintf (fp, "%g ", double_value); + return (double_value); + case PLY_DOUBLE: + pdouble = (double *) item; + double_value = *pdouble; + fprintf (fp, "%g ", double_value); + return (double_value); + default: + fprintf (stderr, "old_write_ascii_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Get the value of an item that is in memory, and place the result +into an integer, an unsigned integer and a double. + +Entry: + ptr - pointer to the item + type - data type supposedly in the item + +Exit: + int_val - integer value + uint_val - unsigned integer value + double_val - double-precision floating point value +******************************************************************************/ + +inline void get_stored_item( + void *ptr, + int type, + int *int_val, + unsigned int *uint_val, + double *double_val +) +{ + switch (type) { + case PLY_CHAR: + *int_val = *((char *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_UCHAR: + *uint_val = *((unsigned char *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_SHORT: + *int_val = *((short int *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_USHORT: + *uint_val = *((unsigned short int *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_INT: + *int_val = *((int *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_UINT: + *uint_val = *((unsigned int *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_FLOAT: + *double_val = *((float *) ptr); + *int_val = (int) *double_val; + *uint_val = (unsigned int) *double_val; + break; + case PLY_DOUBLE: + *double_val = *((double *) ptr); + *int_val = (int) *double_val; + *uint_val = (unsigned int) *double_val; + break; + default: + fprintf (stderr, "get_stored_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Get the value of an item from a binary file, and place the result +into an integer, an unsigned integer and a double. + +Entry: + fp - file to get item from + type - data type supposedly in the word + +Exit: + int_val - integer value + uint_val - unsigned integer value + double_val - double-precision floating point value +******************************************************************************/ + +inline void get_binary_item( + FILE *fp, + int file_type, + int type, + int *int_val, + unsigned int *uint_val, + double *double_val, + int *native_binary_type +) +{ + char c[8]; + void *ptr; + + ptr = (void *) c; + int ply_type_size[] = { + 0, 1, 2, 4, 1, 2, 4, 4, 8 + }; + + if (fread (ptr, ply_type_size[type], 1, fp) != 1) + { + fprintf(stderr, "PLY ERROR: fread() failed -- aborting.\n"); + exit(1); + } + + + if ((file_type != *native_binary_type) && (ply_type_size[type] > 1)) + swap_bytes((char *)ptr, ply_type_size[type]); + + switch (type) { + case PLY_CHAR: + *int_val = *((char *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_UCHAR: + *uint_val = *((unsigned char *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_SHORT: + *int_val = *((short int *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_USHORT: + *uint_val = *((unsigned short int *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_INT: + *int_val = *((int *) ptr); + *uint_val = *int_val; + *double_val = *int_val; + break; + case PLY_UINT: + *uint_val = *((unsigned int *) ptr); + *int_val = *uint_val; + *double_val = *uint_val; + break; + case PLY_FLOAT: + *double_val = *((float *) ptr); + *int_val = (int) *double_val; + *uint_val = (unsigned int) *double_val; + break; + case PLY_DOUBLE: + *double_val = *((double *) ptr); + *int_val = (int) *double_val; + *uint_val = (unsigned int) *double_val; + break; + default: + fprintf (stderr, "get_binary_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Extract the value of an item from an ascii word, and place the result +into an integer, an unsigned integer and a double. + +Entry: + word - word to extract value from + type - data type supposedly in the word + +Exit: + int_val - integer value + uint_val - unsigned integer value + double_val - double-precision floating point value +******************************************************************************/ + +inline void get_ascii_item( + char *word, + int type, + int *int_val, + unsigned int *uint_val, + double *double_val +) +{ + switch (type) { + case PLY_CHAR: + case PLY_UCHAR: + case PLY_SHORT: + case PLY_USHORT: + case PLY_INT: + *int_val = atoi (word); + *uint_val = (unsigned int) *int_val; + *double_val = (double) *int_val; + break; + + case PLY_UINT: + *uint_val = strtol (word, (char **) NULL, 10); + *int_val = (int) *uint_val; + *double_val = (double) *uint_val; + break; + + case PLY_FLOAT: + case PLY_DOUBLE: + *double_val = atof (word); + *int_val = (int) *double_val; + *uint_val = (unsigned int) *double_val; + break; + + default: + fprintf (stderr, "get_ascii_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Store a value into a place being pointed to, guided by a data type. + +Entry: + item - place to store value + type - data type + int_val - integer version of value + uint_val - unsigned integer version of value + double_val - double version of value + +Exit: + item - pointer to stored value +******************************************************************************/ + +inline void store_item ( + char *item, + int type, + int int_val, + unsigned int uint_val, + double double_val +) +{ + unsigned char *puchar; + short int *pshort; + unsigned short int *pushort; + int *pint; + unsigned int *puint; + float *pfloat; + double *pdouble; + + switch (type) { + case PLY_CHAR: + *item = int_val; + break; + case PLY_UCHAR: + puchar = (unsigned char *) item; + *puchar = uint_val; + break; + case PLY_SHORT: + pshort = (short *) item; + *pshort = int_val; + break; + case PLY_USHORT: + pushort = (unsigned short *) item; + *pushort = uint_val; + break; + case PLY_INT: + pint = (int *) item; + *pint = int_val; + break; + case PLY_UINT: + puint = (unsigned int *) item; + *puint = uint_val; + break; + case PLY_FLOAT: + pfloat = (float *) item; + *pfloat = double_val; + break; + case PLY_DOUBLE: + pdouble = (double *) item; + *pdouble = double_val; + break; + default: + fprintf (stderr, "store_item: bad type = %d\n", type); + exit (-1); + } +} + + +/****************************************************************************** +Add an element to a PLY file descriptor. + +Entry: + plyfile - PLY file descriptor + words - list of words describing the element + nwords - number of words in the list +******************************************************************************/ + +inline void add_element (PlyFile *plyfile, char **words) +{ + PlyElement *elem; + + /* create the new element */ + elem = (PlyElement *) myalloc (sizeof (PlyElement)); + elem->name = strdup (words[1]); + elem->num = atoi (words[2]); + elem->nprops = 0; + + /* make room for new element in the object's list of elements */ + if (plyfile->nelems == 0) + plyfile->elems = (PlyElement **) myalloc (sizeof (PlyElement *)); + else + plyfile->elems = (PlyElement **) realloc (plyfile->elems, + sizeof (PlyElement *) * (plyfile->nelems + 1)); + + /* add the new element to the object's list */ + plyfile->elems[plyfile->nelems] = elem; + plyfile->nelems++; +} + + +/****************************************************************************** +Return the type of a property, given the name of the property. + +Entry: + name - name of property type + +Exit: + returns integer code for property, or 0 if not found +******************************************************************************/ + +inline int get_prop_type(char *type_name) +{ + int i; + const char *type_names[] = { + "invalid", + "char", "short", "int", + "uchar", "ushort", "uint", + "float", "double", + }; + + const char *alt_type_names[] = { + "invalid", + "int8", "int16", "int32", "uint8", "uint16", "uint32", "float32", "float64", + }; + + + for (i = PLY_START_TYPE + 1; i < PLY_END_TYPE; i++) + if (equal_strings (type_name, type_names[i])) + return (i); + + for (i = PLY_START_TYPE + 1; i < PLY_END_TYPE; i++) + if (equal_strings (type_name, alt_type_names[i])) + return (i); + + /* if we get here, we didn't find the type */ + return (0); +} + + +/****************************************************************************** +Add a property to a PLY file descriptor. + +Entry: + plyfile - PLY file descriptor + words - list of words describing the property + nwords - number of words in the list +******************************************************************************/ + +inline void add_property (PlyFile *plyfile, char **words) +{ + PlyProperty *prop; + PlyElement *elem; + + /* create the new property */ + + prop = (PlyProperty *) myalloc (sizeof (PlyProperty)); + + if (equal_strings (words[1], "list")) { /* is a list */ + prop->count_external = get_prop_type (words[2]); + prop->external_type = get_prop_type (words[3]); + prop->name = strdup (words[4]); + prop->is_list = 1; + } + else { /* not a list */ + prop->external_type = get_prop_type (words[1]); + prop->name = strdup (words[2]); + prop->is_list = 0; + } + + /* add this property to the list of properties of the current element */ + + elem = plyfile->elems[plyfile->nelems - 1]; + + if (elem->nprops == 0) + elem->props = (PlyProperty **) myalloc (sizeof (PlyProperty *)); + else + elem->props = (PlyProperty **) realloc (elem->props, + sizeof (PlyProperty *) * (elem->nprops + 1)); + + elem->props[elem->nprops] = prop; + elem->nprops++; +} + + +/****************************************************************************** +Add a comment to a PLY file descriptor. + +Entry: + plyfile - PLY file descriptor + line - line containing comment +******************************************************************************/ + +inline void add_comment (PlyFile *plyfile, char *line) +{ + int i; + + /* skip over "comment" and leading spaces and tabs */ + i = 7; + while (line[i] == ' ' || line[i] == '\t') + i++; + + ply_put_comment (plyfile, &line[i]); +} + + +/****************************************************************************** +Add a some object information to a PLY file descriptor. + +Entry: + plyfile - PLY file descriptor + line - line containing text info +******************************************************************************/ + +inline void add_obj_info (PlyFile *plyfile, char *line) +{ + int i; + + /* skip over "obj_info" and leading spaces and tabs */ + i = 8; + while (line[i] == ' ' || line[i] == '\t') + i++; + + ply_put_obj_info (plyfile, &line[i]); +} + + +/****************************************************************************** +Copy a property. +******************************************************************************/ + +inline void copy_property(PlyProperty *dest, PlyProperty *src) +{ + dest->name = strdup (src->name); + dest->external_type = src->external_type; + dest->internal_type = src->internal_type; + dest->offset = src->offset; + + dest->is_list = src->is_list; + dest->count_external = src->count_external; + dest->count_internal = src->count_internal; + dest->count_offset = src->count_offset; +} + + +/****************************************************************************** +Allocate some memory. + +Entry: + size - amount of memory requested (in bytes) + lnum - line number from which memory was requested + fname - file name from which memory was requested +******************************************************************************/ + +inline char *my_alloc(int size, int lnum, const char *fe) +{ + char *ptr; + + ptr = (char *) malloc (size); + + if (ptr == 0) { + fprintf(stderr, "Memory allocation bombed on line %d in %s\n", lnum, fe); + } + + return (ptr); +} + +} +} +#endif diff --git a/src/igl/png/readPNG.cpp b/src/igl/png/readPNG.cpp new file mode 100644 index 000000000..8d42ad8d9 --- /dev/null +++ b/src/igl/png/readPNG.cpp @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readPNG.h" +#include + +IGL_INLINE bool igl::png::readPNG( + const std::string png_file, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A +) +{ + int cols,rows,n; + unsigned char *data = stbi_load(png_file.c_str(), &cols, &rows, &n, 4); + if(data == NULL) { + return false; + } + + R.resize(cols,rows); + G.resize(cols,rows); + B.resize(cols,rows); + A.resize(cols,rows); + + for (unsigned i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_READ_PNG_H +#define IGL_PNG_READ_PNG_H +#include "../igl_inline.h" +#include +#include + +namespace igl +{ + namespace png + { + // Read an image from a .png file into 4 memory buffers + // + // Input: + // png_file path to .png file + // Output: + // R,G,B,A texture channels + // Returns true on success, false on failure + // + IGL_INLINE bool readPNG(const std::string png_file, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A + ); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "readPNG.cpp" +#endif + +#endif diff --git a/src/igl/png/render_to_png.cpp b/src/igl/png/render_to_png.cpp new file mode 100644 index 000000000..6533ad9ab --- /dev/null +++ b/src/igl/png/render_to_png.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "render_to_png.h" +#include + +#include "../opengl/gl.h" + +IGL_INLINE bool igl::png::render_to_png( + const std::string png_file, + const int width, + const int height, + const bool alpha, + const bool fast) +{ + unsigned char * data = new unsigned char[4*width*height]; + glReadPixels( + 0, + 0, + width, + height, + GL_RGBA, + GL_UNSIGNED_BYTE, + data); + //img->flip(); + if(!alpha) + { + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_RENDER_TO_PNG_H +#define IGL_PNG_RENDER_TO_PNG_H +#include + +#include +namespace igl +{ + namespace png + { + // + // Render current open GL image to .png file + // Inputs: + // png_file path to output .png file + // width width of scene and resulting image + // height height of scene and resulting image + // alpha whether to include alpha channel + // fast sacrifice compression ratio for speed + // Returns true only if no errors occurred + // + // See also: igl/render_to_tga which is faster but writes .tga files + IGL_INLINE bool render_to_png( + const std::string png_file, + const int width, + const int height, + const bool alpha = true, + const bool fast = false); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "render_to_png.cpp" +#endif + +#endif diff --git a/src/igl/png/render_to_png_async.cpp b/src/igl/png/render_to_png_async.cpp new file mode 100644 index 000000000..033701e63 --- /dev/null +++ b/src/igl/png/render_to_png_async.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "render_to_png_async.h" +#include "../opengl/gl.h" +#include + +static IGL_INLINE bool render_to_png_async_helper( + unsigned char * img, int width, int height, + const std::string png_file, + const bool alpha, + const bool fast) +{ + //img->flip(); + if(!alpha) + { + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_RENDER_TO_PNG_ASYNC_H +#define IGL_PNG_RENDER_TO_PNG_ASYNC_H +#include +#include +//#include + +#include +namespace igl +{ + namespace png + { + // History: + // added multithreaded parameter and support, Alec Sept 3, 2012 + // + // Render current open GL image to .png file + // Inputs: + // png_file path to output .png file + // width width of scene and resulting image + // height height of scene and resulting image + // alpha whether to include alpha channel + // fast sacrifice compression ratio for speed + // Returns true only if no errors occurred + // + // See also: igl/render_to_tga which is faster but writes .tga files + IGL_INLINE std::thread render_to_png_async( + const std::string png_file, + const int width, + const int height, + const bool alpha = true, + const bool fast = false); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "render_to_png_async.cpp" +#endif + +#endif diff --git a/src/igl/png/texture_from_file.cpp b/src/igl/png/texture_from_file.cpp new file mode 100644 index 000000000..60e8c795c --- /dev/null +++ b/src/igl/png/texture_from_file.cpp @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "texture_from_file.h" + +#include "texture_from_png.h" +#include "../STR.h" +#include "../pathinfo.h" +#include "../opengl/report_gl_error.h" +//#include "../opengl2/texture_from_tga.h" +#include +#include +#include + +IGL_INLINE bool igl::png::texture_from_file(const std::string filename, GLuint & id) +{ + using namespace igl::opengl; + using namespace std; + // dirname, basename, extension and filename + string d,b,ext,f; + pathinfo(filename,d,b,ext,f); + // Convert extension to lower case + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + //if(ext == "tga") + //{ + // return texture_from_tga(filename,id); + //}else + if(ext == "png") + { + return texture_from_png(filename,id); + }else + { +#ifdef __APPLE__ + // Convert to a temporary png file + string tmp = "/var/tmp/.texture_from_file.png"; +#define PATH_TO_CONVERT "/opt/local/bin/convert" + string command = STR(PATH_TO_CONVERT<<" \""< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_TEXTURE_FROM_FILE_H +#define IGL_PNG_TEXTURE_FROM_FILE_H +#include "../igl_inline.h" +#include "../opengl/gl.h" + +#include + +namespace igl +{ + namespace png + { + // Read an image from an image file and use it as a texture. Officially, + // only .tga and .png are supported. Any filetype read by + // ImageMagick's `convert` will work via an unsafe system call. + // + // Input: + // filename path to image file + // Output: + // id of generated openGL texture + // Returns true on success, false on failure + IGL_INLINE bool texture_from_file(const std::string filename, GLuint & id); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "texture_from_file.cpp" +#endif + +#endif + + + diff --git a/src/igl/png/texture_from_png.cpp b/src/igl/png/texture_from_png.cpp new file mode 100644 index 000000000..fde3c002c --- /dev/null +++ b/src/igl/png/texture_from_png.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "texture_from_png.h" + +#include "../opengl/report_gl_error.h" +#include + +IGL_INLINE bool igl::png::texture_from_png(const std::string png_file, const bool flip, GLuint & id) +{ + int width,height,n; + unsigned char *data = igl::stbi_load(png_file.c_str(), &width, &height, &n, 4); + if(data == NULL) { + return false; + } + + // Why do I need to flip? + /*if(flip) + { + yimg.flip(); + }*/ + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB, + width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindTexture(GL_TEXTURE_2D, 0); + + igl::stbi_image_free(data); + + return true; +} + +IGL_INLINE bool igl::png::texture_from_png(const std::string png_file, GLuint & id) +{ + return texture_from_png(png_file,false,id); +} + + +IGL_INLINE bool igl::png::texture_from_png( + const std::string png_file, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A +) +{ + int width,height,n; + unsigned char *data = igl::stbi_load(png_file.c_str(), &width, &height, &n, 4); + if(data == NULL) { + return false; + } + + R.resize(height,width); + G.resize(height,width); + B.resize(height,width); + A.resize(height,width); + + for (unsigned j=0; j +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_TEXTURE_FROM_PNG_H +#define IGL_PNG_TEXTURE_FROM_PNG_H +#include "../igl_inline.h" +#include +#include + +#include "../opengl/gl.h" + +namespace igl +{ + namespace png + { + // Read an image from a .png file and use it as a texture + // + // Input: + // png_file path to .png file + // flip whether to flip the image vertically (A --> ∀) + // Output: + // id of generated openGL texture + // Returns true on success, false on failure + IGL_INLINE bool texture_from_png(const std::string png_file, const bool flip, GLuint & id); + IGL_INLINE bool texture_from_png(const std::string png_file, GLuint & id); + + // Read an image from a .png file and use it as a texture + // + // Input: + // png_file path to .png file + // Output: + // R,G,B,A texture channels + // Returns true on success, false on failure + // + // Todo: this is an inappropriate function name. This is really just + // reading a png.... Not necessarily as a texture. + IGL_INLINE bool texture_from_png(const std::string png_file, + Eigen::Matrix& R, + Eigen::Matrix& G, + Eigen::Matrix& B, + Eigen::Matrix& A + ); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "texture_from_png.cpp" +#endif + +#endif diff --git a/src/igl/png/writePNG.cpp b/src/igl/png/writePNG.cpp new file mode 100644 index 000000000..9ab68d775 --- /dev/null +++ b/src/igl/png/writePNG.cpp @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writePNG.h" +#include +#include + +IGL_INLINE bool igl::png::writePNG( + const Eigen::Matrix& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B, + const Eigen::Matrix& A, + const std::string png_file +) +{ + assert((R.rows() == G.rows()) && (G.rows() == B.rows()) && (B.rows() == A.rows())); + assert((R.cols() == G.cols()) && (G.cols() == B.cols()) && (B.cols() == A.cols())); + + const int comp = 4; // 4 Channels Red, Green, Blue, Alpha + const int stride_in_bytes = R.rows()*comp; // Length of one row in bytes + std::vector data(R.size()*comp,0); // The image itself; + + for (unsigned i = 0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PNG_WRITE_PNG_H +#define IGL_PNG_WRITE_PNG_H +#include "../igl_inline.h" +#include +#include + +namespace igl +{ + namespace png + { + // Writes an image to a png file + // + // Input: + // R,G,B,A texture channels + // Output: + // png_file path to .png file + // Returns true on success, false on failure + // + IGL_INLINE bool writePNG + ( + const Eigen::Matrix& R, + const Eigen::Matrix& G, + const Eigen::Matrix& B, + const Eigen::Matrix& A, + const std::string png_file + ); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "writePNG.cpp" +#endif + +#endif diff --git a/src/igl/point_in_circle.cpp b/src/igl/point_in_circle.cpp new file mode 100644 index 000000000..9c26d8c0d --- /dev/null +++ b/src/igl/point_in_circle.cpp @@ -0,0 +1,18 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_in_circle.h" + +IGL_INLINE bool igl::point_in_circle( + const double qx, + const double qy, + const double cx, + const double cy, + const double r) +{ + return (qx-cx)*(qx-cx) + (qy-cy)*(qy-cy) - r*r < 0; +} diff --git a/src/igl/point_in_circle.h b/src/igl/point_in_circle.h new file mode 100644 index 000000000..f19344c40 --- /dev/null +++ b/src/igl/point_in_circle.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POINT_IN_CIRCLE_H +#define IGL_POINT_IN_CIRCLE_H +#include "igl_inline.h" + +namespace igl +{ + // Determine if 2d point is in a circle + // Inputs: + // qx x-coordinate of query point + // qy y-coordinate of query point + // cx x-coordinate of circle center + // cy y-coordinate of circle center + // r radius of circle + // Returns true if query point is in circle, false otherwise + IGL_INLINE bool point_in_circle( + const double qx, + const double qy, + const double cx, + const double cy, + const double r); +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_in_circle.cpp" +#endif + +#endif diff --git a/src/igl/point_in_poly.cpp b/src/igl/point_in_poly.cpp new file mode 100644 index 000000000..f0b07edf9 --- /dev/null +++ b/src/igl/point_in_poly.cpp @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_in_poly.h" + +bool IGL_INLINE igl::point_in_poly( const std::vector >&poly, + const unsigned int xt, + const unsigned int yt) +{ + int npoints= poly.size(); + unsigned int xnew,ynew; + unsigned int xold,yold; + unsigned int x1,y1; + unsigned int x2,y2; + int i; + int inside=0; + + if (npoints < 3) { + return(0); + } + xold=poly[npoints-1][0]; + yold=poly[npoints-1][1]; + for (i=0 ; i < npoints ; i++) { + xnew=poly[i][0]; + ynew=poly[i][1]; + if (xnew > xold) { + x1=xold; + x2=xnew; + y1=yold; + y2=ynew; + } + else { + x1=xnew; + x2=xold; + y1=ynew; + y2=yold; + } + if ((xnew < xt) == (xt <= xold) /* edge "open" at one end */ + && ((long)yt-(long)y1)*(long)(x2-x1) + < ((long)y2-(long)y1)*(long)(xt-x1)) { + inside=!inside; + } + xold=xnew; + yold=ynew; + } + return (inside != 0); +} diff --git a/src/igl/point_in_poly.h b/src/igl/point_in_poly.h new file mode 100644 index 000000000..2b9c6e51b --- /dev/null +++ b/src/igl/point_in_poly.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POINT_IN_POLY_H +#define IGL_POINT_IN_POLY_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Determine if 2d point is inside a 2D polygon + // Inputs: + // poly vector of polygon points, [0]=x, [1]=y. + // Polyline need not be closed (i.e. first point != last point), + // the line segment between last and first selected points is constructed + // within this function. + // x x-coordinate of query point + // y y-coordinate of query point + // Returns true if query point is in polygon, false otherwise + // from http://www.visibone.com/inpoly/ +bool IGL_INLINE point_in_poly( const std::vector >&poly, + const unsigned int xt, + const unsigned int yt); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_in_poly.cpp" +#endif + +#endif diff --git a/src/igl/point_mesh_squared_distance.cpp b/src/igl/point_mesh_squared_distance.cpp new file mode 100644 index 000000000..57dd9aa14 --- /dev/null +++ b/src/igl/point_mesh_squared_distance.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_mesh_squared_distance.h" +#include "AABB.h" +#include + +template < + typename DerivedP, + typename DerivedV, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> +IGL_INLINE void igl::point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C) +{ + using namespace std; + const size_t dim = P.cols(); + assert((dim == 2 || dim == 3) && "P.cols() should be 2 or 3"); + assert(P.cols() == V.cols() && "P.cols() should equal V.cols()"); + switch(dim) + { + default: + assert(false && "Unsupported dim"); + // fall-through and pray + case 3: + { + AABB tree; + tree.init(V,Ele); + return tree.squared_distance(V,Ele,P,sqrD,I,C); + } + case 2: + { + AABB tree; + tree.init(V,Ele); + return tree.squared_distance(V,Ele,P,sqrD,I,C); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::point_mesh_squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::point_mesh_squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::point_mesh_squared_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/point_mesh_squared_distance.h b/src/igl/point_mesh_squared_distance.h new file mode 100644 index 000000000..0635288a3 --- /dev/null +++ b/src/igl/point_mesh_squared_distance.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POINT_MESH_SQUARED_DISTANCE_H +#define IGL_POINT_MESH_SQUARED_DISTANCE_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Compute distances from a set of points P to a triangle mesh (V,F) + // + // Inputs: + // P #P by 3 list of query point positions + // V #V by 3 list of vertex positions + // Ele #Ele by (3|2|1) list of (triangle|edge|point) indices + // Outputs: + // sqrD #P list of smallest squared distances + // I #P list of primitive indices corresponding to smallest distances + // C #P by 3 list of closest points + // + // Known bugs: This only computes distances to given primitivess. So + // unreferenced vertices are ignored. However, degenerate primitives are + // handled correctly: triangle [1 2 2] is treated as a segment [1 2], and + // triangle [1 1 1] is treated as a point. So one _could_ add extra + // combinatorially degenerate rows to Ele for all unreferenced vertices to + // also get distances to points. + template < + typename DerivedP, + typename DerivedV, + typename DerivedsqrD, + typename DerivedI, + typename DerivedC> + IGL_INLINE void point_mesh_squared_distance( + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & V, + const Eigen::MatrixXi & Ele, + Eigen::PlainObjectBase & sqrD, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "point_mesh_squared_distance.cpp" +#endif + +#endif diff --git a/src/igl/point_simplex_squared_distance.cpp b/src/igl/point_simplex_squared_distance.cpp new file mode 100644 index 000000000..e2bc34d28 --- /dev/null +++ b/src/igl/point_simplex_squared_distance.cpp @@ -0,0 +1,181 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "point_simplex_squared_distance.h" +#include "project_to_line_segment.h" +#include "barycentric_coordinates.h" +#include +#include +#include + + + +template < + int DIM, + typename Derivedp, + typename DerivedV, + typename DerivedEle, + typename Derivedsqr_d, + typename Derivedc, + typename Derivedb> +IGL_INLINE void igl::point_simplex_squared_distance( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const typename DerivedEle::Index primitive, + Derivedsqr_d & sqr_d, + Eigen::MatrixBase & c, + Eigen::PlainObjectBase & bary) +{ + typedef typename Derivedp::Scalar Scalar; + typedef typename Eigen::Matrix Vector; + typedef Vector Point; + //typedef Derivedb BaryPoint; + typedef Eigen::Matrix BaryPoint; + + const auto & Dot = [](const Point & a, const Point & b)->Scalar + { + return a.dot(b); + }; + // Real-time collision detection, Ericson, Chapter 5 + const auto & ClosestBaryPtPointTriangle = + [&Dot](const Point &p, const Point &a, const Point &b, const Point &c, BaryPoint& bary_out )->Point + { + // Check if P in vertex region outside A + Vector ab = b - a; + Vector ac = c - a; + Vector ap = p - a; + Scalar d1 = Dot(ab, ap); + Scalar d2 = Dot(ac, ap); + if (d1 <= 0.0 && d2 <= 0.0) { + // barycentric coordinates (1,0,0) + bary_out << 1, 0, 0; + return a; + } + // Check if P in vertex region outside B + Vector bp = p - b; + Scalar d3 = Dot(ab, bp); + Scalar d4 = Dot(ac, bp); + if (d3 >= 0.0 && d4 <= d3) { + // barycentric coordinates (0,1,0) + bary_out << 0, 1, 0; + return b; + } + // Check if P in edge region of AB, if so return projection of P onto AB + Scalar vc = d1*d4 - d3*d2; + if( a != b) + { + if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) { + Scalar v = d1 / (d1 - d3); + // barycentric coordinates (1-v,v,0) + bary_out << 1-v, v, 0; + return a + v * ab; + } + } + // Check if P in vertex region outside C + Vector cp = p - c; + Scalar d5 = Dot(ab, cp); + Scalar d6 = Dot(ac, cp); + if (d6 >= 0.0 && d5 <= d6) { + // barycentric coordinates (0,0,1) + bary_out << 0, 0, 1; + return c; + } + // Check if P in edge region of AC, if so return projection of P onto AC + Scalar vb = d5*d2 - d1*d6; + if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) { + Scalar w = d2 / (d2 - d6); + // barycentric coordinates (1-w,0,w) + bary_out << 1-w, 0, w; + return a + w * ac; + } + // Check if P in edge region of BC, if so return projection of P onto BC + Scalar va = d3*d6 - d5*d4; + if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) { + Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + // barycentric coordinates (0,1-w,w) + bary_out << 0, 1-w, w; + return b + w * (c - b); + } + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + Scalar denom = 1.0 / (va + vb + vc); + Scalar v = vb * denom; + Scalar w = vc * denom; + bary_out << 1.0-v-w, v, w; + return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w + }; + + assert(p.size() == DIM); + assert(V.cols() == DIM); + assert(Ele.cols() <= DIM+1); + assert(Ele.cols() <= 3 && "Only simplices up to triangles are considered"); + + assert((Derivedb::RowsAtCompileTime == 1 || Derivedb::ColsAtCompileTime == 1) && "bary must be Eigen Vector or Eigen RowVector"); + assert( + ((Derivedb::RowsAtCompileTime == -1 || Derivedb::ColsAtCompileTime == -1) || + (Derivedb::RowsAtCompileTime == Ele.cols() || Derivedb::ColsAtCompileTime == Ele.cols()) + ) && "bary must be Dynamic or size of Ele.cols()"); + + BaryPoint tmp_bary; + c = (const Derivedc &)ClosestBaryPtPointTriangle( + p, + V.row(Ele(primitive,0)), + // modulo is a HACK to handle points, segments and triangles. Because of + // this, we need 3d buffer for bary + V.row(Ele(primitive,1%Ele.cols())), + V.row(Ele(primitive,2%Ele.cols())), + tmp_bary); + bary.resize( Derivedb::RowsAtCompileTime == 1 ? 1 : Ele.cols(), Derivedb::ColsAtCompileTime == 1 ? 1 : Ele.cols()); + bary.head(Ele.cols()) = tmp_bary.head(Ele.cols()); + sqr_d = (p-c).squaredNorm(); +} + +template < + int DIM, + typename Derivedp, + typename DerivedV, + typename DerivedEle, + typename Derivedsqr_d, + typename Derivedc> +IGL_INLINE void igl::point_simplex_squared_distance( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const typename DerivedEle::Index primitive, + Derivedsqr_d & sqr_d, + Eigen::MatrixBase & c) +{ + // Use Dynamic because we don't know Ele.cols() at compile time. + Eigen::Matrix b; + point_simplex_squared_distance( p, V, Ele, primitive, sqr_d, c, b ); +} + +namespace igl +{ + template <> IGL_INLINE void point_simplex_squared_distance<2>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, float&, Eigen::MatrixBase >&) {assert(false);}; + template <> IGL_INLINE void point_simplex_squared_distance<2>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&) {assert(false);}; + template <> IGL_INLINE void point_simplex_squared_distance<2>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&) {assert(false);}; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&); +// generated by autoexplicit.sh +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, float&, Eigen::MatrixBase >&); +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, float&, Eigen::MatrixBase >&); +// generated by autoexplicit.sh +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, float&, Eigen::MatrixBase >&); +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&); +template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&); +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); +template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); +template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); +template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/point_simplex_squared_distance.h b/src/igl/point_simplex_squared_distance.h new file mode 100644 index 000000000..0cc871cf0 --- /dev/null +++ b/src/igl/point_simplex_squared_distance.h @@ -0,0 +1,72 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POINT_SIMPLEX_SQUARED_DISTANCE_H +#define IGL_POINT_SIMPLEX_SQUARED_DISTANCE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Determine squared distance from a point to linear simplex. + // + // Inputs: + // p d-long query point + // V #V by d list of vertices + // Ele #Ele by ss<=d+1 list of simplex indices into V + // i index into Ele of simplex + // Outputs: + // sqr_d squared distance of Ele(i) to p + // c closest point on Ele(i) + // + template < + int DIM, + typename Derivedp, + typename DerivedV, + typename DerivedEle, + typename Derivedsqr_d, + typename Derivedc> + IGL_INLINE void point_simplex_squared_distance( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const typename DerivedEle::Index i, + Derivedsqr_d & sqr_d, + Eigen::MatrixBase & c); + // Determine squared distance from a point to linear simplex. + // Also return barycentric coordinate of closest point. + // + // Inputs: + // p d-long query point + // V #V by d list of vertices + // Ele #Ele by ss<=d+1 list of simplex indices into V + // i index into Ele of simplex + // Outputs: + // sqr_d squared distance of Ele(i) to p + // c closest point on Ele(i) + // b barycentric coordinates of closest point on Ele(i) + // + template < + int DIM, + typename Derivedp, + typename DerivedV, + typename DerivedEle, + typename Derivedsqr_d, + typename Derivedc, + typename Derivedb> + IGL_INLINE void point_simplex_squared_distance( + const Eigen::MatrixBase & p, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & Ele, + const typename DerivedEle::Index i, + Derivedsqr_d & sqr_d, + Eigen::MatrixBase & c, + Eigen::PlainObjectBase & b); +} +#ifndef IGL_STATIC_LIBRARY +# include "point_simplex_squared_distance.cpp" +#endif +#endif diff --git a/src/igl/polar_dec.cpp b/src/igl/polar_dec.cpp new file mode 100644 index 000000000..cdd358a07 --- /dev/null +++ b/src/igl/polar_dec.cpp @@ -0,0 +1,97 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "polar_dec.h" +#include "polar_svd.h" +#ifdef _WIN32 +#else +# include +#endif +#include +#include +#include +#include + +// From Olga's CGAL mentee's ARAP code +template < + typename DerivedA, + typename DerivedR, + typename DerivedT, + typename DerivedU, + typename DerivedS, + typename DerivedV> +IGL_INLINE void igl::polar_dec( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & V) +{ + using namespace std; + using namespace Eigen; + typedef typename DerivedA::Scalar Scalar; + + const Scalar th = std::sqrt(Eigen::NumTraits::dummy_precision()); + + Eigen::SelfAdjointEigenSolver eig; + feclearexcept(FE_UNDERFLOW); + eig.computeDirect(A.transpose()*A); + if(fetestexcept(FE_UNDERFLOW) || eig.eigenvalues()(0)/eig.eigenvalues()(2) th) + { + cout<<"resorting to svd 2..."< +IGL_INLINE void igl::polar_dec( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T) +{ + DerivedA U; + DerivedA V; + Eigen::Matrix S; + return igl::polar_dec(A,R,T,U,S,V); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::polar_dec, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_dec, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/polar_dec.h b/src/igl/polar_dec.h new file mode 100644 index 000000000..2b995eabe --- /dev/null +++ b/src/igl/polar_dec.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POLAR_DEC +#define IGL_POLAR_DEC +#include "igl_inline.h" +#include + +namespace igl +{ + // Computes the polar decomposition (R,T) of a matrix A + // Inputs: + // A 3 by 3 matrix to be decomposed + // Outputs: + // R 3 by 3 orthonormal matrix part of decomposition + // T 3 by 3 stretch matrix part of decomposition + // U 3 by 3 left-singular vectors + // S 3 by 1 singular values + // V 3 by 3 right-singular vectors + // + // + template < + typename DerivedA, + typename DerivedR, + typename DerivedT, + typename DerivedU, + typename DerivedS, + typename DerivedV> + IGL_INLINE void polar_dec( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & V); + template < + typename DerivedA, + typename DerivedR, + typename DerivedT> + IGL_INLINE void polar_dec( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T); +} +#ifndef IGL_STATIC_LIBRARY +# include "polar_dec.cpp" +#endif +#endif + diff --git a/src/igl/polar_svd.cpp b/src/igl/polar_svd.cpp new file mode 100644 index 000000000..962a754fb --- /dev/null +++ b/src/igl/polar_svd.cpp @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "polar_svd.h" +#include +#include +#include + +// Adapted from Olga's CGAL mentee's ARAP code +template < + typename DerivedA, + typename DerivedR, + typename DerivedT> +IGL_INLINE void igl::polar_svd( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T) +{ + DerivedA U; + DerivedA V; + Eigen::Matrix S; + return igl::polar_svd(A,R,T,U,S,V); +} + +template < + typename DerivedA, + typename DerivedR, + typename DerivedT, + typename DerivedU, + typename DerivedS, + typename DerivedV> +IGL_INLINE void igl::polar_svd( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & V) +{ + using namespace std; + Eigen::JacobiSVD svd; + svd.compute(A, Eigen::ComputeFullU | Eigen::ComputeFullV ); + U = svd.matrixU(); + V = svd.matrixV(); + S = svd.singularValues(); + R = U*V.transpose(); + const auto & SVT = S.asDiagonal() * V.adjoint(); + // Check for reflection + if(R.determinant() < 0) + { + // Annoyingly the .eval() is necessary + auto W = V.eval(); + W.col(V.cols()-1) *= -1.; + R = U*W.transpose(); + T = W*SVT; + }else + { + T = V*SVT; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::PlainObjectBase > const &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase > &,Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::polar_svd, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/polar_svd.h b/src/igl/polar_svd.h new file mode 100644 index 000000000..13d3f3873 --- /dev/null +++ b/src/igl/polar_svd.h @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POLAR_SVD +#define IGL_POLAR_SVD +#include "igl_inline.h" +#include + +namespace igl +{ + // Computes the polar decomposition (R,T) of a matrix A using SVD singular + // value decomposition + // + // Inputs: + // A 3 by 3 matrix to be decomposed + // Outputs: + // R 3 by 3 rotation matrix part of decomposition (**always rotataion**) + // T 3 by 3 stretch matrix part of decomposition + // U 3 by 3 left-singular vectors + // S 3 by 1 singular values + // V 3 by 3 right-singular vectors + // + // + template < + typename DerivedA, + typename DerivedR, + typename DerivedT, + typename DerivedU, + typename DerivedS, + typename DerivedV> + IGL_INLINE void polar_svd( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & V); + template < + typename DerivedA, + typename DerivedR, + typename DerivedT> + IGL_INLINE void polar_svd( + const Eigen::PlainObjectBase & A, + Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & T); +} +#ifndef IGL_STATIC_LIBRARY +# include "polar_svd.cpp" +#endif +#endif diff --git a/src/igl/polar_svd3x3.cpp b/src/igl/polar_svd3x3.cpp new file mode 100644 index 000000000..c0d972aca --- /dev/null +++ b/src/igl/polar_svd3x3.cpp @@ -0,0 +1,113 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "polar_svd3x3.h" +#include "svd3x3.h" +#ifdef __SSE__ +# include "svd3x3_sse.h" +#endif +#ifdef __AVX__ +# include "svd3x3_avx.h" +#endif + +template +IGL_INLINE void igl::polar_svd3x3(const Mat& A, Mat& R) +{ + // should be caught at compile time, but just to be 150% sure: + assert(A.rows() == 3 && A.cols() == 3); + + Eigen::Matrix U, Vt; + Eigen::Matrix S; + svd3x3(A, U, S, Vt); + R = U * Vt.transpose(); +} + +#ifdef __SSE__ +template +IGL_INLINE void igl::polar_svd3x3_sse(const Eigen::Matrix& A, Eigen::Matrix &R) +{ + // should be caught at compile time, but just to be 150% sure: + assert(A.rows() == 3*4 && A.cols() == 3); + + Eigen::Matrix U, Vt; + Eigen::Matrix S; + svd3x3_sse(A, U, S, Vt); + + for (int k=0; k<4; k++) + { + R.block(3*k, 0, 3, 3) = U.block(3*k, 0, 3, 3) * Vt.block(3*k, 0, 3, 3).transpose(); + } + + //// test: + //for (int k=0; k<4; k++) + //{ + // Eigen::Matrix3f Apart = A.block(3*k, 0, 3, 3); + // Eigen::Matrix3f Rpart; + // polar_svd3x3(Apart, Rpart); + + // Eigen::Matrix3f Rpart_SSE = R.block(3*k, 0, 3, 3); + // Eigen::Matrix3f diff = Rpart - Rpart_SSE; + // float diffNorm = diff.norm(); + + // int hu = 1; + //} + //// eof test +} +#endif + +#ifdef __AVX__ +template +IGL_INLINE void igl::polar_svd3x3_avx(const Eigen::Matrix& A, Eigen::Matrix &R) +{ + // should be caught at compile time, but just to be 150% sure: + assert(A.rows() == 3*8 && A.cols() == 3); + + Eigen::Matrix U, Vt; + Eigen::Matrix S; + svd3x3_avx(A, U, S, Vt); + + for (int k=0; k<8; k++) + { + R.block(3*k, 0, 3, 3) = U.block(3*k, 0, 3, 3) * Vt.block(3*k, 0, 3, 3).transpose(); + } + + // test: + for (int k=0; k<8; k++) + { + Eigen::Matrix3f Apart = A.block(3*k, 0, 3, 3); + Eigen::Matrix3f Rpart; + polar_svd3x3(Apart, Rpart); + + Eigen::Matrix3f Rpart_SSE = R.block(3*k, 0, 3, 3); + Eigen::Matrix3f diff = Rpart - Rpart_SSE; + float diffNorm = diff.norm(); + if (std::abs(diffNorm) > 0.001) + { + printf("Huh: diffNorm = %15f (k = %i)\n", diffNorm, k); + } + + // Unused + //int hu = 1; + } + // eof test +} +#endif + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::polar_svd3x3 >(Eigen::Matrix const&, Eigen::Matrix&); +template void igl::polar_svd3x3 >(Eigen::Matrix const &,Eigen::Matrix &); + +#ifdef __SSE__ +template void igl::polar_svd3x3_sse(Eigen::Matrix const&, Eigen::Matrix&); +#endif + +#ifdef __AVX__ +template void igl::polar_svd3x3_avx(Eigen::Matrix const&, Eigen::Matrix&); +#endif + +#endif diff --git a/src/igl/polar_svd3x3.h b/src/igl/polar_svd3x3.h new file mode 100644 index 000000000..a579774e0 --- /dev/null +++ b/src/igl/polar_svd3x3.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POLAR_SVD3X3_H +#define IGL_POLAR_SVD3X3_H +#include +#include "igl_inline.h" +namespace igl +{ + // Computes the closest rotation to input matrix A using specialized 3x3 SVD + // singular value decomposition (WunderSVD3x3) + // + // Inputs: + // A 3 by 3 matrix to be decomposed + // Outputs: + // R 3 by 3 closest element in SO(3) (closeness in terms of Frobenius + // metric) + // + // This means that det(R) = 1. Technically it's not polar decomposition + // which guarantees positive semidefinite stretch factor (at the cost of + // having det(R) = -1). "• The orthogonal factors U and V will be true + // rotation matrices..." [McAdams, Selle, Tamstorf, Teran, Sefakis 2011] + // + template + IGL_INLINE void polar_svd3x3(const Mat& A, Mat& R); + #ifdef __SSE__ + template + IGL_INLINE void polar_svd3x3_sse(const Eigen::Matrix& A, Eigen::Matrix &R); + #endif + #ifdef __AVX__ + template + IGL_INLINE void polar_svd3x3_avx(const Eigen::Matrix& A, Eigen::Matrix &R); + #endif +} +#ifndef IGL_STATIC_LIBRARY +# include "polar_svd3x3.cpp" +#endif +#endif + diff --git a/src/igl/polygon_mesh_to_triangle_mesh.cpp b/src/igl/polygon_mesh_to_triangle_mesh.cpp new file mode 100644 index 000000000..2ea43238b --- /dev/null +++ b/src/igl/polygon_mesh_to_triangle_mesh.cpp @@ -0,0 +1,76 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "polygon_mesh_to_triangle_mesh.h" +#include "matrix_to_list.h" + +template +IGL_INLINE void igl::polygon_mesh_to_triangle_mesh( + const std::vector > & vF, + Eigen::PlainObjectBase& F) +{ + using namespace std; + using namespace Eigen; + int m = 0; + // estimate of size + for(typename vector >::const_iterator fit = vF.begin(); + fit!=vF.end(); + fit++) + { + if(fit->size() >= 3) + { + m += fit->size() - 2; + } + } + // Resize output + F.resize(m,3); + { + int k = 0; + for(typename vector >::const_iterator fit = vF.begin(); + fit!=vF.end(); + fit++) + { + if(fit->size() >= 3) + { + typename vector::const_iterator cit = fit->begin(); + cit++; + typename vector::const_iterator pit = cit++; + for(; + cit!=fit->end(); + cit++,pit++) + { + F(k,0) = *(fit->begin()); + F(k,1) = *pit; + F(k,2) = *cit; + k++; + } + } + } + assert(k==m); + } + +} + +template +IGL_INLINE void igl::polygon_mesh_to_triangle_mesh( + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& F) +{ + std::vector > vP; + matrix_to_list(P,vP); + return polygon_mesh_to_triangle_mesh(vP,F); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::polygon_mesh_to_triangle_mesh >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::polygon_mesh_to_triangle_mesh >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template void igl::polygon_mesh_to_triangle_mesh >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +template void igl::polygon_mesh_to_triangle_mesh >(std::vector >, std::allocator > > > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/polygon_mesh_to_triangle_mesh.h b/src/igl/polygon_mesh_to_triangle_mesh.h new file mode 100644 index 000000000..7d8f16e82 --- /dev/null +++ b/src/igl/polygon_mesh_to_triangle_mesh.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_POLYGON_MESH_TO_TRIANGLE_MESH_H +#define IGL_POLYGON_MESH_TO_TRIANGLE_MESH_H +#include "igl_inline.h" + +#ifndef IGL_NO_EIGEN +# include +#endif +#include + +namespace igl +{ + // Triangulate a general polygonal mesh into a triangle mesh. + // + // Inputs: + // vF list of polygon index lists + // Outputs: + // F eigen int matrix #F by 3 + // + // Example: + // vector > vV; + // vector > vF; + // read_triangle_mesh("poly.obj",vV,vF); + // MatrixXd V; + // MatrixXi F; + // list_to_matrix(vV,V); + // triangulate(vF,F); + template + IGL_INLINE void polygon_mesh_to_triangle_mesh( + const std::vector > & vF, + Eigen::PlainObjectBase& F); + template + IGL_INLINE void polygon_mesh_to_triangle_mesh( + const Eigen::PlainObjectBase& P, + Eigen::PlainObjectBase& F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "polygon_mesh_to_triangle_mesh.cpp" +#endif + +#endif diff --git a/src/igl/principal_curvature.cpp b/src/igl/principal_curvature.cpp new file mode 100644 index 000000000..5712130a0 --- /dev/null +++ b/src/igl/principal_curvature.cpp @@ -0,0 +1,854 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "principal_curvature.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Lib IGL includes +#include +#include +#include +#include +#include + +typedef enum +{ + SPHERE_SEARCH, + K_RING_SEARCH +} searchType; + +typedef enum +{ + AVERAGE, + PROJ_PLANE +} normalType; + +class CurvatureCalculator +{ +public: + /* Row number i represents the i-th vertex, whose columns are: + curv[i][0] : K2 + curv[i][1] : K1 + curvDir[i][0] : PD1 + curvDir[i][1] : PD2 + */ + std::vector< std::vector > curv; + std::vector< std::vector > curvDir; + bool curvatureComputed; + class Quadric + { + public: + + IGL_INLINE Quadric () + { + a() = b() = c() = d() = e() = 1.0; + } + + IGL_INLINE Quadric(double av, double bv, double cv, double dv, double ev) + { + a() = av; + b() = bv; + c() = cv; + d() = dv; + e() = ev; + } + + IGL_INLINE double& a() { return data[0];} + IGL_INLINE double& b() { return data[1];} + IGL_INLINE double& c() { return data[2];} + IGL_INLINE double& d() { return data[3];} + IGL_INLINE double& e() { return data[4];} + + double data[5]; + + IGL_INLINE double evaluate(double u, double v) + { + return a()*u*u + b()*u*v + c()*v*v + d()*u + e()*v; + } + + IGL_INLINE double du(double u, double v) + { + return 2.0*a()*u + b()*v + d(); + } + + IGL_INLINE double dv(double u, double v) + { + return 2.0*c()*v + b()*u + e(); + } + + IGL_INLINE double duv(double u, double v) + { + return b(); + } + + IGL_INLINE double duu(double u, double v) + { + return 2.0*a(); + } + + IGL_INLINE double dvv(double u, double v) + { + return 2.0*c(); + } + + + IGL_INLINE static Quadric fit(const std::vector &VV) + { + assert(VV.size() >= 5); + if (VV.size() < 5) + { + std::cerr << "ASSERT FAILED! fit function requires at least 5 points: Only " << VV.size() << " were given." << std::endl; + exit(0); + } + + Eigen::MatrixXd A(VV.size(),5); + Eigen::MatrixXd b(VV.size(),1); + Eigen::MatrixXd sol(5,1); + + for(unsigned int c=0; c < VV.size(); ++c) + { + double u = VV[c][0]; + double v = VV[c][1]; + double n = VV[c][2]; + + A(c,0) = u*u; + A(c,1) = u*v; + A(c,2) = v*v; + A(c,3) = u; + A(c,4) = v; + + b(c) = n; + } + + sol=A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); + + return Quadric(sol(0),sol(1),sol(2),sol(3),sol(4)); + } + }; + +public: + + Eigen::MatrixXd vertices; + // Face list of current mesh (#F x 3) or (#F x 4) + // The i-th row contains the indices of the vertices that forms the i-th face in ccw order + Eigen::MatrixXi faces; + + std::vector > vertex_to_vertices; + std::vector > vertex_to_faces; + std::vector > vertex_to_faces_index; + Eigen::MatrixXd face_normals; + Eigen::MatrixXd vertex_normals; + + /* Size of the neighborhood */ + double sphereRadius; + int kRing; + + bool localMode; /* Use local mode */ + bool projectionPlaneCheck; /* Check collected vertices on tangent plane */ + bool montecarlo; + unsigned int montecarloN; + + searchType st; /* Use either a sphere search or a k-ring search */ + normalType nt; + + double lastRadius; + double scaledRadius; + std::string lastMeshName; + + /* Benchmark related variables */ + bool expStep; /* True if we want the radius to increase exponentially */ + int step; /* If expStep==false, by how much rhe radius increases on every step */ + int maxSize; /* The maximum limit of the radius in the benchmark */ + + IGL_INLINE CurvatureCalculator(); + IGL_INLINE void init(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F); + + IGL_INLINE void finalEigenStuff(int, const std::vector&, Quadric&); + IGL_INLINE void fitQuadric(const Eigen::Vector3d&, const std::vector& ref, const std::vector& , Quadric *); + IGL_INLINE void applyProjOnPlane(const Eigen::Vector3d&, const std::vector&, std::vector&); + IGL_INLINE void getSphere(const int, const double, std::vector&, int min); + IGL_INLINE void getKRing(const int, const double,std::vector&); + IGL_INLINE Eigen::Vector3d project(const Eigen::Vector3d&, const Eigen::Vector3d&, const Eigen::Vector3d&); + IGL_INLINE void computeReferenceFrame(int, const Eigen::Vector3d&, std::vector&); + IGL_INLINE void getAverageNormal(int, const std::vector&, Eigen::Vector3d&); + IGL_INLINE void getProjPlane(int, const std::vector&, Eigen::Vector3d&); + IGL_INLINE void applyMontecarlo(const std::vector&,std::vector*); + IGL_INLINE void computeCurvature(); + IGL_INLINE void printCurvature(const std::string& outpath); + IGL_INLINE double getAverageEdge(); + + IGL_INLINE static int rotateForward (double *v0, double *v1, double *v2) + { + double t; + + if (std::abs(*v2) >= std::abs(*v1) && std::abs(*v2) >= std::abs(*v0)) + return 0; + + t = *v0; + *v0 = *v2; + *v2 = *v1; + *v1 = t; + + return 1 + rotateForward (v0, v1, v2); + } + + IGL_INLINE static void rotateBackward (int nr, double *v0, double *v1, double *v2) + { + double t; + + if (nr == 0) + return; + + t = *v2; + *v2 = *v0; + *v0 = *v1; + *v1 = t; + + rotateBackward (nr - 1, v0, v1, v2); + } + + IGL_INLINE static Eigen::Vector3d chooseMax (Eigen::Vector3d n, Eigen::Vector3d abc, double ab) + { + int max_i; + double max_sp; + Eigen::Vector3d nt[8]; + + n.normalize (); + abc.normalize (); + + max_sp = - std::numeric_limits::max(); + + for (int i = 0; i < 4; ++i) + { + nt[i] = n; + if (ab > 0) + { + switch (i) + { + case 0: + break; + + case 1: + nt[i][2] = -n[2]; + break; + + case 2: + nt[i][0] = -n[0]; + nt[i][1] = -n[1]; + break; + + case 3: + nt[i][0] = -n[0]; + nt[i][1] = -n[1]; + nt[i][2] = -n[2]; + break; + } + } + else + { + switch (i) + { + case 0: + nt[i][0] = -n[0]; + break; + + case 1: + nt[i][1] = -n[1]; + break; + + case 2: + nt[i][0] = -n[0]; + nt[i][2] = -n[2]; + break; + + case 3: + nt[i][1] = -n[1]; + nt[i][2] = -n[2]; + break; + } + } + + if (nt[i].dot(abc) > max_sp) + { + max_sp = nt[i].dot(abc); + max_i = i; + } + } + return nt[max_i]; + } + +}; + +class comparer +{ +public: + IGL_INLINE bool operator() (const std::pair& lhs, const std::pair&rhs) const + { + return lhs.second>rhs.second; + } +}; + +IGL_INLINE CurvatureCalculator::CurvatureCalculator() +{ + this->localMode=true; + this->projectionPlaneCheck=true; + this->sphereRadius=5; + this->st=SPHERE_SEARCH; + this->nt=AVERAGE; + this->montecarlo=false; + this->montecarloN=0; + this->kRing=3; + this->curvatureComputed=false; + this->expStep=true; +} + +IGL_INLINE void CurvatureCalculator::init(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) +{ + // Normalize vertices + vertices = V; + +// vertices = vertices.array() - vertices.minCoeff(); +// vertices = vertices.array() / vertices.maxCoeff(); +// vertices = vertices.array() * (1.0/igl::avg_edge_length(V,F)); + + faces = F; + igl::adjacency_list(F, vertex_to_vertices); + igl::vertex_triangle_adjacency(V, F, vertex_to_faces, vertex_to_faces_index); + igl::per_face_normals(V, F, face_normals); + igl::per_vertex_normals(V, F, face_normals, vertex_normals); +} + +IGL_INLINE void CurvatureCalculator::fitQuadric(const Eigen::Vector3d& v, const std::vector& ref, const std::vector& vv, Quadric *q) +{ + std::vector points; + points.reserve (vv.size()); + + for (unsigned int i = 0; i < vv.size(); ++i) { + + Eigen::Vector3d cp = vertices.row(vv[i]); + + // vtang non e` il v tangente!!! + Eigen::Vector3d vTang = cp - v; + + double x = vTang.dot(ref[0]); + double y = vTang.dot(ref[1]); + double z = vTang.dot(ref[2]); + points.push_back(Eigen::Vector3d (x,y,z)); + } + if (points.size() < 5) + { + std::cerr << "ASSERT FAILED! fit function requires at least 5 points: Only " << points.size() << " were given." << std::endl; + *q = Quadric(0,0,0,0,0); + } + else + { + *q = Quadric::fit (points); + } +} + +IGL_INLINE void CurvatureCalculator::finalEigenStuff(int i, const std::vector& ref, Quadric& q) +{ + + const double a = q.a(); + const double b = q.b(); + const double c = q.c(); + const double d = q.d(); + const double e = q.e(); + +// if (fabs(a) < 10e-8 || fabs(b) < 10e-8) +// { +// std::cout << "Degenerate quadric: " << i << std::endl; +// } + + double E = 1.0 + d*d; + double F = d*e; + double G = 1.0 + e*e; + + Eigen::Vector3d n = Eigen::Vector3d(-d,-e,1.0).normalized(); + + double L = 2.0 * a * n[2]; + double M = b * n[2]; + double N = 2 * c * n[2]; + + + // ----------------- Eigen stuff + Eigen::Matrix2d m; + m << L*G - M*F, M*E-L*F, M*E-L*F, N*E-M*F; + m = m / (E*G-F*F); + Eigen::SelfAdjointEigenSolver eig(m); + + Eigen::Vector2d c_val = eig.eigenvalues(); + Eigen::Matrix2d c_vec = eig.eigenvectors(); + + // std::cerr << "c_val:" << c_val << std::endl; + // std::cerr << "c_vec:" << c_vec << std::endl; + + // std::cerr << "c_vec:" << c_vec(0) << " " << c_vec(1) << std::endl; + + c_val = -c_val; + + Eigen::Vector3d v1, v2; + v1[0] = c_vec(0); + v1[1] = c_vec(1); + v1[2] = 0; //d * v1[0] + e * v1[1]; + + v2[0] = c_vec(2); + v2[1] = c_vec(3); + v2[2] = 0; //d * v2[0] + e * v2[1]; + + + // v1 = v1.normalized(); + // v2 = v2.normalized(); + + Eigen::Vector3d v1global = ref[0] * v1[0] + ref[1] * v1[1] + ref[2] * v1[2]; + Eigen::Vector3d v2global = ref[0] * v2[0] + ref[1] * v2[1] + ref[2] * v2[2]; + + v1global.normalize(); + v2global.normalize(); + + v1global *= c_val(0); + v2global *= c_val(1); + + if (c_val[0] > c_val[1]) + { + curv[i]=std::vector(2); + curv[i][0]=c_val(1); + curv[i][1]=c_val(0); + curvDir[i]=std::vector(2); + curvDir[i][0]=v2global; + curvDir[i][1]=v1global; + } + else + { + curv[i]=std::vector(2); + curv[i][0]=c_val(0); + curv[i][1]=c_val(1); + curvDir[i]=std::vector(2); + curvDir[i][0]=v1global; + curvDir[i][1]=v2global; + } + // ---- end Eigen stuff +} + +IGL_INLINE void CurvatureCalculator::getKRing(const int start, const double r, std::vector&vv) +{ + int bufsize=vertices.rows(); + vv.reserve(bufsize); + std::list > queue; + bool* visited = (bool*)calloc(bufsize,sizeof(bool)); + queue.push_back(std::pair(start,0)); + visited[start]=true; + while (!queue.empty()) + { + int toVisit=queue.front().first; + int distance=queue.front().second; + queue.pop_front(); + vv.push_back(toVisit); + if (distance<(int)r) + { + for (unsigned int i=0; i (neighbor,distance+1)); + visited[neighbor]=true; + } + } + } + } + free(visited); + return; +} + + +IGL_INLINE void CurvatureCalculator::getSphere(const int start, const double r, std::vector &vv, int min) +{ + int bufsize=vertices.rows(); + vv.reserve(bufsize); + std::list* queue= new std::list(); + bool* visited = (bool*)calloc(bufsize,sizeof(bool)); + queue->push_back(start); + visited[start]=true; + Eigen::Vector3d me=vertices.row(start); + std::priority_queue, std::vector >, comparer >* extra_candidates= new std::priority_queue, std::vector >, comparer >(); + while (!queue->empty()) + { + int toVisit=queue->front(); + queue->pop_front(); + vv.push_back(toVisit); + for (unsigned int i=0; ipush_back(neighbor); + else if ((int)vv.size()push(std::pair(neighbor,distance)); + visited[neighbor]=true; + } + } + } + while (!extra_candidates->empty() && (int)vv.size() cand=extra_candidates->top(); + extra_candidates->pop(); + vv.push_back(cand.first); + for (unsigned int i=0; ipush(std::pair(neighbor,distance)); + visited[neighbor]=true; + } + } + } + free(extra_candidates); + free(queue); + free(visited); +} + +IGL_INLINE Eigen::Vector3d CurvatureCalculator::project(const Eigen::Vector3d& v, const Eigen::Vector3d& vp, const Eigen::Vector3d& ppn) +{ + return (vp - (ppn * ((vp - v).dot(ppn)))); +} + +IGL_INLINE void CurvatureCalculator::computeReferenceFrame(int i, const Eigen::Vector3d& normal, std::vector& ref ) +{ + + Eigen::Vector3d longest_v=Eigen::Vector3d(vertices.row(vertex_to_vertices[i][0])); + + longest_v=(project(vertices.row(i),longest_v,normal)-Eigen::Vector3d(vertices.row(i))).normalized(); + + /* L'ultimo asse si ottiene come prodotto vettoriale tra i due + * calcolati */ + Eigen::Vector3d y_axis=(normal.cross(longest_v)).normalized(); + ref[0]=longest_v; + ref[1]=y_axis; + ref[2]=normal; +} + +IGL_INLINE void CurvatureCalculator::getAverageNormal(int j, const std::vector& vv, Eigen::Vector3d& normal) +{ + normal=(vertex_normals.row(j)).normalized(); + if (localMode) + return; + + for (unsigned int i=0; i& vv, Eigen::Vector3d& ppn) +{ + int nr; + double a, b, c; + double nx, ny, nz; + double abcq; + + a = b = c = 0; + + if (localMode) + { + for (unsigned int i=0; i& vin, std::vector &vout) +{ + for (std::vector::const_iterator vpi = vin.begin(); vpi != vin.end(); ++vpi) + if (vertex_normals.row(*vpi) * ppn > 0.0) + vout.push_back(*vpi); +} + +IGL_INLINE void CurvatureCalculator::applyMontecarlo(const std::vector& vin, std::vector *vout) +{ + if (montecarloN >= vin.size ()) + { + *vout = vin; + return; + } + + float p = ((float) montecarloN) / (float) vin.size(); + for (std::vector::const_iterator vpi = vin.begin(); vpi != vin.end(); ++vpi) + { + float r; + if ((r = ((float)rand () / RAND_MAX)) < p) + { + vout->push_back(*vpi); + } + } +} + +IGL_INLINE void CurvatureCalculator::computeCurvature() +{ + //CHECK che esista la mesh + const size_t vertices_count=vertices.rows(); + + if (vertices_count ==0) + return; + + curvDir=std::vector< std::vector >(vertices_count); + curv=std::vector >(vertices_count); + + + + scaledRadius=getAverageEdge()*sphereRadius; + + std::vector vv; + std::vector vvtmp; + Eigen::Vector3d normal; + + //double time_spent; + //double searchtime=0, ref_time=0, fit_time=0, final_time=0; + + for (size_t i=0; i= 6 && vvtmp.size() ref(3); + computeReferenceFrame(i,normal,ref); + + Quadric q; + fitQuadric (me, ref, vv, &q); + finalEigenStuff(i,ref,q); + } + + lastRadius=sphereRadius; + curvatureComputed=true; +} + +IGL_INLINE void CurvatureCalculator::printCurvature(const std::string& outpath) +{ + using namespace std; + if (!curvatureComputed) + return; + + std::ofstream of; + of.open(outpath.c_str()); + + if (!of) + { + fprintf(stderr, "Error: could not open output file %s\n", outpath.c_str()); + return; + } + + int vertices_count=vertices.rows(); + of << vertices_count << endl; + for (int i=0; i +IGL_INLINE void igl::principal_curvature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& PD1, + Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& PV1, + Eigen::PlainObjectBase& PV2, + unsigned radius, + bool useKring) +{ + if (radius < 2) + { + radius = 2; + std::cout << "WARNING: igl::principal_curvature needs a radius >= 2, fixing it to 2." << std::endl; + } + + // Preallocate memory + PD1.resize(V.rows(),3); + PD2.resize(V.rows(),3); + + // Preallocate memory + PV1.resize(V.rows(),1); + PV2.resize(V.rows(),1); + + // Precomputation + CurvatureCalculator cc; + cc.init(V.template cast(),F.template cast()); + cc.sphereRadius = radius; + + if (useKring) + { + cc.kRing = radius; + cc.st = K_RING_SEARCH; + } + + // Compute + cc.computeCurvature(); + + // Copy it back + for (unsigned i=0; i 10e-6) + { + std::cerr << "PRINCIPAL_CURVATURE: Something is wrong with vertex: " << i << std::endl; + PD1.row(i) *= 0; + PD2.row(i) *= 0; + } + } + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::principal_curvature, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, unsigned int, bool); +template void igl::principal_curvature, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, unsigned int, bool); +template void igl::principal_curvature, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, unsigned int, bool); +#endif diff --git a/src/igl/principal_curvature.h b/src/igl/principal_curvature.h new file mode 100644 index 000000000..958e58418 --- /dev/null +++ b/src/igl/principal_curvature.h @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PRINCIPAL_CURVATURE_H +#define IGL_PRINCIPAL_CURVATURE_H + + +#include +#include + +#include "igl_inline.h" +//#include +//#include + + + +namespace igl +{ + + // Compute the principal curvature directions and magnitude of the given triangle mesh + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // DerivedF derived from face indices matrix type: i.e. MatrixXi + // Inputs: + // V eigen matrix #V by 3 + // F #F by 3 list of mesh faces (must be triangles) + // radius controls the size of the neighbourhood used, 1 = average edge length + // + // Outputs: + // PD1 #V by 3 maximal curvature direction for each vertex. + // PD2 #V by 3 minimal curvature direction for each vertex. + // PV1 #V by 1 maximal curvature value for each vertex. + // PV2 #V by 1 minimal curvature value for each vertex. + // + // See also: average_onto_faces, average_onto_vertices + // + // This function has been developed by: Nikolas De Giorgis, Luigi Rocca and Enrico Puppo. + // The algorithm is based on: + // Efficient Multi-scale Curvature and Crease Estimation + // Daniele Panozzo, Enrico Puppo, Luigi Rocca + // GraVisMa, 2010 +template < + typename DerivedV, + typename DerivedF, + typename DerivedPD1, + typename DerivedPD2, + typename DerivedPV1, + typename DerivedPV2> +IGL_INLINE void principal_curvature( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& PD1, + Eigen::PlainObjectBase& PD2, + Eigen::PlainObjectBase& PV1, + Eigen::PlainObjectBase& PV2, + unsigned radius = 5, + bool useKring = true); +} + + +#ifndef IGL_STATIC_LIBRARY +#include "principal_curvature.cpp" +#endif + +#endif diff --git a/src/igl/print_ijv.cpp b/src/igl/print_ijv.cpp new file mode 100644 index 000000000..38b1dd22a --- /dev/null +++ b/src/igl/print_ijv.cpp @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "print_ijv.h" + +#include "find.h" +#include + +template +IGL_INLINE void igl::print_ijv( + const Eigen::SparseMatrix& X, + const int offset) +{ + Eigen::Matrix I; + Eigen::Matrix J; + Eigen::Matrix V; + igl::find(X,I,J,V); + // Concatenate I,J,V + Eigen::Matrix IJV(I.size(),3); + IJV.col(0) = I.cast(); + IJV.col(1) = J.cast(); + IJV.col(2) = V; + // Offset + if(offset != 0) + { + IJV.col(0).array() += offset; + IJV.col(1).array() += offset; + } + std::cout<(Eigen::SparseMatrix const&, int); +#endif diff --git a/src/igl/print_ijv.h b/src/igl/print_ijv.h new file mode 100644 index 000000000..e45c1a76c --- /dev/null +++ b/src/igl/print_ijv.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PRINT_IJV_H +#define IGL_PRINT_IJV_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Prints a 3 column matrix representing [I,J,V] = find(X). That is, each + // row is the row index, column index and value for each non zero entry. Each + // row is printed on a new line + // + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // X m by n matrix whose entries are to be sorted + // offset optional offset for I and J indices {0} + template + IGL_INLINE void print_ijv( + const Eigen::SparseMatrix& X, + const int offset=0); +} + +#ifndef IGL_STATIC_LIBRARY +# include "print_ijv.cpp" +#endif + +#endif diff --git a/src/igl/print_vector.cpp b/src/igl/print_vector.cpp new file mode 100644 index 000000000..b47753216 --- /dev/null +++ b/src/igl/print_vector.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "print_vector.h" +#include +#include + + +template +IGL_INLINE void igl::print_vector( std::vector& v) +{ + using namespace std; + for (int i=0; i +IGL_INLINE void igl::print_vector( std::vector< std::vector >& v) +{ + using namespace std; + for (int i=0; i +IGL_INLINE void igl::print_vector( std::vector< std::vector< std::vector > >& v) +{ + using namespace std; + for (int m=0; m +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PRINT_VECTOR_H +#define IGL_PRINT_VECTOR_H +#include "igl_inline.h" + +#include +namespace igl +{ + // Not clear what these are supposed to be doing. Currently they print + // vectors to standard error... + template + IGL_INLINE void print_vector( std::vector& v); + template + IGL_INLINE void print_vector( std::vector< std::vector >& v); + template + IGL_INLINE void print_vector(std::vector< std::vector< std::vector > >& v); +} + +#ifndef IGL_STATIC_LIBRARY +# include "print_vector.cpp" +#endif + +#endif diff --git a/src/igl/procrustes.cpp b/src/igl/procrustes.cpp new file mode 100644 index 000000000..659eea340 --- /dev/null +++ b/src/igl/procrustes.cpp @@ -0,0 +1,140 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "procrustes.h" +#include "polar_svd.h" +#include "polar_dec.h" + +template < + typename DerivedX, + typename DerivedY, + typename Scalar, + typename DerivedR, + typename DerivedT> +IGL_INLINE void igl::procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Scalar& scale, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& t) +{ + using namespace Eigen; + assert (X.rows() == Y.rows() && "Same number of points"); + assert(X.cols() == Y.cols() && "Points have same dimensions"); + + // Center data + const VectorXd Xmean = X.colwise().mean(); + const VectorXd Ymean = Y.colwise().mean(); + MatrixXd XC = X.rowwise() - Xmean.transpose(); + MatrixXd YC = Y.rowwise() - Ymean.transpose(); + + // Scale + scale = 1.; + if (includeScaling) + { + double scaleX = XC.norm() / XC.rows(); + double scaleY = YC.norm() / YC.rows(); + scale = scaleY/scaleX; + XC *= scale; + assert (std::abs(XC.norm() / XC.rows() - scaleY) < 1e-8); + } + + // Rotation + MatrixXd S = XC.transpose() * YC; + MatrixXd T; + if (includeReflections) + { + polar_dec(S,R,T); + }else + { + polar_svd(S,R,T); + } +// R.transposeInPlace(); + + // Translation + t = Ymean - scale*R.transpose()*Xmean; +} + + +template < + typename DerivedX, + typename DerivedY, + typename Scalar, + int DIM, + int TType> +IGL_INLINE void igl::procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Eigen::Transform& T) +{ + using namespace Eigen; + double scale; + MatrixXd R; + VectorXd t; + procrustes(X,Y,includeScaling,includeReflections,scale,R,t); + + // Combine + T = Translation(t) * R * Scaling(scale); +} + +template < + typename DerivedX, + typename DerivedY, + typename DerivedR, + typename DerivedT> +IGL_INLINE void igl::procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Eigen::PlainObjectBase& S, + Eigen::PlainObjectBase& t) +{ + double scale; + procrustes(X,Y,includeScaling,includeReflections,scale,S,t); + S *= scale; +} + +template < + typename DerivedX, + typename DerivedY, + typename DerivedR, + typename DerivedT> +IGL_INLINE void igl::procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& t) +{ + procrustes(X,Y,false,false,R,t); +} + +template < + typename DerivedX, + typename DerivedY, + typename Scalar, + typename DerivedT> +IGL_INLINE void igl::procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + Eigen::Rotation2D& R, + Eigen::PlainObjectBase& t) +{ + using namespace Eigen; + assert (X.cols() == 2 && Y.cols() == 2 && "Points must have dimension 2"); + Matrix2d Rmat; + procrustes(X,Y,false,false,Rmat,t); + R.fromRotationMatrix(Rmat); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::procrustes, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool, bool, double&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/procrustes.h b/src/igl/procrustes.h new file mode 100644 index 000000000..5ce00db49 --- /dev/null +++ b/src/igl/procrustes.h @@ -0,0 +1,137 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Stefan Brugger +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PROCRUSTES_H +#define IGL_PROCRUSTES_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Solve Procrustes problem in d dimensions. Given two point sets X,Y in R^d + // find best scale s, orthogonal R and translation t s.t. |s*X*R + t - Y|^2 + // is minimized. + // + // Templates: + // DerivedV point type + // Scalar scalar type + // DerivedR type of R + // DerivedT type of t + // Inputs: + // X #V by DIM first list of points + // Y #V by DIM second list of points + // includeScaling if scaling should be allowed + // includeReflections if R is allowed to be a reflection + // Outputs: + // scale scaling + // R orthogonal matrix + // t translation + // + // Example: + // MatrixXd X, Y; (containing 3d points as rows) + // double scale; + // MatrixXd R; + // VectorXd t; + // igl::procrustes(X,Y,true,false,scale,R,t); + // R *= scale; + // MatrixXd Xprime = (X * R).rowwise() + t.transpose(); + // + template < + typename DerivedX, + typename DerivedY, + typename Scalar, + typename DerivedR, + typename DerivedT> + IGL_INLINE void procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Scalar& scale, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& t); + // Same as above but returns Eigen transformation object. + // + // Templates: + // DerivedV point type + // Scalar scalar type + // DIM point dimension + // TType type of transformation + // (Isometry,Affine,AffineCompact,Projective) + // Inputs: + // X #V by DIM first list of points + // Y #V by DIM second list of points + // includeScaling if scaling should be allowed + // includeReflections if R is allowed to be a reflection + // Outputs: + // T transformation that minimizes error + // + // Example: + // MatrixXd X, Y; (containing 3d points as rows) + // AffineCompact3d T; + // igl::procrustes(X,Y,true,false,T); + // MatrixXd Xprime = (X * T.linear()).rowwise() + T.translation().transpose(); + template < + typename DerivedX, + typename DerivedY, + typename Scalar, + int DIM, + int TType> + IGL_INLINE void procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Eigen::Transform& T); + + + // Convenient wrapper that returns S=scale*R instead of scale and R separately + template < + typename DerivedX, + typename DerivedY, + typename DerivedR, + typename DerivedT> + IGL_INLINE void procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + bool includeScaling, + bool includeReflections, + Eigen::PlainObjectBase& S, + Eigen::PlainObjectBase& t); + + // Convenient wrapper for rigid case (no scaling, no reflections) + template < + typename DerivedX, + typename DerivedY, + typename DerivedR, + typename DerivedT> + IGL_INLINE void procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& R, + Eigen::PlainObjectBase& t); + + // Convenient wrapper for 2D case. + template < + typename DerivedX, + typename DerivedY, + typename Scalar, + typename DerivedT> + IGL_INLINE void procrustes( + const Eigen::PlainObjectBase& X, + const Eigen::PlainObjectBase& Y, + Eigen::Rotation2D& R, + Eigen::PlainObjectBase& t); +} + +#ifndef IGL_STATIC_LIBRARY + #include "procrustes.cpp" +#endif + +#endif diff --git a/src/igl/project.cpp b/src/igl/project.cpp new file mode 100644 index 000000000..e57004f3c --- /dev/null +++ b/src/igl/project.cpp @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "project.h" + +template +Eigen::Matrix igl::project( + const Eigen::Matrix& obj, + const Eigen::Matrix& model, + const Eigen::Matrix& proj, + const Eigen::Matrix& viewport) +{ + Eigen::Matrix tmp; + tmp << obj,1; + + tmp = model * tmp; + + tmp = proj * tmp; + + tmp = tmp.array() / tmp(3); + tmp = tmp.array() * 0.5f + 0.5f; + tmp(0) = tmp(0) * viewport(2) + viewport(0); + tmp(1) = tmp(1) * viewport(3) + viewport(1); + + return tmp.head(3); +} + +template +IGL_INLINE void igl::project( + const Eigen::PlainObjectBase& V, + const Eigen::MatrixBase& model, + const Eigen::MatrixBase& proj, + const Eigen::MatrixBase& viewport, + Eigen::PlainObjectBase & P) +{ + typedef typename DerivedP::Scalar PScalar; + Eigen::Matrix HV(V.rows(),4); + HV.leftCols(3) = V.template cast(); + HV.col(3).setConstant(1); + HV = (HV*model.template cast().transpose()* + proj.template cast().transpose()).eval(); + HV = (HV.array().colwise()/HV.col(3).array()).eval(); + HV = (HV.array() * 0.5 + 0.5).eval(); + HV.col(0) = (HV.array().col(0) * viewport(2) + viewport(0)).eval(); + HV.col(1) = (HV.array().col(1) * viewport(3) + viewport(1)).eval(); + P = HV.leftCols(3); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template Eigen::Matrix igl::project(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +template Eigen::Matrix igl::project(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +template void igl::project, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix>(const Eigen::PlainObjectBase>&, const Eigen::MatrixBase>&, const Eigen::MatrixBase>&, const Eigen::MatrixBase>&, Eigen::PlainObjectBase>&); +template void igl::project, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix>(const Eigen::PlainObjectBase>&, const Eigen::MatrixBase>&, const Eigen::MatrixBase>&, const Eigen::MatrixBase>&, Eigen::PlainObjectBase>&); +#endif diff --git a/src/igl/project.h b/src/igl/project.h new file mode 100644 index 000000000..e2ee27a15 --- /dev/null +++ b/src/igl/project.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PROJECT_H +#define IGL_PROJECT_H +#include "igl_inline.h" +#include +namespace igl +{ + // Eigen reimplementation of gluProject + // Inputs: + // obj* 3D objects' x, y, and z coordinates respectively + // model model matrix + // proj projection matrix + // viewport viewport vector + // Returns: + // screen space x, y, and z coordinates respectively + template + IGL_INLINE Eigen::Matrix project( + const Eigen::Matrix& obj, + const Eigen::Matrix& model, + const Eigen::Matrix& proj, + const Eigen::Matrix& viewport); + // Inputs: + // V #V by 3 list of object points + // model model matrix + // proj projection matrix + // viewport viewport vector + // Outputs: + // P #V by 3 list of screen space points + template + IGL_INLINE void project( + const Eigen::PlainObjectBase& V, + const Eigen::MatrixBase& model, + const Eigen::MatrixBase& proj, + const Eigen::MatrixBase& viewport, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "project.cpp" +#endif + +#endif diff --git a/src/igl/project_isometrically_to_plane.cpp b/src/igl/project_isometrically_to_plane.cpp new file mode 100644 index 000000000..537629caa --- /dev/null +++ b/src/igl/project_isometrically_to_plane.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "project_isometrically_to_plane.h" +#include "edge_lengths.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedU, + typename DerivedUF, + typename Scalar> +IGL_INLINE void igl::project_isometrically_to_plane( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & UF, + Eigen::SparseMatrix& I) +{ + using namespace std; + using namespace Eigen; + assert(F.cols() == 3 && "F should contain triangles"); + typedef DerivedV MatrixX; + MatrixX l; + edge_lengths(V,F,l); + // Number of faces + const int m = F.rows(); + + // First corner at origin + U = DerivedU::Zero(m*3,2); + // Second corner along x-axis + U.block(m,0,m,1) = l.col(2); + // Third corner rotated onto plane + U.block(m*2,0,m,1) = + (-l.col(0).array().square() + + l.col(1).array().square() + + l.col(2).array().square())/(2.*l.col(2).array()); + U.block(m*2,1,m,1) = + (l.col(1).array().square()-U.block(m*2,0,m,1).array().square()).sqrt(); + + typedef Triplet IJV; + vector ijv; + ijv.reserve(3*m); + UF.resize(m,3); + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/project_isometrically_to_plane.h b/src/igl/project_isometrically_to_plane.h new file mode 100644 index 000000000..388f6c307 --- /dev/null +++ b/src/igl/project_isometrically_to_plane.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PROJECT_ISOMETRICALLY_TO_PLANE_H +#define IGL_PROJECT_ISOMETRICALLY_TO_PLANE_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Project each triangle to the plane + // + // [U,UF,I] = project_isometrically_to_plane(V,F) + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of mesh indices + // Outputs: + // U #F*3 by 2 list of triangle positions + // UF #F by 3 list of mesh indices into U + // I #V by #F such that I(i,j) = 1 implies U(j,:) corresponds to V(i,:) + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedU, + typename DerivedUF, + typename Scalar> + IGL_INLINE void project_isometrically_to_plane( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & UF, + Eigen::SparseMatrix& I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "project_isometrically_to_plane.cpp" +#endif + +#endif + diff --git a/src/igl/project_to_line.cpp b/src/igl/project_to_line.cpp new file mode 100644 index 000000000..a7f3a690e --- /dev/null +++ b/src/igl/project_to_line.cpp @@ -0,0 +1,139 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "project_to_line.h" +#include +#include + +template < + typename DerivedP, + typename DerivedS, + typename DerivedD, + typename Derivedt, + typename DerivedsqrD> +IGL_INLINE void igl::project_to_line( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & S, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & t, + Eigen::PlainObjectBase & sqrD) +{ + // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html + + // number of dimensions +#ifndef NDEBUG + int dim = P.cols(); + assert(dim == S.size()); + assert(dim == D.size()); +#endif + // number of points + int np = P.rows(); + // vector from source to destination + DerivedD DmS = D-S; + double v_sqrlen = (double)(DmS.squaredNorm()); + assert(v_sqrlen != 0); + // resize output + t.resize(np,1); + sqrD.resize(np,1); + // loop over points +#pragma omp parallel for if (np>10000) + for(int i = 0;i +IGL_INLINE void igl::project_to_line( + const Scalar px, + const Scalar py, + const Scalar pz, + const Scalar sx, + const Scalar sy, + const Scalar sz, + const Scalar dx, + const Scalar dy, + const Scalar dz, + Scalar & projpx, + Scalar & projpy, + Scalar & projpz, + Scalar & t, + Scalar & sqrd) +{ + // vector from source to destination + Scalar dms[3]; + dms[0] = dx-sx; + dms[1] = dy-sy; + dms[2] = dz-sz; + Scalar v_sqrlen = dms[0]*dms[0] + dms[1]*dms[1] + dms[2]*dms[2]; + // line should have some length + assert(v_sqrlen != 0); + // vector from point to source + Scalar smp[3]; + smp[0] = sx-px; + smp[1] = sy-py; + smp[2] = sz-pz; + t = -(dms[0]*smp[0]+dms[1]*smp[1]+dms[2]*smp[2])/v_sqrlen; + // P projectred onto line + projpx = (1.0-t)*sx + t*dx; + projpy = (1.0-t)*sy + t*dy; + projpz = (1.0-t)*sz + t*dz; + // vector from projected point to p + Scalar pmprojp[3]; + pmprojp[0] = px-projpx; + pmprojp[1] = py-projpy; + pmprojp[2] = pz-projpz; + sqrd = pmprojp[0]*pmprojp[0] + pmprojp[1]*pmprojp[1] + pmprojp[2]*pmprojp[2]; +} + +template +IGL_INLINE void igl::project_to_line( + const Scalar px, + const Scalar py, + const Scalar pz, + const Scalar sx, + const Scalar sy, + const Scalar sz, + const Scalar dx, + const Scalar dy, + const Scalar dz, + Scalar & t, + Scalar & sqrd) +{ + Scalar projpx; + Scalar projpy; + Scalar projpz; + return igl::project_to_line( + px, py, pz, sx, sy, sz, dx, dy, dz, + projpx, projpy, projpz, t, sqrd); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line const, -1, -1, false>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase const, -1, -1, false> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line const, -1, -1, false>, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase const, -1, -1, false> > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::project_to_line(double, double, double, double, double, double, double, double, double, double&, double&); +template void igl::project_to_line(double, double, double, double, double, double, double, double, double, double&, double&,double&,double&, double&); +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::project_to_line, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/project_to_line.h b/src/igl/project_to_line.h new file mode 100644 index 000000000..d11399a76 --- /dev/null +++ b/src/igl/project_to_line.h @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PROJECT_TO_LINE_H +#define IGL_PROJECT_TO_LINE_H +#include "igl_inline.h" +#include + +namespace igl +{ + // PROJECT_TO_LINE project points onto vectors, that is find the parameter + // t for a point p such that proj_p = (y-x).*t, additionally compute the + // squared distance from p to the line of the vector, such that + // |p - proj_p|² = sqr_d + // + // [T,sqrD] = project_to_line(P,S,D) + // + // Inputs: + // P #P by dim list of points to be projected + // S size dim start position of line vector + // D size dim destination position of line vector + // Outputs: + // T #P by 1 list of parameters + // sqrD #P by 1 list of squared distances + // + // + template < + typename DerivedP, + typename DerivedS, + typename DerivedD, + typename Derivedt, + typename DerivedsqrD> + IGL_INLINE void project_to_line( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & S, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & t, + Eigen::PlainObjectBase & sqrD); + + // Same as above but for a single query point + template + IGL_INLINE void project_to_line( + const Scalar px, + const Scalar py, + const Scalar pz, + const Scalar sx, + const Scalar sy, + const Scalar sz, + const Scalar dx, + const Scalar dy, + const Scalar dz, + Scalar & projpx, + Scalar & projpy, + Scalar & projpz, + Scalar & t, + Scalar & sqrd); + + // Same as above but for a single query point + template + IGL_INLINE void project_to_line( + const Scalar px, + const Scalar py, + const Scalar pz, + const Scalar sx, + const Scalar sy, + const Scalar sz, + const Scalar dx, + const Scalar dy, + const Scalar dz, + Scalar & t, + Scalar & sqrd); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "project_to_line.cpp" +#endif + +#endif diff --git a/src/igl/project_to_line_segment.cpp b/src/igl/project_to_line_segment.cpp new file mode 100644 index 000000000..71866fa33 --- /dev/null +++ b/src/igl/project_to_line_segment.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "project_to_line_segment.h" +#include "project_to_line.h" +#include + +template < + typename DerivedP, + typename DerivedS, + typename DerivedD, + typename Derivedt, + typename DerivedsqrD> +IGL_INLINE void igl::project_to_line_segment( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & S, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & t, + Eigen::PlainObjectBase & sqrD) +{ + project_to_line(P,S,D,t,sqrD); + const int np = P.rows(); + // loop over points and fix those that projected beyond endpoints +#pragma omp parallel for if (np>10000) + for(int p = 0;p1) + { + sqrD(p) = (Pp-D).squaredNorm(); + t(p) = 1; + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::project_to_line_segment, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line_segment, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line_segment, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::project_to_line_segment, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::project_to_line_segment, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/project_to_line_segment.h b/src/igl/project_to_line_segment.h new file mode 100644 index 000000000..2fe33f2d4 --- /dev/null +++ b/src/igl/project_to_line_segment.h @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PROJECT_TO_LINE_SEGMENT_H +#define IGL_PROJECT_TO_LINE_SEGMENT_H +#include "igl_inline.h" +#include + +namespace igl +{ + // PROJECT_TO_LINE_SEGMENT project points onto vectors, that is find the parameter + // t for a point p such that proj_p = (y-x).*t, additionally compute the + // squared distance from p to the line of the vector, such that + // |p - proj_p|² = sqr_d + // + // [T,sqrD] = project_to_line_segment(P,S,D) + // + // Inputs: + // P #P by dim list of points to be projected + // S size dim start position of line vector + // D size dim destination position of line vector + // Outputs: + // T #P by 1 list of parameters + // sqrD #P by 1 list of squared distances + // + // + template < + typename DerivedP, + typename DerivedS, + typename DerivedD, + typename Derivedt, + typename DerivedsqrD> + IGL_INLINE void project_to_line_segment( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & S, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & t, + Eigen::PlainObjectBase & sqrD); +} + +#ifndef IGL_STATIC_LIBRARY +# include "project_to_line_segment.cpp" +#endif + +#endif + diff --git a/src/igl/pseudonormal_test.cpp b/src/igl/pseudonormal_test.cpp new file mode 100644 index 000000000..b56fdf7ba --- /dev/null +++ b/src/igl/pseudonormal_test.cpp @@ -0,0 +1,224 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "pseudonormal_test.h" +#include "barycentric_coordinates.h" +#include "doublearea.h" +#include "project_to_line_segment.h" +#include +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename Derivedq, + typename Derivedc, + typename Scalar, + typename Derivedn> +IGL_INLINE void igl::pseudonormal_test( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q, + const int f, + Eigen::PlainObjectBase & c, + Scalar & s, + Eigen::PlainObjectBase & n) +{ + using namespace Eigen; + const auto & qc = q-c; + typedef Eigen::Matrix RowVector3S; + RowVector3S b; + // Using barycentric coorindates to determine whether close to a vertex/edge + // seems prone to error when dealing with nearly degenerate triangles: Even + // the barycenter (1/3,1/3,1/3) can be made arbitrarily close to an + // edge/vertex + // + const RowVector3S A = V.row(F(f,0)); + const RowVector3S B = V.row(F(f,1)); + const RowVector3S C = V.row(F(f,2)); + + const double area = [&A,&B,&C]() + { + Matrix area; + doublearea(A,B,C,area); + return area(0); + }(); + // These were chosen arbitrarily. In a floating point scenario, I'm not sure + // the best way to determine if c is on a vertex/edge or in the middle of the + // face: specifically, I'm worrying about degenerate triangles where + // barycentric coordinates are error-prone. + const double MIN_DOUBLE_AREA = 1e-4; + const double epsilon = 1e-12; + if(area>MIN_DOUBLE_AREA) + { + barycentric_coordinates( c,A,B,C,b); + // Determine which normal to use + const int type = (b.array()<=epsilon).template cast().sum(); + switch(type) + { + case 2: + // Find vertex + for(int x = 0;x<3;x++) + { + if(b(x)>epsilon) + { + n = VN.row(F(f,x)); + break; + } + } + break; + case 1: + // Find edge + for(int x = 0;x<3;x++) + { + if(b(x)<=epsilon) + { + n = EN.row(EMAP(F.rows()*x+f)); + break; + } + } + break; + default: + assert(false && "all barycentric coords zero."); + case 0: + n = FN.row(f); + break; + } + }else + { + // Check each vertex + bool found = false; + for(int v = 0;v<3 && !found;v++) + { + if( (c-V.row(F(f,v))).norm() < epsilon) + { + found = true; + n = VN.row(F(f,v)); + } + } + // Check each edge + for(int e = 0;e<3 && !found;e++) + { + const RowVector3S s = V.row(F(f,(e+1)%3)); + const RowVector3S d = V.row(F(f,(e+2)%3)); + Matrix sqr_d_j_x(1,1); + Matrix t(1,1); + project_to_line_segment(c,s,d,t,sqr_d_j_x); + if(sqrt(sqr_d_j_x(0)) < epsilon) + { + n = EN.row(EMAP(F.rows()*e+f)); + found = true; + } + } + // Finally just use face + if(!found) + { + n = FN.row(f); + } + } + s = (qc.dot(n) >= 0 ? 1. : -1.); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedEN, + typename DerivedVN, + typename Derivedq, + typename Derivedc, + typename Scalar, + typename Derivedn> +IGL_INLINE void igl::pseudonormal_test( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & q, + const int e, + Eigen::PlainObjectBase & c, + Scalar & s, + Eigen::PlainObjectBase & n) +{ + using namespace Eigen; + const auto & qc = q-c; + const double len = (V.row(E(e,1))-V.row(E(e,0))).norm(); + // barycentric coordinates + // this .head() nonsense is for "ridiculus" templates instantiations that AABB + // needs to compile + Eigen::Matrix + b((c-V.row(E(e,1))).norm()/len,(c-V.row(E(e,0))).norm()/len); + //b((c-V.row(E(e,1)).head(c.size())).norm()/len,(c-V.row(E(e,0)).head(c.size())).norm()/len); + // Determine which normal to use + const double epsilon = 1e-12; + const int type = (b.array()<=epsilon).template cast().sum(); + switch(type) + { + case 1: + // Find vertex + for(int x = 0;x<2;x++) + { + if(b(x)>epsilon) + { + n = VN.row(E(e,x)).head(2); + break; + } + } + break; + default: + assert(false && "all barycentric coords zero."); + case 0: + n = EN.row(e).head(2); + break; + } + s = (qc.dot(n) >= 0 ? 1. : -1.); +} + +// This is a bullshit template because AABB annoyingly needs templates for bad +// combinations of 3D V with DIM=2 AABB +// +// _Define_ as a no-op rather than monkeying around with the proper code above +namespace igl +{ + template <> IGL_INLINE void pseudonormal_test(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&){assert(false);}; + template <> IGL_INLINE void pseudonormal_test(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&){assert(false);}; + template <> IGL_INLINE void pseudonormal_test(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&){assert(false);}; + template <> IGL_INLINE void pseudonormal_test(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&){assert(false);}; + template <> IGL_INLINE void pseudonormal_test(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&) {assert(false);}; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +// generated by autoexplicit.sh +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&); +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Block, 1, -1, false>, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase, 1, -1, false> > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +//template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&); +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, float&, Eigen::PlainObjectBase >&); +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +template void igl::pseudonormal_test, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, double&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/pseudonormal_test.h b/src/igl/pseudonormal_test.h new file mode 100644 index 000000000..fc07946fb --- /dev/null +++ b/src/igl/pseudonormal_test.h @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_PSEUDONORMAL_TEST_H +#define IGL_PSEUDONORMAL_TEST_H +#include "igl_inline.h" +#include +namespace igl +{ + // Given a mesh (V,F), a query point q, and a point on (V,F) c, determine + // whether q is inside (V,F) --> s=-1 or outside (V,F) s=1, based on the + // sign of the dot product between (q-c) and n, where n is the normal _at c_, + // carefully chosen according to [Bærentzen & Aanæs 2005] + // + // Inputs: + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // FN #F by 3 list of triangle normals + // VN #V by 3 list of vertex normals (ANGLE WEIGHTING) + // EN #E by 3 list of edge normals (UNIFORM WEIGHTING) + // EMAP #F*3 mapping edges in F to E + // q Query point + // f index into F to face to which c belongs + // c Point on (V,F) + // Outputs: + // s sign + // n normal + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename Derivedq, + typename Derivedc, + typename Scalar, + typename Derivedn> + IGL_INLINE void pseudonormal_test( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q, + const int f, + Eigen::PlainObjectBase & c, + Scalar & s, + Eigen::PlainObjectBase & n); + template < + typename DerivedV, + typename DerivedF, + typename DerivedEN, + typename DerivedVN, + typename Derivedq, + typename Derivedc, + typename Scalar, + typename Derivedn> + IGL_INLINE void pseudonormal_test( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & q, + const int e, + Eigen::PlainObjectBase & c, + Scalar & s, + Eigen::PlainObjectBase & n); +} +#ifndef IGL_STATIC_LIBRARY +# include "pseudonormal_test.cpp" +#endif +#endif diff --git a/src/igl/pso.cpp b/src/igl/pso.cpp new file mode 100644 index 000000000..bf24a3a8c --- /dev/null +++ b/src/igl/pso.cpp @@ -0,0 +1,174 @@ +#include "pso.h" +#include +#include +#include +#include + +template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB> +IGL_INLINE Scalar igl::pso( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const int max_iters, + const int population, + DerivedX & X) +{ + const Eigen::Array P = + Eigen::Array::Zero(LB.size(),1); + return igl::pso(f,LB,UB,P,max_iters,population,X); +} + +template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB, + typename DerivedP> +IGL_INLINE Scalar igl::pso( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const Eigen::DenseBase & P, + const int max_iters, + const int population, + DerivedX & X) +{ + const int dim = LB.size(); + assert(UB.size() == dim && "UB should match LB size"); + assert(P.size() == dim && "P should match LB size"); + typedef std::vector > VectorList; + VectorList position(population); + VectorList best_position(population); + VectorList velocity(population); + Eigen::Matrix best_f(population); + // https://en.wikipedia.org/wiki/Particle_swarm_optimization#Algorithm + // + // g → X + // p_i → best[i] + // v_i → velocity[i] + // x_i → position[i] + Scalar min_f = std::numeric_limits::max(); + for(int p=0;p UB(d)) + { + position[p](d) = UB(d); + if(velocity[p](d) > 0.0) velocity[p](d) *= -1.0; + } +#else +//#warning "trying no bounds on periodic" +// // TODO: I'm not sure this is the right thing to do/enough. The +// // velocities could be weird. Suppose the current "best" value is ε and +// // the value is -ε and the "periodic bounds" [0,2π]. Moding will send +// // the value to 2π-ε but the "velocity" term will now be huge pointing +// // all the way from 2π-ε to ε. +// // +// // Q: Would it be enough to try (all combinations) of ±(UB-LB) before +// // computing velocities to "best"s? In the example above, instead of +// // +// // v += best - p = ε - (2π-ε) = -2π+2ε +// // +// // you'd use +// // +// // v += / argmin |b - p| \ - p = (ε+2π)-(2π-ε) = 2ε +// // | | +// // \ b∈{best, best+2π, best-2π} / +// // +// // Though, for multivariate b,p,v this would seem to explode +// // combinatorially. +// // +// // Maybe periodic things just shouldn't be bounded and we hope that the +// // forces toward the current minima "regularize" them away from insane +// // values. +// if(P(d)) +// { +// position[p](d) = std::fmod(position[p](d)-LB(d),UB(d)-LB(d))+LB(d); +// }else +// { +// position[p](d) = std::max(LB(d),std::min(UB(d),position[p](d))); +// } + position[p](d) = std::max(LB(d),std::min(UB(d),position[p](d))); +#endif + } + const Scalar fp = f(position[p]); + if(fp=max_iters) + { + break; + } + } + return min_f; +} + +#ifdef IGL_STATIC_LIBRARY +template float igl::pso, Eigen::Matrix, Eigen::Matrix >(std::function&)>, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, int, Eigen::Matrix&); +#endif diff --git a/src/igl/pso.h b/src/igl/pso.h new file mode 100644 index 000000000..d2a2ccdff --- /dev/null +++ b/src/igl/pso.h @@ -0,0 +1,59 @@ +#ifndef IGL_PSO_H +#define IGL_PSO_H +#include +#include +#include + +namespace igl +{ + // Solve the problem: + // + // minimize f(x) + // subject to lb ≤ x ≤ ub + // + // by particle swarm optimization (PSO). + // + // Inputs: + // f function that evaluates the objective for a given "particle" location + // LB #X vector of lower bounds + // UB #X vector of upper bounds + // max_iters maximum number of iterations + // population number of particles in swarm + // Outputs: + // X best particle seen so far + // Returns objective corresponding to best particle seen so far + template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB> + IGL_INLINE Scalar pso( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const int max_iters, + const int population, + DerivedX & X); + // Inputs: + // P whether each DOF is periodic + template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB, + typename DerivedP> + IGL_INLINE Scalar pso( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const Eigen::DenseBase & P, + const int max_iters, + const int population, + DerivedX & X); +} + +#ifndef IGL_STATIC_LIBRARY +# include "pso.cpp" +#endif + +#endif diff --git a/src/igl/qslim.cpp b/src/igl/qslim.cpp new file mode 100644 index 000000000..e76db5f0b --- /dev/null +++ b/src/igl/qslim.cpp @@ -0,0 +1,120 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "qslim.h" + +#include "collapse_edge.h" +#include "connect_boundary_to_infinity.h" +#include "decimate.h" +#include "edge_flaps.h" +#include "is_edge_manifold.h" +#include "max_faces_stopping_condition.h" +#include "per_vertex_point_to_plane_quadrics.h" +#include "qslim_optimal_collapse_edge_callbacks.h" +#include "quadric_binary_plus_operator.h" +#include "remove_unreferenced.h" +#include "slice.h" +#include "slice_mask.h" + +IGL_INLINE bool igl::qslim( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I) +{ + using namespace igl; + + // Original number of faces + const int orig_m = F.rows(); + // Tracking number of faces + int m = F.rows(); + typedef Eigen::MatrixXd DerivedV; + typedef Eigen::MatrixXi DerivedF; + DerivedV VO; + DerivedF FO; + igl::connect_boundary_to_infinity(V,F,VO,FO); + // decimate will not work correctly on non-edge-manifold meshes. By extension + // this includes meshes with non-manifold vertices on the boundary since these + // will create a non-manifold edge when connected to infinity. + if(!is_edge_manifold(FO)) + { + return false; + } + Eigen::VectorXi EMAP; + Eigen::MatrixXi E,EF,EI; + edge_flaps(FO,E,EMAP,EF,EI); + // Quadrics per vertex + typedef std::tuple Quadric; + std::vector quadrics; + per_vertex_point_to_plane_quadrics(VO,FO,EMAP,EF,EI,quadrics); + // State variables keeping track of edge we just collapsed + int v1 = -1; + int v2 = -1; + // Callbacks for computing and updating metric + std::function cost_and_placement; + std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> pre_collapse; + std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> post_collapse; + qslim_optimal_collapse_edge_callbacks( + E,quadrics,v1,v2, cost_and_placement, pre_collapse,post_collapse); + // Call to greedy decimator + bool ret = decimate( + VO, FO, + cost_and_placement, + max_faces_stopping_condition(m,orig_m,max_m), + pre_collapse, + post_collapse, + E, EMAP, EF, EI, + U, G, J, I); + // Remove phony boundary faces and clean up + const Eigen::Array keep = (J.array() +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QSLIM_H +#define IGL_QSLIM_H +#include "igl_inline.h" +#include +namespace igl +{ + + // Decimate (simplify) a triangle mesh in nD according to the paper + // "Simplifying Surfaces with Color and Texture using Quadric Error Metrics" + // by [Garland and Heckbert, 1987] (technically a followup to qslim). The + // mesh can have open boundaries but should be edge-manifold. + // + // Inputs: + // V #V by dim list of vertex positions. Assumes that vertices w + // F #F by 3 list of triangle indices into V + // max_m desired number of output faces + // Outputs: + // U #U by dim list of output vertex posistions (can be same ref as V) + // G #G by 3 list of output face indices into U (can be same ref as G) + // J #G list of indices into F of birth face + // I #U list of indices into V of birth vertices + IGL_INLINE bool qslim( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const size_t max_m, + Eigen::MatrixXd & U, + Eigen::MatrixXi & G, + Eigen::VectorXi & J, + Eigen::VectorXi & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "qslim.cpp" +#endif +#endif diff --git a/src/igl/qslim_optimal_collapse_edge_callbacks.cpp b/src/igl/qslim_optimal_collapse_edge_callbacks.cpp new file mode 100644 index 000000000..ff3fbd045 --- /dev/null +++ b/src/igl/qslim_optimal_collapse_edge_callbacks.cpp @@ -0,0 +1,130 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "qslim_optimal_collapse_edge_callbacks.h" +#include "quadric_binary_plus_operator.h" +#include + +IGL_INLINE void igl::qslim_optimal_collapse_edge_callbacks( + Eigen::MatrixXi & E, + std::vector > & + quadrics, + int & v1, + int & v2, + std::function & cost_and_placement, + std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse) +{ + typedef std::tuple Quadric; + cost_and_placement = [&quadrics,&v1,&v2]( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & /*F*/, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & /*EMAP*/, + const Eigen::MatrixXi & /*EF*/, + const Eigen::MatrixXi & /*EI*/, + double & cost, + Eigen::RowVectorXd & p) + { + // Combined quadric + Quadric quadric_p; + quadric_p = quadrics[E(e,0)] + quadrics[E(e,1)]; + // Quadric: p'Ap + 2b'p + c + // optimal point: Ap = -b, or rather because we have row vectors: pA=-b + const auto & A = std::get<0>(quadric_p); + const auto & b = std::get<1>(quadric_p); + const auto & c = std::get<2>(quadric_p); + p = -b*A.inverse(); + cost = p.dot(p*A) + 2*p.dot(b) + c; + // Force infs and nans to infinity + if(std::isinf(cost) || cost!=cost) + { + cost = std::numeric_limits::infinity(); + // Prevent NaNs. Actually NaNs might be useful for debugging. + p.setConstant(0); + } + }; + // Remember endpoints + pre_collapse = [&v1,&v2]( + const Eigen::MatrixXd & ,/*V*/ + const Eigen::MatrixXi & ,/*F*/ + const Eigen::MatrixXi & E, + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & ,/*EF*/ + const Eigen::MatrixXi & ,/*EI*/ + const std::set > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int e)->bool + { + v1 = E(e,0); + v2 = E(e,1); + return true; + }; + // update quadric + post_collapse = [&v1,&v2,&quadrics]( + const Eigen::MatrixXd & , /*V*/ + const Eigen::MatrixXi & , /*F*/ + const Eigen::MatrixXi & , /*E*/ + const Eigen::VectorXi & ,/*EMAP*/ + const Eigen::MatrixXi & , /*EF*/ + const Eigen::MatrixXi & , /*EI*/ + const std::set > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool collapsed + )->void + { + if(collapsed) + { + quadrics[v1 +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QSLIM_OPTIMAL_COLLAPSE_EDGE_CALLBACKS_H +#define IGL_QSLIM_OPTIMAL_COLLAPSE_EDGE_CALLBACKS_H +#include "igl_inline.h" +#include +#include +#include +#include +#include +namespace igl +{ + + // Prepare callbacks for decimating edges using the qslim optimal placement + // metric. + // + // Inputs: + // E #E by 2 list of working edges + // quadrics reference to list of working per vertex quadrics + // v1 working variable to maintain end point of collapsed edge + // v2 working variable to maintain end point of collapsed edge + // Outputs + // cost_and_placement callback for evaluating cost of edge collapse and + // determining placement of vertex (see collapse_edge) + // pre_collapse callback before edge collapse (see collapse_edge) + // post_collapse callback after edge collapse (see collapse_edge) + IGL_INLINE void qslim_optimal_collapse_edge_callbacks( + Eigen::MatrixXi & E, + std::vector > & + quadrics, + int & v1, + int & v2, + std::function & cost_and_placement, + std::function > & ,/*Q*/ + const std::vector >::iterator > &,/*Qit*/ + const Eigen::MatrixXd & ,/*C*/ + const int /*e*/ + )> & pre_collapse, + std::function > & , /*Q*/ + const std::vector >::iterator > &, /*Qit*/ + const Eigen::MatrixXd & , /*C*/ + const int , /*e*/ + const int , /*e1*/ + const int , /*e2*/ + const int , /*f1*/ + const int , /*f2*/ + const bool /*collapsed*/ + )> & post_collapse); +} +#ifndef IGL_STATIC_LIBRARY +# include "qslim_optimal_collapse_edge_callbacks.cpp" +#endif +#endif diff --git a/src/igl/quad_planarity.cpp b/src/igl/quad_planarity.cpp new file mode 100644 index 000000000..2e0e4773e --- /dev/null +++ b/src/igl/quad_planarity.cpp @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quad_planarity.h" +#include + +template +IGL_INLINE void igl::quad_planarity( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & P) +{ + int nf = F.rows(); + P.setZero(nf,1); + for (int i =0; i &v1 = V.row(F(i,0)); + const Eigen::Matrix &v2 = V.row(F(i,1)); + const Eigen::Matrix &v3 = V.row(F(i,2)); + const Eigen::Matrix &v4 = V.row(F(i,3)); + Eigen::Matrix diagCross=(v3-v1).cross(v4-v2); + typename DerivedV::Scalar denom = + diagCross.norm()*(((v3-v1).norm()+(v4-v2).norm())/2); + if (fabs(denom)<1e-8) + //degenerate quad is still planar + P[i] = 0; + else + P[i] = (diagCross.dot(v2-v1)/denom); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::quad_planarity, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/quad_planarity.h b/src/igl/quad_planarity.h new file mode 100644 index 000000000..0d78b4e35 --- /dev/null +++ b/src/igl/quad_planarity.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUAD_PLANARITY_H +#define IGL_QUAD_PLANARITY_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute planarity of the faces of a quad mesh + // Inputs: + // V #V by 3 eigen Matrix of mesh vertex 3D positions + // F #F by 4 eigen Matrix of face (quad) indices + // Output: + // P #F by 1 eigen Matrix of mesh face (quad) planarities + // + template + IGL_INLINE void quad_planarity( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "quad_planarity.cpp" +#endif + +#endif diff --git a/src/igl/quadric_binary_plus_operator.cpp b/src/igl/quadric_binary_plus_operator.cpp new file mode 100644 index 000000000..eb3fd695e --- /dev/null +++ b/src/igl/quadric_binary_plus_operator.cpp @@ -0,0 +1,24 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quadric_binary_plus_operator.h" + +IGL_INLINE std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> + igl::operator+( + const std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> & a, + const std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> & b) +{ + std::tuple< + Eigen::MatrixXd, + Eigen::RowVectorXd, + double> c; + std::get<0>(c) = (std::get<0>(a) + std::get<0>(b)).eval(); + std::get<1>(c) = (std::get<1>(a) + std::get<1>(b)).eval(); + std::get<2>(c) = (std::get<2>(a) + std::get<2>(b)); + return c; +} + diff --git a/src/igl/quadric_binary_plus_operator.h b/src/igl/quadric_binary_plus_operator.h new file mode 100644 index 000000000..3c6b845d8 --- /dev/null +++ b/src/igl/quadric_binary_plus_operator.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUADRIC_BINARY_PLUS_OPERATOR_H +#define IGL_QUADRIC_BINARY_PLUS_OPERATOR_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // A binary addition operator for Quadric tuples compatible with qslim, + // computing c = a+b + // + // Inputs: + // a QSlim quadric + // b QSlim quadric + // Output + // c QSlim quadric + // + IGL_INLINE std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> + operator+( + const std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> & a, + const std::tuple< Eigen::MatrixXd, Eigen::RowVectorXd, double> & b); +} + +#ifndef IGL_STATIC_LIBRARY +# include "quadric_binary_plus_operator.cpp" +#endif + +#endif diff --git a/src/igl/quat_conjugate.cpp b/src/igl/quat_conjugate.cpp new file mode 100644 index 000000000..5856ea72d --- /dev/null +++ b/src/igl/quat_conjugate.cpp @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quat_conjugate.h" + +template +IGL_INLINE void igl::quat_conjugate( + const Q_type *q1, + Q_type *out) +{ + out[0] = -q1[0]; + out[1] = -q1[1]; + out[2] = -q1[2]; + out[3] = q1[3]; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::quat_conjugate(double const*, double*); +// generated by autoexplicit.sh +template void igl::quat_conjugate(float const*, float*); +#endif diff --git a/src/igl/quat_conjugate.h b/src/igl/quat_conjugate.h new file mode 100644 index 000000000..32ccb3b5b --- /dev/null +++ b/src/igl/quat_conjugate.h @@ -0,0 +1,32 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUAT_CONJUGATE_H +#define IGL_QUAT_CONJUGATE_H +#include "igl_inline.h" + +namespace igl +{ + // Compute conjugate of given quaternion + // http://en.wikipedia.org/wiki/Quaternion#Conjugation.2C_the_norm.2C_and_reciprocal + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // q1 input quaternion + // Outputs: + // out result of conjugation, allowed to be same as input + template + IGL_INLINE void quat_conjugate( + const Q_type *q1, + Q_type *out); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "quat_conjugate.cpp" +#endif + +#endif diff --git a/src/igl/quat_mult.cpp b/src/igl/quat_mult.cpp new file mode 100644 index 000000000..beb7baba5 --- /dev/null +++ b/src/igl/quat_mult.cpp @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quat_mult.h" + +#include +// http://www.antisphere.com/Wiki/tools:anttweakbar +template +IGL_INLINE void igl::quat_mult( + const Q_type *q1, + const Q_type *q2, + Q_type *out) +{ + // output can't be either of the inputs + assert(q1 != out); + assert(q2 != out); + + out[0] = q1[3]*q2[0] + q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1]; + out[1] = q1[3]*q2[1] + q1[1]*q2[3] + q1[2]*q2[0] - q1[0]*q2[2]; + out[2] = q1[3]*q2[2] + q1[2]*q2[3] + q1[0]*q2[1] - q1[1]*q2[0]; + out[3] = q1[3]*q2[3] - (q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2]); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::quat_mult(double const*, double const*, double*); +// generated by autoexplicit.sh +template void igl::quat_mult(float const*, float const*, float*); +#endif diff --git a/src/igl/quat_mult.h b/src/igl/quat_mult.h new file mode 100644 index 000000000..4a7ba654f --- /dev/null +++ b/src/igl/quat_mult.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUAT_MULT_H +#define IGL_QUAT_MULT_H +#include "igl_inline.h" + +namespace igl +{ + // Computes out = q1 * q2 with quaternion multiplication + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // q1 left quaternion + // q2 right quaternion + // Outputs: + // out result of multiplication + template + IGL_INLINE void quat_mult( + const Q_type *q1, + const Q_type *q2, + Q_type *out); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "quat_mult.cpp" +#endif + +#endif diff --git a/src/igl/quat_to_axis_angle.cpp b/src/igl/quat_to_axis_angle.cpp new file mode 100644 index 000000000..1aa59fad6 --- /dev/null +++ b/src/igl/quat_to_axis_angle.cpp @@ -0,0 +1,75 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quat_to_axis_angle.h" +#include "EPS.h" +#include "PI.h" +#include +#include +// +// http://www.antisphere.com/Wiki/tools:anttweakbar +template +IGL_INLINE void igl::quat_to_axis_angle( + const Q_type *q, + Q_type *axis, + Q_type & angle) +{ + if( fabs(q[3])>(1.0 + igl::EPS()) ) + { + //axis[0] = axis[1] = axis[2] = 0; // no, keep the previous value + angle = 0; + } + else + { + double a; + if( q[3]>=1.0f ) + a = 0; // and keep V + else if( q[3]<=-1.0f ) + a = PI; // and keep V + else if( fabs(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3])()) + { + a = 0; + }else + { + a = acos(q[3]); + if( a*angle<0 ) // Preserve the sign of angle + a = -a; + double f = 1.0f / sin(a); + axis[0] = q[0] * f; + axis[1] = q[1] * f; + axis[2] = q[2] * f; + } + angle = 2.0*a; + } + + // if( angle>FLOAT_PI ) + // angle -= 2.0f*FLOAT_PI; + // else if( angle<-FLOAT_PI ) + // angle += 2.0f*FLOAT_PI; + //angle = RadToDeg(angle); + + if( fabs(angle)()&& fabs(axis[0]*axis[0]+axis[1]*axis[1]+axis[2]*axis[2])()) + { + axis[0] = 1.0e-7; // all components cannot be null + } +} + +template +IGL_INLINE void igl::quat_to_axis_angle_deg( + const Q_type *q, + Q_type *axis, + Q_type & angle) +{ + igl::quat_to_axis_angle(q,axis,angle); + angle = angle*(180.0/PI); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::quat_to_axis_angle(float const*, float*, float&); +template void igl::quat_to_axis_angle_deg(float const*, float*, float&); +#endif diff --git a/src/igl/quat_to_axis_angle.h b/src/igl/quat_to_axis_angle.h new file mode 100644 index 000000000..77ea10e5c --- /dev/null +++ b/src/igl/quat_to_axis_angle.h @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUAT_TO_AXIS_ANGLE_H +#define IGL_QUAT_TO_AXIS_ANGLE_H +#include "igl_inline.h" + +namespace igl +{ + // Convert quat representation of a rotation to axis angle + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // q quaternion + // Outputs: + // axis 3d vector + // angle scalar in radians + template + IGL_INLINE void quat_to_axis_angle( + const Q_type *q, + Q_type *axis, + Q_type & angle); + // Wrapper with angle in degrees + template + IGL_INLINE void quat_to_axis_angle_deg( + const Q_type *q, + Q_type *axis, + Q_type & angle); +} + +#ifndef IGL_STATIC_LIBRARY +# include "quat_to_axis_angle.cpp" +#endif + +#endif + diff --git a/src/igl/quat_to_mat.cpp b/src/igl/quat_to_mat.cpp new file mode 100644 index 000000000..22ec07b94 --- /dev/null +++ b/src/igl/quat_to_mat.cpp @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quat_to_mat.h" + +template +IGL_INLINE void igl::quat_to_mat(const Q_type * quat, Q_type * mat) +{ + Q_type yy2 = 2.0f * quat[1] * quat[1]; + Q_type xy2 = 2.0f * quat[0] * quat[1]; + Q_type xz2 = 2.0f * quat[0] * quat[2]; + Q_type yz2 = 2.0f * quat[1] * quat[2]; + Q_type zz2 = 2.0f * quat[2] * quat[2]; + Q_type wz2 = 2.0f * quat[3] * quat[2]; + Q_type wy2 = 2.0f * quat[3] * quat[1]; + Q_type wx2 = 2.0f * quat[3] * quat[0]; + Q_type xx2 = 2.0f * quat[0] * quat[0]; + mat[0*4+0] = - yy2 - zz2 + 1.0f; + mat[0*4+1] = xy2 + wz2; + mat[0*4+2] = xz2 - wy2; + mat[0*4+3] = 0; + mat[1*4+0] = xy2 - wz2; + mat[1*4+1] = - xx2 - zz2 + 1.0f; + mat[1*4+2] = yz2 + wx2; + mat[1*4+3] = 0; + mat[2*4+0] = xz2 + wy2; + mat[2*4+1] = yz2 - wx2; + mat[2*4+2] = - xx2 - yy2 + 1.0f; + mat[2*4+3] = 0; + mat[3*4+0] = mat[3*4+1] = mat[3*4+2] = 0; + mat[3*4+3] = 1; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::quat_to_mat(double const*, double*); +// generated by autoexplicit.sh +template void igl::quat_to_mat(float const*, float*); +#endif diff --git a/src/igl/quat_to_mat.h b/src/igl/quat_to_mat.h new file mode 100644 index 000000000..4291b291e --- /dev/null +++ b/src/igl/quat_to_mat.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUAT_TO_MAT_H +#define IGL_QUAT_TO_MAT_H +#include "igl_inline.h" +// Name history: +// quat2mat until 16 Sept 2011 +namespace igl +{ + // Convert a quaternion to a 4x4 matrix + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Input: + // quat pointer to four elements of quaternion (x,y,z,w) + // Output: + // mat pointer to 16 elements of matrix + template + IGL_INLINE void quat_to_mat(const Q_type * quat, Q_type * mat); +} + +#ifndef IGL_STATIC_LIBRARY +# include "quat_to_mat.cpp" +#endif + +#endif diff --git a/src/igl/quats_to_column.cpp b/src/igl/quats_to_column.cpp new file mode 100644 index 000000000..a4a88a666 --- /dev/null +++ b/src/igl/quats_to_column.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "quats_to_column.h" + +IGL_INLINE void igl::quats_to_column( + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > vQ, + Eigen::VectorXd & Q) +{ + Q.resize(vQ.size()*4); + for(int q = 0;q<(int)vQ.size();q++) + { + auto & xyzw = vQ[q].coeffs(); + for(int c = 0;c<4;c++) + { + Q(q*4+c) = xyzw(c); + } + } +} + +IGL_INLINE Eigen::VectorXd igl::quats_to_column( + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > vQ) +{ + Eigen::VectorXd Q; + quats_to_column(vQ,Q); + return Q; +} diff --git a/src/igl/quats_to_column.h b/src/igl/quats_to_column.h new file mode 100644 index 000000000..4a7795ae1 --- /dev/null +++ b/src/igl/quats_to_column.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_QUATS_TO_COLUMN_H +#define IGL_QUATS_TO_COLUMN_H +#include "igl_inline.h" +#include +#include +#include +#include +namespace igl +{ + // "Columnize" a list of quaternions (q1x,q1y,q1z,q1w,q2x,q2y,q2z,q2w,...) + // + // Inputs: + // vQ n-long list of quaternions + // Outputs: + // Q n*4-long list of coefficients + IGL_INLINE void quats_to_column( + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > vQ, + Eigen::VectorXd & Q); + IGL_INLINE Eigen::VectorXd quats_to_column( + const std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > vQ); +} + +#ifndef IGL_STATIC_LIBRARY +# include "quats_to_column.cpp" +#endif + +#endif + diff --git a/src/igl/ramer_douglas_peucker.cpp b/src/igl/ramer_douglas_peucker.cpp new file mode 100644 index 000000000..0dd1a40a3 --- /dev/null +++ b/src/igl/ramer_douglas_peucker.cpp @@ -0,0 +1,150 @@ +#include "ramer_douglas_peucker.h" + +#include "LinSpaced.h" +#include "find.h" +#include "cumsum.h" +#include "histc.h" +#include "slice.h" +#include "project_to_line.h" +#include "EPS.h" +#include "slice_mask.h" + +template +IGL_INLINE void igl::ramer_douglas_peucker( + const Eigen::MatrixBase & P, + const typename DerivedP::Scalar tol, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & J) +{ + typedef typename DerivedP::Scalar Scalar; + // number of vertices + const int n = P.rows(); + // Trivial base case + if(n <= 1) + { + J = DerivedJ::Zero(n); + S = P; + return; + } + // number of dimensions + const int m = P.cols(); + Eigen::Array I = + Eigen::Array::Constant(n,1,true); + const auto stol = tol*tol; + std::function simplify; + simplify = [&I,&P,&stol,&simplify](const int ixs, const int ixe)->void + { + assert(ixe>ixs); + Scalar sdmax = 0; + typename Eigen::Matrix::Index ixc = -1; + if((ixe-ixs)>1) + { + Scalar sdes = (P.row(ixe)-P.row(ixs)).squaredNorm(); + Eigen::Matrix sD; + const auto & Pblock = P.block(ixs+1,0,((ixe+1)-ixs)-2,P.cols()); + if(sdes<=EPS()) + { + sD = (Pblock.rowwise()-P.row(ixs)).rowwise().squaredNorm(); + }else + { + Eigen::Matrix T; + project_to_line(Pblock,P.row(ixs).eval(),P.row(ixe).eval(),T,sD); + } + sdmax = sD.maxCoeff(&ixc); + // Index full P + ixc = ixc+(ixs+1); + } + if(sdmax <= stol) + { + if(ixs != ixe-1) + { + I.block(ixs+1,0,((ixe+1)-ixs)-2,1).setConstant(false); + } + }else + { + simplify(ixs,ixc); + simplify(ixc,ixe); + } + }; + simplify(0,n-1); + slice_mask(P,I,1,S); + find(I,J); +} + +template < + typename DerivedP, + typename DerivedS, + typename DerivedJ, + typename DerivedQ> +IGL_INLINE void igl::ramer_douglas_peucker( + const Eigen::MatrixBase & P, + const typename DerivedP::Scalar tol, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & Q) +{ + typedef typename DerivedP::Scalar Scalar; + ramer_douglas_peucker(P,tol,S,J); + const int n = P.rows(); + assert(n>=2 && "Curve should be at least 2 points"); + typedef Eigen::Matrix VectorXS; + // distance traveled along high-res curve + VectorXS L(n); + L(0) = 0; + L.block(1,0,n-1,1) = (P.bottomRows(n-1)-P.topRows(n-1)).rowwise().norm(); + // Give extra on end + VectorXS T; + cumsum(L,1,T); + T.conservativeResize(T.size()+1); + T(T.size()-1) = T(T.size()-2); + // index of coarse point before each fine vertex + Eigen::VectorXi B; + { + Eigen::VectorXi N; + histc(igl::LinSpaced(n,0,n-1),J,N,B); + } + // Add extra point at end + J.conservativeResize(J.size()+1); + J(J.size()-1) = J(J.size()-2); + Eigen::VectorXi s,d; + // Find index in original list of "start" vertices + slice(J,B,s); + // Find index in original list of "destination" vertices + slice(J,(B.array()+1).eval(),d); + // Parameter between start and destination is linear in arc-length + VectorXS Ts,Td; + slice(T,s,Ts); + slice(T,d,Td); + T = ((T.head(T.size()-1)-Ts).array()/(Td-Ts).array()).eval(); + for(int t =0;t= S.rows()) + { + MB(b) = S.rows()-1; + } + } + DerivedS SMB; + slice(S,MB,1,SMB); + Q = SB.array() + ((SMB.array()-SB.array()).colwise()*T.array()); + + // Remove extra point at end + J.conservativeResize(J.size()-1); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::ramer_douglas_peucker, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::ramer_douglas_peucker, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/ramer_douglas_peucker.h b/src/igl/ramer_douglas_peucker.h new file mode 100644 index 000000000..dcfe47e85 --- /dev/null +++ b/src/igl/ramer_douglas_peucker.h @@ -0,0 +1,49 @@ +#ifndef IGL_RAMER_DOUGLAS_PEUCKER_H +#define IGL_RAMER_DOUGLAS_PEUCKER_H +#include "igl_inline.h" +#include +namespace igl +{ + // Ramer-Douglas-Peucker piecewise-linear curve simplification. + // + // Inputs: + // P #P by dim ordered list of vertices along the curve + // tol tolerance (maximal euclidean distance allowed between the new line + // and a vertex) + // Outputs: + // S #S by dim ordered list of points along the curve + // J #S list of indices into P so that S = P(J,:) + template + IGL_INLINE void ramer_douglas_peucker( + const Eigen::MatrixBase & P, + const typename DerivedP::Scalar tol, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & J); + // Run (Ramer-)Duglass-Peucker curve simplification but keep track of where + // every point on the original curve maps to on the simplified curve. + // + // Inputs: + // P #P by dim list of points, (use P([1:end 1],:) for loops) + // tol DP tolerance + // Outputs: + // S #S by dim list of points along simplified curve + // J #S indices into P of simplified points + // Q #P by dim list of points mapping along simplified curve + // + template < + typename DerivedP, + typename DerivedS, + typename DerivedJ, + typename DerivedQ> + IGL_INLINE void ramer_douglas_peucker( + const Eigen::MatrixBase & P, + const typename DerivedP::Scalar tol, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & J, + Eigen::PlainObjectBase & Q); + +} +#ifndef IGL_STATIC_LIBRARY +# include "ramer_douglas_peucker.cpp" +#endif +#endif diff --git a/src/igl/random_dir.cpp b/src/igl/random_dir.cpp new file mode 100644 index 000000000..f30783418 --- /dev/null +++ b/src/igl/random_dir.cpp @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "random_dir.h" +#include +#include + +IGL_INLINE Eigen::Vector3d igl::random_dir() +{ + using namespace Eigen; + double z = (double)rand() / (double)RAND_MAX*2.0 - 1.0; + double t = (double)rand() / (double)RAND_MAX*2.0*PI; + // http://www.altdevblogaday.com/2012/05/03/generating-uniformly-distributed-points-on-sphere/ + double r = sqrt(1.0-z*z); + double x = r * cos(t); + double y = r * sin(t); + return Vector3d(x,y,z); +} + +IGL_INLINE Eigen::MatrixXd igl::random_dir_stratified(const int n) +{ + using namespace Eigen; + using namespace std; + const double m = std::floor(sqrt(double(n))); + MatrixXd N(n,3); + int row = 0; + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RANDOM_DIR_H +#define IGL_RANDOM_DIR_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Generate a uniformly random unit direction in 3D, return as vector + IGL_INLINE Eigen::Vector3d random_dir(); + // Generate n stratified uniformly random unit directions in 3d, return as rows + // of an n by 3 matrix + // + // Inputs: + // n number of directions + // Return n by 3 matrix of random directions + IGL_INLINE Eigen::MatrixXd random_dir_stratified(const int n); +} + +#ifndef IGL_STATIC_LIBRARY +# include "random_dir.cpp" +#endif + +#endif diff --git a/src/igl/random_points_on_mesh.cpp b/src/igl/random_points_on_mesh.cpp new file mode 100644 index 000000000..b28135ad1 --- /dev/null +++ b/src/igl/random_points_on_mesh.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "random_points_on_mesh.h" +#include "doublearea.h" +#include "cumsum.h" +#include "histc.h" +#include +#include + +template +IGL_INLINE void igl::random_points_on_mesh( + const int n, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & FI) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedV::Scalar Scalar; + typedef Matrix VectorXs; + VectorXs A; + doublearea(V,F,A); + A /= A.array().sum(); + // Should be traingle mesh. Although Turk's method 1 generalizes... + assert(F.cols() == 3); + VectorXs C; + VectorXs A0(A.size()+1); + A0(0) = 0; + A0.bottomRightCorner(A.size(),1) = A; + // Even faster would be to use the "Alias Table Method" + cumsum(A0,1,C); + const VectorXs R = (VectorXs::Random(n,1).array() + 1.)/2.; + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() <= 1); + histc(R,C,FI); + const VectorXs S = (VectorXs::Random(n,1).array() + 1.)/2.; + const VectorXs T = (VectorXs::Random(n,1).array() + 1.)/2.; + B.resize(n,3); + B.col(0) = 1.-T.array().sqrt(); + B.col(1) = (1.-S.array()) * T.array().sqrt(); + B.col(2) = S.array() * T.array().sqrt(); +} + +template +IGL_INLINE void igl::random_points_on_mesh( + const int n, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix & B, + Eigen::PlainObjectBase & FI) +{ + using namespace Eigen; + using namespace std; + Matrix BC; + random_points_on_mesh(n,V,F,BC,FI); + vector > BIJV; + BIJV.reserve(n*3); + for(int s = 0;s= 0); + const int v = F(FI(s),c); + BIJV.push_back(Triplet(s,v,BC(s,c))); + } + } + B.resize(n,V.rows()); + B.reserve(n*3); + B.setFromTriplets(BIJV.begin(),BIJV.end()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::random_points_on_mesh, Eigen::Matrix, double, Eigen::Matrix >(int, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix&, Eigen::PlainObjectBase >&); +template void igl::random_points_on_mesh, Eigen::Matrix, double, Eigen::Matrix >(int, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/random_points_on_mesh.h b/src/igl/random_points_on_mesh.h new file mode 100644 index 000000000..7cde24f3c --- /dev/null +++ b/src/igl/random_points_on_mesh.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RANDOM_POINTS_ON_MESH_H +#define IGL_RANDOM_POINTS_ON_MESH_H + +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // RANDOM_POINTS_ON_MESH Randomly sample a mesh (V,F) n times. + // + // Inputs: + // n number of samples + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh triangle indices + // Outputs: + // B n by 3 list of barycentric coordinates, ith row are coordinates of + // ith sampled point in face FI(i) + // FI n list of indices into F + // + template + IGL_INLINE void random_points_on_mesh( + const int n, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & B, + Eigen::PlainObjectBase & FI); + // Outputs: + // B n by #V sparse matrix so that B*V produces a list of sample points + template + IGL_INLINE void random_points_on_mesh( + const int n, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix & B, + Eigen::PlainObjectBase & FI); +} + +#ifndef IGL_STATIC_LIBRARY +# include "random_points_on_mesh.cpp" +#endif + +#endif + + diff --git a/src/igl/random_quaternion.cpp b/src/igl/random_quaternion.cpp new file mode 100644 index 000000000..2d042e33f --- /dev/null +++ b/src/igl/random_quaternion.cpp @@ -0,0 +1,84 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "random_quaternion.h" +#include "PI.h" + +template +IGL_INLINE Eigen::Quaternion igl::random_quaternion() +{ + const auto & unit_rand = []()->Scalar + { + return ((Scalar)rand() / (Scalar)RAND_MAX); + }; +#ifdef false + // http://mathproofs.blogspot.com/2005/05/uniformly-distributed-random-unit.html + const Scalar t0 = 2.*igl::PI*unit_rand(); + const Scalar t1 = acos(1.-2.*unit_rand()); + const Scalar t2 = 0.5*(igl::PI*unit_rand() + acos(unit_rand())); + return Eigen::Quaternion( + 1.*sin(t0)*sin(t1)*sin(t2), + 1.*cos(t0)*sin(t1)*sin(t2), + 1.*cos(t1)*sin(t2), + 1.*cos(t2)); +#elif false + // "Uniform Random Rotations" [Shoemake 1992] method 1 + const auto & uurand = [&unit_rand]()->Scalar + { + return unit_rand()*2.-1.; + }; + Scalar x = uurand(); + Scalar y = uurand(); + Scalar z = uurand(); + Scalar w = uurand(); + const auto & hype = [&uurand](Scalar & x, Scalar & y)->Scalar + { + Scalar s1; + while((s1 = x*x + y*y) > 1.0) + { + x = uurand(); + y = uurand(); + } + return s1; + }; + Scalar s1 = hype(x,y); + Scalar s2 = hype(z,w); + Scalar num1 = -2.*log(s1); + Scalar num2 = -2.*log(s2); + Scalar r = num1 + num2; + Scalar root1 = sqrt((num1/s1)/r); + Scalar root2 = sqrt((num2/s2)/r); + return Eigen::Quaternion( + x*root1, + y*root1, + z*root2, + w*root2); +#else + // Shoemake method 2 + const Scalar x0 = unit_rand(); + const Scalar x1 = unit_rand(); + const Scalar x2 = unit_rand(); + const Scalar r1 = sqrt(1.0 - x0); + const Scalar r2 = sqrt(x0); + const Scalar t1 = 2.*igl::PI*x1; + const Scalar t2 = 2.*igl::PI*x2; + const Scalar c1 = cos(t1); + const Scalar s1 = sin(t1); + const Scalar c2 = cos(t2); + const Scalar s2 = sin(t2); + return Eigen::Quaternion( + s1*r1, + c1*r1, + s2*r2, + c2*r2); +#endif +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template Eigen::Quaternion igl::random_quaternion(); +#endif diff --git a/src/igl/random_quaternion.h b/src/igl/random_quaternion.h new file mode 100644 index 000000000..dfa36022b --- /dev/null +++ b/src/igl/random_quaternion.h @@ -0,0 +1,21 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RANDOM_QUATERNION_H +#define IGL_RANDOM_QUATERNION_H +#include "igl_inline.h" +#include +namespace igl +{ + // Return a random quaternion via uniform sampling of the 4-sphere + template + IGL_INLINE Eigen::Quaternion random_quaternion(); +} +#ifndef IGL_STATIC_LIBRARY +#include "random_quaternion.cpp" +#endif +#endif diff --git a/src/igl/random_search.cpp b/src/igl/random_search.cpp new file mode 100644 index 000000000..d248d73e6 --- /dev/null +++ b/src/igl/random_search.cpp @@ -0,0 +1,36 @@ +#include "random_search.h" +#include +#include + +template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB> +IGL_INLINE Scalar igl::random_search( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const int iters, + DerivedX & X) +{ + Scalar min_f = std::numeric_limits::max(); + const int dim = LB.size(); + assert(UB.size() == dim && "UB should match LB size"); + for(int iter = 0;iter, Eigen::Matrix, Eigen::Matrix >(std::function&)>, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, int, Eigen::Matrix&); +#endif diff --git a/src/igl/random_search.h b/src/igl/random_search.h new file mode 100644 index 000000000..8573e88cf --- /dev/null +++ b/src/igl/random_search.h @@ -0,0 +1,42 @@ +#ifndef IGL_RANDOM_SEARCH_H +#define IGL_RANDOM_SEARCH_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Solve the problem: + // + // minimize f(x) + // subject to lb ≤ x ≤ ub + // + // by uniform random search. + // + // Inputs: + // f function to minimize + // LB #X vector of finite lower bounds + // UB #X vector of finite upper bounds + // iters number of iterations + // Outputs: + // X #X optimal parameter vector + // Returns f(X) + // + template < + typename Scalar, + typename DerivedX, + typename DerivedLB, + typename DerivedUB> + IGL_INLINE Scalar random_search( + const std::function< Scalar (DerivedX &) > f, + const Eigen::MatrixBase & LB, + const Eigen::MatrixBase & UB, + const int iters, + DerivedX & X); +} + +#ifndef IGL_STATIC_LIBRARY +# include "random_search.cpp" +#endif + +#endif + diff --git a/src/igl/randperm.cpp b/src/igl/randperm.cpp new file mode 100644 index 000000000..d32af1760 --- /dev/null +++ b/src/igl/randperm.cpp @@ -0,0 +1,27 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "randperm.h" +#include "colon.h" +#include + +template +IGL_INLINE void igl::randperm( + const int n, + Eigen::PlainObjectBase & I) +{ + Eigen::VectorXi II; + igl::colon(0,1,n-1,II); + I = II; + std::random_shuffle(I.data(),I.data()+n); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::randperm >(int, Eigen::PlainObjectBase >&); +template void igl::randperm >(int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/randperm.h b/src/igl/randperm.h new file mode 100644 index 000000000..0ba067141 --- /dev/null +++ b/src/igl/randperm.h @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RANDPERM_H +#define IGL_RANDPERM_H +#include "igl_inline.h" +#include +namespace igl +{ + // Like matlab's randperm(n) but minus 1 + // + // Inputs: + // n number of elements + // Outputs: + // I n list of rand permutation of 0:n-1 + template + IGL_INLINE void randperm( + const int n, + Eigen::PlainObjectBase & I); +} +#ifndef IGL_STATIC_LIBRARY +# include "randperm.cpp" +#endif +#endif diff --git a/src/igl/ray_box_intersect.cpp b/src/igl/ray_box_intersect.cpp new file mode 100644 index 000000000..8c6346d86 --- /dev/null +++ b/src/igl/ray_box_intersect.cpp @@ -0,0 +1,149 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ray_box_intersect.h" +#include + +template < + typename Derivedsource, + typename Deriveddir, + typename Scalar> +IGL_INLINE bool igl::ray_box_intersect( + const Eigen::MatrixBase & origin, + const Eigen::MatrixBase & dir, + const Eigen::AlignedBox & box, + const Scalar & t0, + const Scalar & t1, + Scalar & tmin, + Scalar & tmax) +{ +#ifdef false + // https://github.com/RMonica/basic_next_best_view/blob/master/src/RayTracer.cpp + const auto & intersectRayBox = []( + const Eigen::Vector3f& rayo, + const Eigen::Vector3f& rayd, + const Eigen::Vector3f& bmin, + const Eigen::Vector3f& bmax, + float & tnear, + float & tfar + )->bool + { + Eigen::Vector3f bnear; + Eigen::Vector3f bfar; + // Checks for intersection testing on each direction coordinate + // Computes + float t1, t2; + tnear = -1e+6f, tfar = 1e+6f; //, tCube; + bool intersectFlag = true; + for (int i = 0; i < 3; ++i) { + // std::cout << "coordinate " << i << ": bmin " << bmin(i) << ", bmax " << bmax(i) << std::endl; + assert(bmin(i) <= bmax(i)); + if (::fabs(rayd(i)) < 1e-6) { // Ray parallel to axis i-th + if (rayo(i) < bmin(i) || rayo(i) > bmax(i)) { + intersectFlag = false; + } + } + else { + // Finds the nearest and the farthest vertices of the box from the ray origin + if (::fabs(bmin(i) - rayo(i)) < ::fabs(bmax(i) - rayo(i))) { + bnear(i) = bmin(i); + bfar(i) = bmax(i); + } + else { + bnear(i) = bmax(i); + bfar(i) = bmin(i); + } + // std::cout << " bnear " << bnear(i) << ", bfar " << bfar(i) << std::endl; + // Finds the distance parameters t1 and t2 of the two ray-box intersections: + // t1 must be the closest to the ray origin rayo. + t1 = (bnear(i) - rayo(i)) / rayd(i); + t2 = (bfar(i) - rayo(i)) / rayd(i); + if (t1 > t2) { + std::swap(t1,t2); + } + // The two intersection values are used to saturate tnear and tfar + if (t1 > tnear) { + tnear = t1; + } + if (t2 < tfar) { + tfar = t2; + } + // std::cout << " t1 " << t1 << ", t2 " << t2 << ", tnear " << tnear << ", tfar " << tfar + // << " tnear > tfar? " << (tnear > tfar) << ", tfar < 0? " << (tfar < 0) << std::endl; + if(tnear > tfar) { + intersectFlag = false; + } + if(tfar < 0) { + intersectFlag = false; + } + } + } + // Checks whether intersection occurs or not + return intersectFlag; + }; + float tmin_f, tmax_f; + bool ret = intersectRayBox( + origin. template cast(), + dir. template cast(), + box.min().template cast(), + box.max().template cast(), + tmin_f, + tmax_f); + tmin = tmin_f; + tmax = tmax_f; + return ret; +#else + using namespace Eigen; + // This should be precomputed and provided as input + typedef Matrix RowVector3S; + const RowVector3S inv_dir( 1./dir(0),1./dir(1),1./dir(2)); + const std::vector sign = { inv_dir(0)<0, inv_dir(1)<0, inv_dir(2)<0}; + // http://people.csail.mit.edu/amy/papers/box-jgt.pdf + // "An Efficient and Robust Ray–Box Intersection Algorithm" + Scalar tymin, tymax, tzmin, tzmax; + std::vector bounds = {box.min(),box.max()}; + tmin = ( bounds[sign[0]](0) - origin(0)) * inv_dir(0); + tmax = ( bounds[1-sign[0]](0) - origin(0)) * inv_dir(0); + tymin = (bounds[sign[1]](1) - origin(1)) * inv_dir(1); + tymax = (bounds[1-sign[1]](1) - origin(1)) * inv_dir(1); + if ( (tmin > tymax) || (tymin > tmax) ) + { + return false; + } + if (tymin > tmin) + { + tmin = tymin; + } + if (tymax < tmax) + { + tmax = tymax; + } + tzmin = (bounds[sign[2]](2) - origin(2)) * inv_dir(2); + tzmax = (bounds[1-sign[2]](2) - origin(2)) * inv_dir(2); + if ( (tmin > tzmax) || (tzmin > tmax) ) + { + return false; + } + if (tzmin > tmin) + { + tmin = tzmin; + } + if (tzmax < tmax) + { + tmax = tzmax; + } + if(!( (tmin < t1) && (tmax > t0) )) + { + return false; + } + return true; +#endif +} + +#ifdef IGL_STATIC_LIBRARY +template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); +#endif \ No newline at end of file diff --git a/src/igl/ray_box_intersect.h b/src/igl/ray_box_intersect.h new file mode 100644 index 000000000..6b6e14aaa --- /dev/null +++ b/src/igl/ray_box_intersect.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RAY_BOX_INTERSECT_H +#define IGL_RAY_BOX_INTERSECT_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Determine whether a ray origin+t*dir and box intersect within the ray's parameterized + // range (t0,t1) + // + // Inputs: + // source 3-vector origin of ray + // dir 3-vector direction of ray + // box axis aligned box + // t0 hit only if hit.t less than t0 + // t1 hit only if hit.t greater than t1 + // Outputs: + // tmin minimum of interval of overlap within [t0,t1] + // tmax maximum of interval of overlap within [t0,t1] + // Returns true if hit + template < + typename Derivedsource, + typename Deriveddir, + typename Scalar> + IGL_INLINE bool ray_box_intersect( + const Eigen::MatrixBase & source, + const Eigen::MatrixBase & dir, + const Eigen::AlignedBox & box, + const Scalar & t0, + const Scalar & t1, + Scalar & tmin, + Scalar & tmax); +} +#ifndef IGL_STATIC_LIBRARY +# include "ray_box_intersect.cpp" +#endif +#endif diff --git a/src/igl/ray_mesh_intersect.cpp b/src/igl/ray_mesh_intersect.cpp new file mode 100644 index 000000000..512a35c46 --- /dev/null +++ b/src/igl/ray_mesh_intersect.cpp @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ray_mesh_intersect.h" + +extern "C" +{ +#include "raytri.c" +} + +template < + typename Derivedsource, + typename Deriveddir, + typename DerivedV, + typename DerivedF> +IGL_INLINE bool igl::ray_mesh_intersect( + const Eigen::MatrixBase & s, + const Eigen::MatrixBase & dir, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + std::vector & hits) +{ + using namespace Eigen; + using namespace std; + // Should be but can't be const + Vector3d s_d = s.template cast(); + Vector3d dir_d = dir.template cast(); + hits.clear(); + // loop over all triangles + for(int f = 0;f(); + RowVector3d v1 = V.row(F(f,1)).template cast(); + RowVector3d v2 = V.row(F(f,2)).template cast(); + // shoot ray, record hit + double t,u,v; + if(intersect_triangle1( + s_d.data(), dir_d.data(), v0.data(), v1.data(), v2.data(), &t, &u, &v) && + t>0) + { + hits.push_back({(int)f,(int)-1,(float)u,(float)v,(float)t}); + } + } + // Sort hits based on distance + std::sort( + hits.begin(), + hits.end(), + [](const Hit & a, const Hit & b)->bool{ return a.t < b.t;}); + return hits.size() > 0; +} + +template < + typename Derivedsource, + typename Deriveddir, + typename DerivedV, + typename DerivedF> +IGL_INLINE bool igl::ray_mesh_intersect( + const Eigen::MatrixBase & source, + const Eigen::MatrixBase & dir, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + igl::Hit & hit) +{ + std::vector hits; + ray_mesh_intersect(source,dir,V,F,hits); + if(hits.size() > 0) + { + hit = hits.front(); + return true; + }else + { + return false; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >&); +template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::Hit&); +template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, igl::Hit&); +#endif diff --git a/src/igl/ray_mesh_intersect.h b/src/igl/ray_mesh_intersect.h new file mode 100644 index 000000000..e081bf3bb --- /dev/null +++ b/src/igl/ray_mesh_intersect.h @@ -0,0 +1,56 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RAY_MESH_INTERSECT_H +#define IGL_RAY_MESH_INTERSECT_H +#include "igl_inline.h" +#include "Hit.h" +#include +#include +namespace igl +{ + // Shoot a ray against a mesh (V,F) and collect all hits. + // + // Inputs: + // source 3-vector origin of ray + // dir 3-vector direction of ray + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh face indices into V + // Outputs: + // hits **sorted** list of hits + // Returns true if there were any hits (hits.size() > 0) + // + template < + typename Derivedsource, + typename Deriveddir, + typename DerivedV, + typename DerivedF> + IGL_INLINE bool ray_mesh_intersect( + const Eigen::MatrixBase & source, + const Eigen::MatrixBase & dir, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + std::vector & hits); + // Outputs: + // hit first hit, set only if it exists + // Returns true if there was a hit + template < + typename Derivedsource, + typename Deriveddir, + typename DerivedV, + typename DerivedF> + IGL_INLINE bool ray_mesh_intersect( + const Eigen::MatrixBase & source, + const Eigen::MatrixBase & dir, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + igl::Hit & hit); +} +#ifndef IGL_STATIC_LIBRARY +# include "ray_mesh_intersect.cpp" +#endif +#endif diff --git a/src/igl/ray_sphere_intersect.cpp b/src/igl/ray_sphere_intersect.cpp new file mode 100644 index 000000000..8a05c2a12 --- /dev/null +++ b/src/igl/ray_sphere_intersect.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "ray_sphere_intersect.h" + +template < + typename Derivedo, + typename Derivedd, + typename Derivedc, + typename r_type, + typename t_type> +IGL_INLINE int igl::ray_sphere_intersect( + const Eigen::PlainObjectBase & ao, + const Eigen::PlainObjectBase & d, + const Eigen::PlainObjectBase & ac, + r_type r, + t_type & t0, + t_type & t1) +{ + Eigen::Vector3d o = ao-ac; + // http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection + //Compute A, B and C coefficients + double a = d.dot(d); + double b = 2 * d.dot(o); + double c = o.dot(o) - (r * r); + + //Find discriminant + double disc = b * b - 4 * a * c; + + // if discriminant is negative there are no real roots, so return + // false as ray misses sphere + if (disc < 0) + { + return 0; + } + + // compute q as described above + double distSqrt = sqrt(disc); + double q; + if (b < 0) + { + q = (-b - distSqrt)/2.0; + } else + { + q = (-b + distSqrt)/2.0; + } + + // compute t0 and t1 + t0 = q / a; + double _t1 = c/q; + if(_t1 == t0) + { + return 1; + } + t1 = _t1; + // make sure t0 is smaller than t1 + if (t0 > t1) + { + // if t0 is bigger than t1 swap them around + double temp = t0; + t0 = t1; + t1 = temp; + } + return 2; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template int igl::ray_sphere_intersect, Eigen::Matrix, Eigen::Matrix, double, double>(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double, double&, double&); +#endif diff --git a/src/igl/ray_sphere_intersect.h b/src/igl/ray_sphere_intersect.h new file mode 100644 index 000000000..b4dfea379 --- /dev/null +++ b/src/igl/ray_sphere_intersect.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RAY_SPHERE_INTERSECT_H +#define IGL_RAY_SPHERE_INTERSECT_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the intersection between a ray from O in direction D and a sphere + // centered at C with radius r + // + // Inputs: + // o origin of ray + // d direction of ray + // c center of sphere + // r radius of sphere + // Outputs: + // t0 parameterization of first hit (set only if exists) so that hit + // position = o + t0*d + // t1 parameterization of second hit (set only if exists) + // + // Returns the number of hits + template < + typename Derivedo, + typename Derivedd, + typename Derivedc, + typename r_type, + typename t_type> + IGL_INLINE int ray_sphere_intersect( + const Eigen::PlainObjectBase & o, + const Eigen::PlainObjectBase & d, + const Eigen::PlainObjectBase & c, + r_type r, + t_type & t0, + t_type & t1); +} +#ifndef IGL_STATIC_LIBRARY +#include "ray_sphere_intersect.cpp" +#endif +#endif + diff --git a/src/igl/raytri.c b/src/igl/raytri.c new file mode 100644 index 000000000..b5e7f7b1f --- /dev/null +++ b/src/igl/raytri.c @@ -0,0 +1,267 @@ +/* Ray-Triangle Intersection Test Routines */ +/* Different optimizations of my and Ben Trumbore's */ +/* code from journals of graphics tools (JGT) */ +/* http://www.acm.org/jgt/ */ +/* by Tomas Moller, May 2000 */ + + +// Alec: this file is listed as "Public Domain" +// http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/ + +// Alec: I've added an include guard, made all functions inline and added +// IGL_RAY_TRI_ to #define macros +#ifndef IGL_RAY_TRI_C +#define IGL_RAY_TRI_C + +#include + +#define IGL_RAY_TRI_EPSILON 0.000001 +#define IGL_RAY_TRI_CROSS(dest,v1,v2) \ + dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \ + dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \ + dest[2]=v1[0]*v2[1]-v1[1]*v2[0]; +#define IGL_RAY_TRI_DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]) +#define IGL_RAY_TRI_SUB(dest,v1,v2) \ + dest[0]=v1[0]-v2[0]; \ + dest[1]=v1[1]-v2[1]; \ + dest[2]=v1[2]-v2[2]; + +/* the original jgt code */ +inline int intersect_triangle(double orig[3], double dir[3], + double vert0[3], double vert1[3], double vert2[3], + double *t, double *u, double *v) +{ + double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + double det,inv_det; + + /* find vectors for two edges sharing vert0 */ + IGL_RAY_TRI_SUB(edge1, vert1, vert0); + IGL_RAY_TRI_SUB(edge2, vert2, vert0); + + /* begin calculating determinant - also used to calculate U parameter */ + IGL_RAY_TRI_CROSS(pvec, dir, edge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = IGL_RAY_TRI_DOT(edge1, pvec); + + if (det > -IGL_RAY_TRI_EPSILON && det < IGL_RAY_TRI_EPSILON) + return 0; + inv_det = 1.0 / det; + + /* calculate distance from vert0 to ray origin */ + IGL_RAY_TRI_SUB(tvec, orig, vert0); + + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec) * inv_det; + if (*u < 0.0 || *u > 1.0) + return 0; + + /* prepare to test V parameter */ + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec) * inv_det; + if (*v < 0.0 || *u + *v > 1.0) + return 0; + + /* calculate t, ray intersects triangle */ + *t = IGL_RAY_TRI_DOT(edge2, qvec) * inv_det; + + return 1; +} + + +/* code rewritten to do tests on the sign of the determinant */ +/* the division is at the end in the code */ +inline int intersect_triangle1(double orig[3], double dir[3], + double vert0[3], double vert1[3], double vert2[3], + double *t, double *u, double *v) +{ + double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + double det,inv_det; + + /* find vectors for two edges sharing vert0 */ + IGL_RAY_TRI_SUB(edge1, vert1, vert0); + IGL_RAY_TRI_SUB(edge2, vert2, vert0); + + /* begin calculating determinant - also used to calculate U parameter */ + IGL_RAY_TRI_CROSS(pvec, dir, edge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = IGL_RAY_TRI_DOT(edge1, pvec); + + if (det > IGL_RAY_TRI_EPSILON) + { + /* calculate distance from vert0 to ray origin */ + IGL_RAY_TRI_SUB(tvec, orig, vert0); + + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec); + if (*u < 0.0 || *u > det) + return 0; + + /* prepare to test V parameter */ + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec); + if (*v < 0.0 || *u + *v > det) + return 0; + + } + else if(det < -IGL_RAY_TRI_EPSILON) + { + /* calculate distance from vert0 to ray origin */ + IGL_RAY_TRI_SUB(tvec, orig, vert0); + + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec); +/* printf("*u=%f\n",(float)*u); */ +/* printf("det=%f\n",det); */ + if (*u > 0.0 || *u < det) + return 0; + + /* prepare to test V parameter */ + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec) ; + if (*v > 0.0 || *u + *v < det) + return 0; + } + else return 0; /* ray is parallel to the plane of the triangle */ + + + inv_det = 1.0 / det; + + /* calculate t, ray intersects triangle */ + *t = IGL_RAY_TRI_DOT(edge2, qvec) * inv_det; + (*u) *= inv_det; + (*v) *= inv_det; + + return 1; +} + +/* code rewritten to do tests on the sign of the determinant */ +/* the division is before the test of the sign of the det */ +inline int intersect_triangle2(double orig[3], double dir[3], + double vert0[3], double vert1[3], double vert2[3], + double *t, double *u, double *v) +{ + double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + double det,inv_det; + + /* find vectors for two edges sharing vert0 */ + IGL_RAY_TRI_SUB(edge1, vert1, vert0); + IGL_RAY_TRI_SUB(edge2, vert2, vert0); + + /* begin calculating determinant - also used to calculate U parameter */ + IGL_RAY_TRI_CROSS(pvec, dir, edge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = IGL_RAY_TRI_DOT(edge1, pvec); + + /* calculate distance from vert0 to ray origin */ + IGL_RAY_TRI_SUB(tvec, orig, vert0); + inv_det = 1.0 / det; + + if (det > IGL_RAY_TRI_EPSILON) + { + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec); + if (*u < 0.0 || *u > det) + return 0; + + /* prepare to test V parameter */ + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec); + if (*v < 0.0 || *u + *v > det) + return 0; + + } + else if(det < -IGL_RAY_TRI_EPSILON) + { + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec); + if (*u > 0.0 || *u < det) + return 0; + + /* prepare to test V parameter */ + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec) ; + if (*v > 0.0 || *u + *v < det) + return 0; + } + else return 0; /* ray is parallel to the plane of the triangle */ + + /* calculate t, ray intersects triangle */ + *t = IGL_RAY_TRI_DOT(edge2, qvec) * inv_det; + (*u) *= inv_det; + (*v) *= inv_det; + + return 1; +} + +/* code rewritten to do tests on the sign of the determinant */ +/* the division is before the test of the sign of the det */ +/* and one IGL_RAY_TRI_CROSS has been moved out from the if-else if-else */ +inline int intersect_triangle3(double orig[3], double dir[3], + double vert0[3], double vert1[3], double vert2[3], + double *t, double *u, double *v) +{ + double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + double det,inv_det; + + /* find vectors for two edges sharing vert0 */ + IGL_RAY_TRI_SUB(edge1, vert1, vert0); + IGL_RAY_TRI_SUB(edge2, vert2, vert0); + + /* begin calculating determinant - also used to calculate U parameter */ + IGL_RAY_TRI_CROSS(pvec, dir, edge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = IGL_RAY_TRI_DOT(edge1, pvec); + + /* calculate distance from vert0 to ray origin */ + IGL_RAY_TRI_SUB(tvec, orig, vert0); + inv_det = 1.0 / det; + + IGL_RAY_TRI_CROSS(qvec, tvec, edge1); + + if (det > IGL_RAY_TRI_EPSILON) + { + *u = IGL_RAY_TRI_DOT(tvec, pvec); + if (*u < 0.0 || *u > det) + return 0; + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec); + if (*v < 0.0 || *u + *v > det) + return 0; + + } + else if(det < -IGL_RAY_TRI_EPSILON) + { + /* calculate U parameter and test bounds */ + *u = IGL_RAY_TRI_DOT(tvec, pvec); + if (*u > 0.0 || *u < det) + return 0; + + /* calculate V parameter and test bounds */ + *v = IGL_RAY_TRI_DOT(dir, qvec) ; + if (*v > 0.0 || *u + *v < det) + return 0; + } + else return 0; /* ray is parallel to the plane of the triangle */ + + *t = IGL_RAY_TRI_DOT(edge2, qvec) * inv_det; + (*u) *= inv_det; + (*v) *= inv_det; + + return 1; +} +#endif diff --git a/src/igl/readBF.cpp b/src/igl/readBF.cpp new file mode 100644 index 000000000..03099ea7e --- /dev/null +++ b/src/igl/readBF.cpp @@ -0,0 +1,114 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readBF.h" +#include "list_to_matrix.h" +#include +#include +#include +#include +#include +template < + typename DerivedWI, + typename DerivedP, + typename DerivedO> +IGL_INLINE bool igl::readBF( + const std::string & filename, + Eigen::PlainObjectBase & WI, + Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & O) +{ + using namespace std; + ifstream is(filename); + if(!is.is_open()) + { + return false; + } + string line; + std::vector vWI; + std::vector vP; + std::vector > vO; + while(getline(is, line)) + { + int wi,p; + double cx,cy,cz; + if(sscanf(line.c_str(), "%d %d %lg %lg %lg",&wi,&p,&cx,&cy,&cz) != 5) + { + return false; + } + vWI.push_back(wi); + vP.push_back(p); + vO.push_back({cx,cy,cz}); + } + list_to_matrix(vWI,WI); + list_to_matrix(vP,P); + list_to_matrix(vO,O); + return true; +} + +template < + typename DerivedWI, + typename DerivedbfP, + typename DerivedO, + typename DerivedC, + typename DerivedBE, + typename DerivedP> +IGL_INLINE bool igl::readBF( + const std::string & filename, + Eigen::PlainObjectBase & WI, + Eigen::PlainObjectBase & bfP, + Eigen::PlainObjectBase & offsets, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & BE, + Eigen::PlainObjectBase & P) +{ + using namespace Eigen; + using namespace std; + if(!readBF(filename,WI,bfP,offsets)) + { + return false; + } + + C.resize(WI.rows(),3); + vector computed(C.rows(),false); + // better not be cycles in bfP + std::function locate_tip; + locate_tip = + [&offsets,&computed,&bfP,&locate_tip,&C](const int w)->Eigen::RowVector3d + { + if(w<0) return Eigen::RowVector3d(0,0,0); + if(computed[w]) return C.row(w); + computed[w] = true; + return C.row(w) = locate_tip(bfP(w)) + offsets.row(w); + }; + int num_roots = (bfP.array() == -1).count(); + BE.resize(WI.rows()-num_roots,2); + P.resize(BE.rows()); + for(int c = 0;c=0); + // weight associated with this bone + const int wi = WI(c); + if(wi >= 0) + { + // index into C + const int p = bfP(c); + assert(p >= 0 && "No weights for roots allowed"); + // index into BE + const int pwi = WI(p); + P(wi) = pwi; + BE(wi,0) = p; + BE(wi,1) = c; + } + } + return true; +} + +#ifdef IGL_STATIC_LIBRARY +template bool igl::readBF, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readBF.h b/src/igl/readBF.h new file mode 100644 index 000000000..a2b010c85 --- /dev/null +++ b/src/igl/readBF.h @@ -0,0 +1,67 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READBF_H +#define IGL_READBF_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Read a bones forest from a file, returns a list of bone roots + // Input: + // file_name path to .bf bones tree file + // Output: + // WI #B list of unique weight indices + // P #B list of parent indices into B, -1 for roots + // O #B by 3 list of tip offset vectors from parent (or position for roots) + // Returns true on success, false on errors + template < + typename DerivedWI, + typename DerivedP, + typename DerivedO> + IGL_INLINE bool readBF( + const std::string & filename, + Eigen::PlainObjectBase & WI, + Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & O); + // Read bone forest into pure bone-skeleton format, expects only bones (no + // point handles), and that a root in the .bf <---> no weight attachment. + // + // Input: + // file_name path to .bf bones tree file + // Output: + // WI #B list of unique weight indices + // P #B list of parent indices into B, -1 for roots + // O #B by 3 list of tip offset vectors from parent (or position for roots) + // C #C by 3 list of absolute joint locations + // BE #BE by 3 list of bone indices into C, in order of weight index + // P #BE list of parent bone indices into BE, -1 means root bone + // Returns true on success, false on errors + // + // See also: readTGF, bone_parents, forward_kinematics + template < + typename DerivedWI, + typename DerivedbfP, + typename DerivedO, + typename DerivedC, + typename DerivedBE, + typename DerivedP> + IGL_INLINE bool readBF( + const std::string & filename, + Eigen::PlainObjectBase & WI, + Eigen::PlainObjectBase & bfP, + Eigen::PlainObjectBase & O, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & BE, + Eigen::PlainObjectBase & P); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readBF.cpp" +#endif +#endif diff --git a/src/igl/readCSV.cpp b/src/igl/readCSV.cpp new file mode 100644 index 000000000..eb00b1fe4 --- /dev/null +++ b/src/igl/readCSV.cpp @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readCSV.h" + +#include +#include +#include +#include + +#include + +template +IGL_INLINE bool igl::readCSV( + const std::string str, + Eigen::Matrix& M) +{ + using namespace std; + + std::vector > Mt; + + std::ifstream infile(str.c_str()); + std::string line; + while (std::getline(infile, line)) + { + std::istringstream iss(line); + vector temp; + Scalar a; + while (iss >> a) + temp.push_back(a); + + if (temp.size() != 0) // skip empty lines + Mt.push_back(temp); + } + + if (Mt.size() != 0) + { + // Verify that it is indeed a matrix + for (unsigned i = 0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READ_CSV_H +#define IGL_READ_CSV_H + +#include "igl/igl_inline.h" +#include +#include +#include + +namespace igl +{ + // read a matrix from a csv file into a Eigen matrix + // Templates: + // Scalar type for the matrix + // Inputs: + // str path to .csv file + // Outputs: + // M eigen matrix + template + IGL_INLINE bool readCSV( + const std::string str, + Eigen::Matrix& M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readCSV.cpp" +#endif + +#endif diff --git a/src/igl/readDMAT.cpp b/src/igl/readDMAT.cpp new file mode 100644 index 000000000..1ae9fdd65 --- /dev/null +++ b/src/igl/readDMAT.cpp @@ -0,0 +1,229 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readDMAT.h" + +#include "verbose.h" +#include +#include +#include + +// Static helper method reads the first to elements in the given file +// Inputs: +// fp file pointer of .dmat file that was just opened +// Outputs: +// num_rows number of rows +// num_cols number of columns +// Returns +// 0 success +// 1 did not find header +// 2 bad num_cols +// 3 bad num_rows +// 4 bad line ending +static inline int readDMAT_read_header(FILE * fp, int & num_rows, int & num_cols) +{ + // first line contains number of rows and number of columns + int res = fscanf(fp,"%d %d",&num_cols,&num_rows); + if(res != 2) + { + return 1; + } + // check that number of columns and rows are sane + if(num_cols < 0) + { + fprintf(stderr,"IOError: readDMAT() number of columns %d < 0\n",num_cols); + return 2; + } + if(num_rows < 0) + { + fprintf(stderr,"IOError: readDMAT() number of rows %d < 0\n",num_rows); + return 3; + } + // finish reading header + char lf; + + if(fread(&lf, sizeof(char), 1, fp)!=1 || !(lf == '\n' || lf == '\r')) + { + fprintf(stderr,"IOError: bad line ending in header\n"); + return 4; + } + + return 0; +} + +#ifndef IGL_NO_EIGEN +template +IGL_INLINE bool igl::readDMAT(const std::string file_name, + Eigen::PlainObjectBase & W) +{ + FILE * fp = fopen(file_name.c_str(),"rb"); + if(fp == NULL) + { + fprintf(stderr,"IOError: readDMAT() could not open %s...\n",file_name.c_str()); + return false; + } + int num_rows,num_cols; + int head_success = readDMAT_read_header(fp,num_rows,num_cols); + if(head_success != 0) + { + if(head_success == 1) + { + fprintf(stderr, + "IOError: readDMAT() first row should be [num cols] [num rows]...\n"); + } + fclose(fp); + return false; + } + + // Resize output to fit matrix, only if non-empty since this will trigger an + // error on fixed size matrices before reaching binary data. + bool empty = num_rows == 0 || num_cols == 0; + if(!empty) + { + W.resize(num_rows,num_cols); + } + + // Loop over columns slowly + for(int j = 0;j < num_cols;j++) + { + // loop over rows (down columns) quickly + for(int i = 0;i < num_rows;i++) + { + double d; + if(fscanf(fp," %lg",&d) != 1) + { + fclose(fp); + fprintf( + stderr, + "IOError: readDMAT() bad format after reading %d entries\n", + j*num_rows + i); + return false; + } + W(i,j) = d; + } + } + + // Try to read header for binary part + head_success = readDMAT_read_header(fp,num_rows,num_cols); + if(head_success == 0) + { + assert(W.size() == 0); + // Resize for output + W.resize(num_rows,num_cols); + double * Wraw = new double[num_rows*num_cols]; + fread(Wraw, sizeof(double), num_cols*num_rows, fp); + // Loop over columns slowly + for(int j = 0;j < num_cols;j++) + { + // loop over rows (down columns) quickly + for(int i = 0;i < num_rows;i++) + { + W(i,j) = Wraw[j*num_rows+i]; + } + } + }else + { + // we skipped resizing before in case there was binary data + if(empty) + { + // This could trigger an error if using fixed size matrices. + W.resize(num_rows,num_cols); + } + } + + fclose(fp); + return true; +} +#endif + +template +IGL_INLINE bool igl::readDMAT( + const std::string file_name, + std::vector > & W) +{ + FILE * fp = fopen(file_name.c_str(),"r"); + if(fp == NULL) + { + fprintf(stderr,"IOError: readDMAT() could not open %s...\n",file_name.c_str()); + return false; + } + int num_rows,num_cols; + bool head_success = readDMAT_read_header(fp,num_rows,num_cols); + if(head_success != 0) + { + if(head_success == 1) + { + fprintf(stderr, + "IOError: readDMAT() first row should be [num cols] [num rows]...\n"); + } + fclose(fp); + return false; + } + + // Resize for output + W.resize(num_rows,typename std::vector(num_cols)); + + // Loop over columns slowly + for(int j = 0;j < num_cols;j++) + { + // loop over rows (down columns) quickly + for(int i = 0;i < num_rows;i++) + { + double d; + if(fscanf(fp," %lg",&d) != 1) + { + fclose(fp); + fprintf( + stderr, + "IOError: readDMAT() bad format after reading %d entries\n", + j*num_rows + i); + return false; + } + W[i][j] = (Scalar)d; + } + } + + // Try to read header for binary part + head_success = readDMAT_read_header(fp,num_rows,num_cols); + if(head_success == 0) + { + assert(W.size() == 0); + // Resize for output + W.resize(num_rows,typename std::vector(num_cols)); + double * Wraw = new double[num_rows*num_cols]; + fread(Wraw, sizeof(double), num_cols*num_rows, fp); + // Loop over columns slowly + for(int j = 0;j < num_cols;j++) + { + // loop over rows (down columns) quickly + for(int i = 0;i < num_rows;i++) + { + W[i][j] = Wraw[j*num_rows+i]; + } + } + } + + fclose(fp); + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT(std::string, std::vector >, std::allocator > > >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >( std::string, Eigen::PlainObjectBase >&); +template bool igl::readDMAT >(std::string, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/readDMAT.h b/src/igl/readDMAT.h new file mode 100644 index 000000000..e09c492c1 --- /dev/null +++ b/src/igl/readDMAT.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READDMAT_H +#define IGL_READDMAT_H +#include "igl_inline.h" +// .dmat is a simple ascii matrix file type, defined as follows. The first line +// is always: +// <#columns> <#rows> +// Then the coefficients of the matrix are given separated by whitespace with +// columns running fastest. +// +// Example: +// The matrix m = [1 2 3; 4 5 6]; +// corresponds to a .dmat file containing: +// 3 2 +// 1 4 2 5 3 6 +#include +#include +#ifndef IGL_NO_EIGEN +# include +#endif +namespace igl +{ + // Read a matrix from an ascii dmat file + // + // Inputs: + // file_name path to .dmat file + // Outputs: + // W eigen matrix containing read-in coefficients + // Returns true on success, false on error + // +#ifndef IGL_NO_EIGEN + template + IGL_INLINE bool readDMAT(const std::string file_name, + Eigen::PlainObjectBase & W); +#endif + // Wrapper for vector of vectors + template + IGL_INLINE bool readDMAT( + const std::string file_name, + std::vector > & W); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readDMAT.cpp" +#endif + +#endif diff --git a/src/igl/readMESH.cpp b/src/igl/readMESH.cpp new file mode 100644 index 000000000..bc912e11b --- /dev/null +++ b/src/igl/readMESH.cpp @@ -0,0 +1,506 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readMESH.h" + +template +IGL_INLINE bool igl::readMESH( + const std::string mesh_file_name, + std::vector > & V, + std::vector > & T, + std::vector > & F) +{ + using namespace std; + FILE * mesh_file = fopen(mesh_file_name.c_str(),"r"); + if(NULL==mesh_file) + { + fprintf(stderr,"IOError: %s could not be opened...",mesh_file_name.c_str()); + return false; + } + return igl::readMESH(mesh_file,V,T,F); +} + +template +IGL_INLINE bool igl::readMESH( + FILE * mesh_file, + std::vector > & V, + std::vector > & T, + std::vector > & F) +{ + using namespace std; +#ifndef LINE_MAX +# define LINE_MAX 2048 +#endif + char line[LINE_MAX]; + bool still_comments; + V.clear(); + T.clear(); + F.clear(); + + // eat comments at beginning of file + still_comments= true; + while(still_comments) + { + if(fgets(line,LINE_MAX,mesh_file) == NULL) + { + fprintf(stderr, "Error: couldn't find start of .mesh file"); + fclose(mesh_file); + return false; + } + still_comments = (line[0] == '#' || line[0] == '\n'); + } + char str[LINE_MAX]; + sscanf(line," %s",str); + // check that first word is MeshVersionFormatted + if(0!=strcmp(str,"MeshVersionFormatted")) + { + fprintf(stderr, + "Error: first word should be MeshVersionFormatted not %s\n",str); + fclose(mesh_file); + return false; + } + + int one = -1; + if(2 != sscanf(line,"%s %d",str,&one)) + { + // 1 appears on next line? + fscanf(mesh_file," %d",&one); + } + if(one != 1) + { + fprintf(stderr,"Error: second word should be 1 not %d\n",one); + fclose(mesh_file); + return false; + } + + // eat comments + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,mesh_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + sscanf(line," %s",str); + // check that third word is Dimension + if(0!=strcmp(str,"Dimension")) + { + fprintf(stderr,"Error: third word should be Dimension not %s\n",str); + fclose(mesh_file); + return false; + } + int three = -1; + if(2 != sscanf(line,"%s %d",str,&three)) + { + // 1 appears on next line? + fscanf(mesh_file," %d",&three); + } + if(three != 3) + { + fprintf(stderr,"Error: only Dimension 3 supported not %d\n",three); + fclose(mesh_file); + return false; + } + + // eat comments + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,mesh_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + sscanf(line," %s",str); + // check that fifth word is Vertices + if(0!=strcmp(str,"Vertices")) + { + fprintf(stderr,"Error: fifth word should be Vertices not %s\n",str); + fclose(mesh_file); + return false; + } + + //fgets(line,LINE_MAX,mesh_file); + + int number_of_vertices; + if(1 != fscanf(mesh_file," %d",&number_of_vertices) || number_of_vertices > 1000000000) + { + fprintf(stderr,"Error: expecting number of vertices less than 10^9...\n"); + fclose(mesh_file); + return false; + } + // allocate space for vertices + V.resize(number_of_vertices,vector(3,0)); + int extra; + for(int i = 0;i(3)); + // triangle indices + int tri[3]; + for(int i = 0;i(4)); + // tet indices + int a,b,c,d; + for(int i = 0;i +#include "list_to_matrix.h" + + +template +IGL_INLINE bool igl::readMESH( + const std::string mesh_file_name, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F) +{ + using namespace std; + FILE * mesh_file = fopen(mesh_file_name.c_str(),"r"); + if(NULL==mesh_file) + { + fprintf(stderr,"IOError: %s could not be opened...",mesh_file_name.c_str()); + return false; + } + return readMESH(mesh_file,V,T,F); +} + +template +IGL_INLINE bool igl::readMESH( + FILE * mesh_file, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F) +{ + using namespace std; +#ifndef LINE_MAX +# define LINE_MAX 2048 +#endif + char line[LINE_MAX]; + bool still_comments; + + // eat comments at beginning of file + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,mesh_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + char str[LINE_MAX]; + sscanf(line," %s",str); + // check that first word is MeshVersionFormatted + if(0!=strcmp(str,"MeshVersionFormatted")) + { + fprintf(stderr, + "Error: first word should be MeshVersionFormatted not %s\n",str); + fclose(mesh_file); + return false; + } + int one = -1; + if(2 != sscanf(line,"%s %d",str,&one)) + { + // 1 appears on next line? + fscanf(mesh_file," %d",&one); + } + if(one != 1) + { + fprintf(stderr,"Error: second word should be 1 not %d\n",one); + fclose(mesh_file); + return false; + } + + // eat comments + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,mesh_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + sscanf(line," %s",str); + // check that third word is Dimension + if(0!=strcmp(str,"Dimension")) + { + fprintf(stderr,"Error: third word should be Dimension not %s\n",str); + fclose(mesh_file); + return false; + } + int three = -1; + if(2 != sscanf(line,"%s %d",str,&three)) + { + // 1 appears on next line? + fscanf(mesh_file," %d",&three); + } + if(three != 3) + { + fprintf(stderr,"Error: only Dimension 3 supported not %d\n",three); + fclose(mesh_file); + return false; + } + + // eat comments + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,mesh_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + sscanf(line," %s",str); + // check that fifth word is Vertices + if(0!=strcmp(str,"Vertices")) + { + fprintf(stderr,"Error: fifth word should be Vertices not %s\n",str); + fclose(mesh_file); + return false; + } + + //fgets(line,LINE_MAX,mesh_file); + + int number_of_vertices; + if(1 != fscanf(mesh_file," %d",&number_of_vertices) || number_of_vertices > 1000000000) + { + fprintf(stderr,"Error: expecting number of vertices less than 10^9...\n"); + fclose(mesh_file); + return false; + } + // allocate space for vertices + V.resize(number_of_vertices,3); + int extra; + for(int i = 0;i > vV,vT,vF; +// bool success = igl::readMESH(mesh_file_name,vV,vT,vF); +// if(!success) +// { +// // readMESH already printed error message to std err +// return false; +// } +// bool V_rect = igl::list_to_matrix(vV,V); +// if(!V_rect) +// { +// // igl::list_to_matrix(vV,V) already printed error message to std err +// return false; +// } +// bool T_rect = igl::list_to_matrix(vT,T); +// if(!T_rect) +// { +// // igl::list_to_matrix(vT,T) already printed error message to std err +// return false; +// } +// bool F_rect = igl::list_to_matrix(vF,F); +// if(!F_rect) +// { +// // igl::list_to_matrix(vF,F) already printed error message to std err +// return false; +// } +// assert(V.cols() == 3); +// assert(T.cols() == 4); +// assert(F.cols() == 3); +// return true; +//} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(FILE*, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readMESH(std::basic_string, std::allocator >, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template bool igl::readMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readMESH.h b/src/igl/readMESH.h new file mode 100644 index 000000000..b9b763035 --- /dev/null +++ b/src/igl/readMESH.h @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READMESH_H +#define IGL_READMESH_H +#include "igl_inline.h" + +#include +#include +#include +#include + +namespace igl +{ + // load a tetrahedral volume mesh from a .mesh file + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Input: + // mesh_file_name path of .mesh file + // Outputs: + // V double matrix of vertex positions #V by 3 + // T #T list of tet indices into vertex positions + // F #F list of face indices into vertex positions + // + // Known bugs: Holes and regions are not supported + template + IGL_INLINE bool readMESH( + const std::string mesh_file_name, + std::vector > & V, + std::vector > & T, + std::vector > & F); + // Inputs: + // mesh_file pointer to already opened .mesh file + // Outputs: + // mesh_file closed file + template + IGL_INLINE bool readMESH( + FILE * mesh_file, + std::vector > & V, + std::vector > & T, + std::vector > & F); + + // Input: + // mesh_file_name path of .mesh file + // Outputs: + // V eigen double matrix #V by 3 + // T eigen int matrix #T by 4 + // F eigen int matrix #F by 3 + template + IGL_INLINE bool readMESH( + const std::string mesh_file_name, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F); + // Inputs: + // mesh_file pointer to already opened .mesh file + // Outputs: + // mesh_file closed file + template + IGL_INLINE bool readMESH( + FILE * mesh_file, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& T, + Eigen::PlainObjectBase& F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readMESH.cpp" +#endif + +#endif diff --git a/src/igl/readMSH.cpp b/src/igl/readMSH.cpp new file mode 100644 index 000000000..2335fd1cc --- /dev/null +++ b/src/igl/readMSH.cpp @@ -0,0 +1,500 @@ + +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#include "readMSH.h" +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedT> +IGL_INLINE bool igl::readMSH( + const std::string & filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & T) +{ + // https://github.com/Yixin-Hu/TetWild/blob/master/pymesh/MshSaver.cpp + // Original copyright: /* This file is part of PyMesh. Copyright (c) 2015 by Qingnan Zhou */ + typedef typename DerivedV::Scalar Float; + typedef Eigen::Matrix VectorF; + typedef Eigen::Matrix VectorI; + typedef std::map FieldMap; + typedef std::vector FieldNames; + VectorF m_nodes; + VectorI m_elements; + FieldMap m_node_fields; + FieldMap m_element_fields; + + bool m_binary; + size_t m_data_size; + size_t m_nodes_per_element; + size_t m_element_type; + std::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary); + if (!fin.is_open()) + { + std::stringstream err_msg; + err_msg << "failed to open file \"" << filename << "\""; + return false; + } + // Parse header + std::string buf; + double version; + int type; + fin >> buf; + const auto invalid_format = []()->bool + { + assert(false && "Invalid format"); + return false; + }; + const auto not_implemented = []()->bool + { + assert(false && "Not implemented"); + return false; + }; + if (buf != "$MeshFormat") { return invalid_format(); } + + fin >> version >> type >> m_data_size; + m_binary = (type == 1); + + // Some sanity check. + if (m_data_size != 8) { + std::cerr << "Error: data size must be 8 bytes." << std::endl; + return not_implemented(); + } + if (sizeof(int) != 4) { + std::cerr << "Error: code must be compiled with int size 4 bytes." << std::endl; + return not_implemented(); + } + const auto eat_white_space = [](std::ifstream& fin) + { + char next = fin.peek(); + while (next == '\n' || next == ' ' || next == '\t' || next == '\r') + { + fin.get(); + next = fin.peek(); + } + }; + + // Read in extra info from binary header. + if (m_binary) { + int one; + eat_white_space(fin); + fin.read(reinterpret_cast(&one), sizeof(int)); + if (one != 1) { + std::cerr << "Warning: binary msh file " << filename + << " is saved with different endianness than this machine." + << std::endl; + return not_implemented(); + } + } + + fin >> buf; + if (buf != "$EndMeshFormat") { return not_implemented(); } + + const auto num_nodes_per_elem_type = [](int elem_type)->int + { + size_t nodes_per_element = 0; + switch (elem_type) { + case 2: + nodes_per_element = 3; // Triangle + break; + case 3: + nodes_per_element = 4; // Quad + break; + case 4: + nodes_per_element = 4; // Tet + break; + case 5: + nodes_per_element = 8; // hexahedron + break; + default: + assert(false && "not implemented"); + nodes_per_element = -1; + break; + } + return nodes_per_element; + }; + + const auto parse_nodes = [&](std::ifstream& fin) + { + size_t num_nodes; + fin >> num_nodes; + m_nodes.resize(num_nodes*3); + + if (m_binary) { + size_t num_bytes = (4+3*m_data_size) * num_nodes; + char* data = new char[num_bytes]; + eat_white_space(fin); + fin.read(data, num_bytes); + + for (size_t i=0; i (&data[i*(4+3*m_data_size)]) - 1; + m_nodes[node_idx*3] = *reinterpret_cast(&data[i*(4+3*m_data_size) + 4]); + m_nodes[node_idx*3+1] = *reinterpret_cast(&data[i*(4+3*m_data_size) + 4 + m_data_size]); + m_nodes[node_idx*3+2] = *reinterpret_cast(&data[i*(4+3*m_data_size) + 4 + 2*m_data_size]); + } + + delete [] data; + } else { + int node_idx; + for (size_t i=0; i> node_idx; + node_idx -= 1; + fin >> m_nodes[node_idx*3] + >> m_nodes[node_idx*3+1] + >> m_nodes[node_idx*3+2]; + } + } + }; + + const auto parse_elements = [&](std::ifstream& fin) + { + size_t num_elements; + fin >> num_elements; + + // Tmp storage of elements; + std::vector triangle_element_idx; + std::vector triangle_elements; + std::vector quad_element_idx; + std::vector quad_elements; + std::vector tet_element_idx; + std::vector tet_elements; + std::vector hex_element_idx; + std::vector hex_elements; + + auto get_element_storage = [&](int elem_type) -> std::vector* { + switch (elem_type) { + default: + assert(false && "Unsupported element type encountered"); + case 2: + return &triangle_elements; + case 3: + return &quad_elements; + case 4: + return &tet_elements; + case 5: + return &hex_elements; + }; + }; + + auto get_element_idx_storage = [&](int elem_type) -> std::vector* { + switch (elem_type) { + default: + assert(false && "Unsupported element type encountered"); + case 2: + return &triangle_element_idx; + case 3: + return &quad_element_idx; + case 4: + return &tet_element_idx; + case 5: + return &hex_element_idx; + }; + }; + + size_t nodes_per_element; + int glob_elem_type = -1; + + + if (m_binary) + { + eat_white_space(fin); + int elem_read = 0; + while (elem_read < num_elements) { + // Parse element header. + int elem_type, num_elems, num_tags; + fin.read((char*)&elem_type, sizeof(int)); + fin.read((char*)&num_elems, sizeof(int)); + fin.read((char*)&num_tags, sizeof(int)); + nodes_per_element = num_nodes_per_elem_type(elem_type); + std::vector& elements = *get_element_storage(elem_type); + std::vector& element_idx = *get_element_idx_storage(elem_type); + + for (size_t i=0; i> elem_num >> elem_type >> num_tags; + for (size_t j=0; j> tag; + } + nodes_per_element = num_nodes_per_elem_type(elem_type); + std::vector& elements = *get_element_storage(elem_type); + std::vector& element_idx = *get_element_idx_storage(elem_type); + + elem_num -= 1; + element_idx.push_back(elem_num); + + // Parse node idx. + for (size_t j=0; j> idx; + elements.push_back(idx-1); // msh index starts from 1. + } + } + } + + auto copy_to_array = [&]( + const std::vector& elements, + const int nodes_per_element) { + const size_t num_elements = elements.size() / nodes_per_element; + if (elements.size() % nodes_per_element != 0) { + assert(false && "parsing element failed"); + return; + } + m_elements.resize(elements.size()); + std::copy(elements.begin(), elements.end(), m_elements.data()); + m_nodes_per_element = nodes_per_element; + }; + + if (!tet_elements.empty()) { + copy_to_array(tet_elements, 4); + m_element_type = 4; + } else if (!hex_elements.empty()) { + copy_to_array(hex_elements, 8); + m_element_type = 5; + } else if (!triangle_elements.empty()) { + copy_to_array(triangle_elements, 3); + m_element_type = 2; + } else if (!quad_elements.empty()) { + copy_to_array(quad_elements, 4); + m_element_type = 3; + } else { + // 0 elements, use triangle by default. + m_element_type = 2; + } + }; + const auto parse_element_field = [&](std::ifstream& fin) + { + size_t num_string_tags; + size_t num_real_tags; + size_t num_int_tags; + + fin >> num_string_tags; + std::string* str_tags = new std::string[num_string_tags]; + for (size_t i=0; i> str_tags[i]; + } + } + + fin >> num_real_tags; + Float* real_tags = new Float[num_real_tags]; + for (size_t i=0; i> real_tags[i]; + + fin >> num_int_tags; + int* int_tags = new int[num_int_tags]; + for (size_t i=0; i> int_tags[i]; + + if (num_string_tags <= 0 || num_int_tags <= 2) { assert(false && "invalid format"); return; } + std::string fieldname = str_tags[0]; + int num_components = int_tags[1]; + int num_entries = int_tags[2]; + VectorF field(num_entries * num_components); + + delete [] str_tags; + delete [] real_tags; + delete [] int_tags; + + if (m_binary) { + size_t num_bytes = (num_components * m_data_size + 4) * num_entries; + char* data = new char[num_bytes]; + eat_white_space(fin); + fin.read(data, num_bytes); + for (size_t i=0; i(&data[i*(4+num_components*m_data_size)]); + elem_idx -= 1; + size_t base_idx = i*(4+num_components*m_data_size) + 4; + for (size_t j=0; j(&data[base_idx+j*m_data_size]); + } + } + delete [] data; + } else { + int elem_idx; + for (size_t i=0; i> elem_idx; + elem_idx -= 1; + for (size_t j=0; j> field[elem_idx * num_components + j]; + } + } + } + + m_element_fields[fieldname] = field; + }; + + const auto parse_node_field = [&](std::ifstream& fin) + { + size_t num_string_tags; + size_t num_real_tags; + size_t num_int_tags; + + fin >> num_string_tags; + std::string* str_tags = new std::string[num_string_tags]; + for (size_t i=0; i> str_tags[i]; + } + } + + fin >> num_real_tags; + Float* real_tags = new Float[num_real_tags]; + for (size_t i=0; i> real_tags[i]; + + fin >> num_int_tags; + int* int_tags = new int[num_int_tags]; + for (size_t i=0; i> int_tags[i]; + + if (num_string_tags <= 0 || num_int_tags <= 2) { assert(false && "invalid format"); return; } + std::string fieldname = str_tags[0]; + int num_components = int_tags[1]; + int num_entries = int_tags[2]; + VectorF field(num_entries * num_components); + + delete [] str_tags; + delete [] real_tags; + delete [] int_tags; + + if (m_binary) { + size_t num_bytes = (num_components * m_data_size + 4) * num_entries; + char* data = new char[num_bytes]; + eat_white_space(fin); + fin.read(data, num_bytes); + for (size_t i=0; i(&data[i*(4+num_components*m_data_size)]); + node_idx -= 1; + size_t base_idx = i*(4+num_components*m_data_size) + 4; + for (size_t j=0; j(&data[base_idx+j*m_data_size]); + } + } + delete [] data; + } else { + int node_idx; + for (size_t i=0; i> node_idx; + node_idx -= 1; + for (size_t j=0; j> field[node_idx * num_components + j]; + } + } + } + + m_node_fields[fieldname] = field; + }; + const auto parse_unknown_field = [](std::ifstream& fin, + const std::string& fieldname) + { + std::cerr << "Warning: \"" << fieldname << "\" not supported yet. Ignored." << std::endl; + std::string endmark = fieldname.substr(0,1) + "End" + + fieldname.substr(1,fieldname.size()-1); + + std::string buf(""); + while (buf != endmark && !fin.eof()) { + fin >> buf; + } + }; + + + while (!fin.eof()) { + buf.clear(); + fin >> buf; + if (buf == "$Nodes") { + parse_nodes(fin); + fin >> buf; + if (buf != "$EndNodes") { return invalid_format(); } + } else if (buf == "$Elements") { + parse_elements(fin); + fin >> buf; + if (buf != "$EndElements") { return invalid_format(); } + } else if (buf == "$NodeData") { + parse_node_field(fin); + fin >> buf; + if (buf != "$EndNodeData") { return invalid_format(); } + } else if (buf == "$ElementData") { + parse_element_field(fin); + fin >> buf; + if (buf != "$EndElementData") { return invalid_format(); } + } else if (fin.eof()) { + break; + } else { + parse_unknown_field(fin, buf); + } + } + fin.close(); + V.resize(m_nodes.rows()/3,3); + for (int i = 0; i < m_nodes.rows() / 3; i++) + { + for (int j = 0; j < 3; j++) + { + V(i,j) = m_nodes(i * 3 + j); + } + } + int ss = num_nodes_per_elem_type(m_element_type); + T.resize(m_elements.rows()/ss,ss); + for (int i = 0; i < m_elements.rows() / ss; i++) + { + for (int j = 0; j < ss; j++) + { + T(i, j) = m_elements(i * ss + j); + } + } + return true; +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/readMSH.h b/src/igl/readMSH.h new file mode 100644 index 000000000..1463411d8 --- /dev/null +++ b/src/igl/readMSH.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READMSH_H +#define IGL_READMSH_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Read a mesh (e.g., tet mesh) from a gmsh .msh file + // + // Inputs: + // filename path to .msh file + // Outputs: + // V #V by 3 list of 3D mesh vertex positions + // T #T by ss list of 3D ss-element indices into V (e.g., ss=4 for tets) + // Returns true on success + template < + typename DerivedV, + typename DerivedT> + IGL_INLINE bool readMSH( + const std::string & filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & T); +} + + +#ifndef IGL_STATIC_LIBRARY +# include "readMSH.cpp" +#endif +#endif diff --git a/src/igl/readNODE.cpp b/src/igl/readNODE.cpp new file mode 100644 index 000000000..cb775e599 --- /dev/null +++ b/src/igl/readNODE.cpp @@ -0,0 +1,161 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readNODE.h" +#include "matrix_to_list.h" +#include + +template +IGL_INLINE bool igl::readNODE( + const std::string node_file_name, + std::vector > & V, + std::vector > & I) +{ + // TODO: should be templated + Eigen::MatrixXd mV; + Eigen::MatrixXi mI; + if(igl::readNODE(node_file_name,mV,mI)) + { + matrix_to_list(mV,V); + matrix_to_list(mI,I); + return true; + }else + { + return false; + } +} + +template +IGL_INLINE bool igl::readNODE( + const std::string node_file_name, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& I) +{ + using namespace std; + FILE * node_file = fopen(node_file_name.c_str(),"r"); + if(NULL==node_file) + { + fprintf(stderr,"readNODE: IOError: %s could not be opened...\n", + node_file_name.c_str()); + return false; + } +#ifndef LINE_MAX +# define LINE_MAX 2048 +#endif + char line[LINE_MAX]; + bool still_comments; + + // eat comments at beginning of file + still_comments= true; + while(still_comments) + { + fgets(line,LINE_MAX,node_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + + // Read header + // n number of points + // dim dimension + // num_attr number of attributes + // num_bm number of boundary markers + int n,dim,num_attr,num_bm; + int head_count = sscanf(line,"%d %d %d %d", &n, &dim, &num_attr, &num_bm); + if(head_count!=4) + { + fprintf(stderr,"readNODE: Error: incorrect header in %s...\n", + node_file_name.c_str()); + fclose(node_file); + return false; + } + if(num_attr) + { + fprintf(stderr,"readNODE: Error: %d attributes found in %s. " + "Attributes are not supported...\n", + num_attr, + node_file_name.c_str()); + fclose(node_file); + return false; + } + // Just quietly ignore boundary markers + //if(num_bm) + //{ + // fprintf(stderr,"readNODE: Warning: %d boundary markers found in %s. " + // "Boundary markers are ignored...\n", + // num_bm, + // node_file_name.c_str()); + //} + + // resize output + V.resize(n,dim); + I.resize(n,1); + + int line_no = 0; + int p = 0; + while (fgets(line, LINE_MAX, node_file) != NULL) + { + line_no++; + // Skip comments and blank lines + if(line[0] == '#' || line[0] == '\n') + { + continue; + } + char * l = line; + int offset; + + if(sscanf(l,"%d%n",&I(p),&offset) != 1) + { + fprintf(stderr,"readNODE Error: bad index (%d) in %s...\n", + line_no, + node_file_name.c_str()); + fclose(node_file); + return false; + } + // adjust offset + l += offset; + + // Read coordinates + for(int d = 0;d, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readNODE.h b/src/igl/readNODE.h new file mode 100644 index 000000000..a4bdb4774 --- /dev/null +++ b/src/igl/readNODE.h @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READNODE_H +#define IGL_READNODE_H +#include "igl_inline.h" + +#include +#include +#include + +namespace igl +{ + // load a list of points from a .node file + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Input: + // node_file_name path of .node file + // Outputs: + // V double matrix of vertex positions #V by dim + // I list of indices (first tells whether 0 or 1 indexed) + template + IGL_INLINE bool readNODE( + const std::string node_file_name, + std::vector > & V, + std::vector > & I); + + // Input: + // node_file_name path of .node file + // Outputs: + // V eigen double matrix #V by dim + template + IGL_INLINE bool readNODE( + const std::string node_file_name, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readNODE.cpp" +#endif + +#endif diff --git a/src/igl/readOBJ.cpp b/src/igl/readOBJ.cpp new file mode 100644 index 000000000..42173fc84 --- /dev/null +++ b/src/igl/readOBJ.cpp @@ -0,0 +1,366 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readOBJ.h" + +#include "list_to_matrix.h" +#include "max_size.h" +#include "min_size.h" + +#include +#include +#include +#include +#include + +template +IGL_INLINE bool igl::readOBJ( + const std::string obj_file_name, + std::vector > & V, + std::vector > & TC, + std::vector > & N, + std::vector > & F, + std::vector > & FTC, + std::vector > & FN) +{ + // Open file, and check for error + FILE * obj_file = fopen(obj_file_name.c_str(),"r"); + if(NULL==obj_file) + { + fprintf(stderr,"IOError: %s could not be opened...\n", + obj_file_name.c_str()); + return false; + } + return igl::readOBJ(obj_file,V,TC,N,F,FTC,FN); +} + +template +IGL_INLINE bool igl::readOBJ( + FILE * obj_file, + std::vector > & V, + std::vector > & TC, + std::vector > & N, + std::vector > & F, + std::vector > & FTC, + std::vector > & FN) +{ + // File open was successful so clear outputs + V.clear(); + TC.clear(); + N.clear(); + F.clear(); + FTC.clear(); + FN.clear(); + + // variables and constants to assist parsing the .obj file + // Constant strings to compare against + std::string v("v"); + std::string vn("vn"); + std::string vt("vt"); + std::string f("f"); + std::string tic_tac_toe("#"); +#ifndef IGL_LINE_MAX +# define IGL_LINE_MAX 2048 +#endif + + char line[IGL_LINE_MAX]; + int line_no = 1; + while (fgets(line, IGL_LINE_MAX, obj_file) != NULL) + { + char type[IGL_LINE_MAX]; + // Read first word containing type + if(sscanf(line, "%s",type) == 1) + { + // Get pointer to rest of line right after type + char * l = &line[strlen(type)]; + if(type == v) + { + std::istringstream ls(&line[1]); + std::vector vertex{ std::istream_iterator(ls), std::istream_iterator() }; + + if (vertex.size() < 3) + { + fprintf(stderr, + "Error: readOBJ() vertex on line %d should have at least 3 coordinates", + line_no); + fclose(obj_file); + return false; + } + + V.push_back(vertex); + }else if(type == vn) + { + double x[3]; + int count = + sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]); + if(count != 3) + { + fprintf(stderr, + "Error: readOBJ() normal on line %d should have 3 coordinates", + line_no); + fclose(obj_file); + return false; + } + std::vector normal(count); + for(int i = 0;i tex(count); + for(int i = 0;iint + { + return i<0 ? i+V.size() : i-1; + }; + const auto & shift_t = [&TC](const int i)->int + { + return i<0 ? i+TC.size() : i-1; + }; + const auto & shift_n = [&N](const int i)->int + { + return i<0 ? i+N.size() : i-1; + }; + std::vector f; + std::vector ftc; + std::vector fn; + // Read each "word" after type + char word[IGL_LINE_MAX]; + int offset; + while(sscanf(l,"%s%n",word,&offset) == 1) + { + // adjust offset + l += offset; + // Process word + long int i,it,in; + if(sscanf(word,"%ld/%ld/%ld",&i,&it,&in) == 3) + { + f.push_back(shift(i)); + ftc.push_back(shift_t(it)); + fn.push_back(shift_n(in)); + }else if(sscanf(word,"%ld/%ld",&i,&it) == 2) + { + f.push_back(shift(i)); + ftc.push_back(shift_t(it)); + }else if(sscanf(word,"%ld//%ld",&i,&in) == 2) + { + f.push_back(shift(i)); + fn.push_back(shift_n(in)); + }else if(sscanf(word,"%ld",&i) == 1) + { + f.push_back(shift(i)); + }else + { + fprintf(stderr, + "Error: readOBJ() face on line %d has invalid element format\n", + line_no); + fclose(obj_file); + return false; + } + } + if( + (f.size()>0 && fn.size() == 0 && ftc.size() == 0) || + (f.size()>0 && fn.size() == f.size() && ftc.size() == 0) || + (f.size()>0 && fn.size() == 0 && ftc.size() == f.size()) || + (f.size()>0 && fn.size() == f.size() && ftc.size() == f.size())) + { + // No matter what add each type to lists so that lists are the + // correct lengths + F.push_back(f); + FTC.push_back(ftc); + FN.push_back(fn); + }else + { + fprintf(stderr, + "Error: readOBJ() face on line %d has invalid format\n", line_no); + fclose(obj_file); + return false; + } + }else if(strlen(type) >= 1 && (type[0] == '#' || + type[0] == 'g' || + type[0] == 's' || + strcmp("usemtl",type)==0 || + strcmp("mtllib",type)==0)) + { + //ignore comments or other shit + }else + { + //ignore any other lines + fprintf(stderr, + "Warning: readOBJ() ignored non-comment line %d:\n %s", + line_no, + line); + } + }else + { + // ignore empty line + } + line_no++; + } + fclose(obj_file); + + assert(F.size() == FN.size()); + assert(F.size() == FTC.size()); + + return true; +} + +template +IGL_INLINE bool igl::readOBJ( + const std::string obj_file_name, + std::vector > & V, + std::vector > & F) +{ + std::vector > TC,N; + std::vector > FTC,FN; + return readOBJ(obj_file_name,V,TC,N,F,FTC,FN); +} + +template < + typename DerivedV, + typename DerivedTC, + typename DerivedCN, + typename DerivedF, + typename DerivedFTC, + typename DerivedFN> +IGL_INLINE bool igl::readOBJ( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& TC, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& FTC, + Eigen::PlainObjectBase& FN) +{ + std::vector > vV,vTC,vN; + std::vector > vF,vFTC,vFN; + bool success = igl::readOBJ(str,vV,vTC,vN,vF,vFTC,vFN); + if(!success) + { + // readOBJ(str,vV,vTC,vN,vF,vFTC,vFN) should have already printed an error + // message to stderr + return false; + } + bool V_rect = igl::list_to_matrix(vV,V); + const char * format = "Failed to cast %s to matrix: min (%d) != max (%d)\n"; + if(!V_rect) + { + printf(format,"V",igl::min_size(vV),igl::max_size(vV)); + return false; + } + bool F_rect = igl::list_to_matrix(vF,F); + if(!F_rect) + { + printf(format,"F",igl::min_size(vF),igl::max_size(vF)); + return false; + } + if(!vN.empty()) + { + bool VN_rect = igl::list_to_matrix(vN,CN); + if(!VN_rect) + { + printf(format,"CN",igl::min_size(vN),igl::max_size(vN)); + return false; + } + } + + if(!vFN.empty() && !vFN[0].empty()) + { + bool FN_rect = igl::list_to_matrix(vFN,FN); + if(!FN_rect) + { + printf(format,"FN",igl::min_size(vFN),igl::max_size(vFN)); + return false; + } + } + + if(!vTC.empty()) + { + + bool T_rect = igl::list_to_matrix(vTC,TC); + if(!T_rect) + { + printf(format,"TC",igl::min_size(vTC),igl::max_size(vTC)); + return false; + } + } + if(!vFTC.empty()&& !vFTC[0].empty()) + { + + bool FTC_rect = igl::list_to_matrix(vFTC,FTC); + if(!FTC_rect) + { + printf(format,"FTC",igl::min_size(vFTC),igl::max_size(vFTC)); + return false; + } + } + return true; +} + +template +IGL_INLINE bool igl::readOBJ( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + std::vector > vV,vTC,vN; + std::vector > vF,vFTC,vFN; + bool success = igl::readOBJ(str,vV,vTC,vN,vF,vFTC,vFN); + if(!success) + { + // readOBJ(str,vV,vTC,vN,vF,vFTC,vFN) should have already printed an error + // message to stderr + return false; + } + bool V_rect = igl::list_to_matrix(vV,V); + if(!V_rect) + { + // igl::list_to_matrix(vV,V) already printed error message to std err + return false; + } + bool F_rect = igl::list_to_matrix(vF,F); + if(!F_rect) + { + // igl::list_to_matrix(vF,F) already printed error message to std err + return false; + } + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::readOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readOBJ(std::basic_string, std::allocator >, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +#endif diff --git a/src/igl/readOBJ.h b/src/igl/readOBJ.h new file mode 100644 index 000000000..443b6fc8e --- /dev/null +++ b/src/igl/readOBJ.h @@ -0,0 +1,101 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READOBJ_H +#define IGL_READOBJ_H +#include "igl_inline.h" +#include "deprecated.h" +// History: +// return type changed from void to bool Alec 18 Sept 2011 +// added pure vector of vectors version that has much more support Alec 31 Oct +// 2011 + +#ifndef IGL_NO_EIGEN +# include +#endif +#include +#include +#include + +namespace igl +{ + // Read a mesh from an ascii obj file, filling in vertex positions, normals + // and texture coordinates. Mesh may have faces of any number of degree + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to .obj file + // Outputs: + // V double matrix of vertex positions #V by 3 + // TC double matrix of texture coordinats #TC by 2 + // N double matrix of corner normals #N by 3 + // F #F list of face indices into vertex positions + // FTC #F list of face indices into vertex texture coordinates + // FN #F list of face indices into vertex normals + // Returns true on success, false on errors + template + IGL_INLINE bool readOBJ( + const std::string obj_file_name, + std::vector > & V, + std::vector > & TC, + std::vector > & N, + std::vector > & F, + std::vector > & FTC, + std::vector > & FN); + // Inputs: + // obj_file pointer to already opened .obj file + // Outputs: + // obj_file closed file + template + IGL_INLINE bool readOBJ( + FILE * obj_file, + std::vector > & V, + std::vector > & TC, + std::vector > & N, + std::vector > & F, + std::vector > & FTC, + std::vector > & FN); + // Just V and F + template + IGL_INLINE bool readOBJ( + const std::string obj_file_name, + std::vector > & V, + std::vector > & F); + // Eigen Wrappers. These will return true only if the data is perfectly + // "rectangular": All faces are the same degree, all have the same number of + // textures/normals etc. + template < + typename DerivedV, + typename DerivedTC, + typename DerivedCN, + typename DerivedF, + typename DerivedFTC, + typename DerivedFN> + IGL_INLINE bool readOBJ( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& TC, + Eigen::PlainObjectBase& CN, + Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& FTC, + Eigen::PlainObjectBase& FN); + template + IGL_INLINE bool readOBJ( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "readOBJ.cpp" +#endif + +#endif diff --git a/src/igl/readOFF.cpp b/src/igl/readOFF.cpp new file mode 100644 index 000000000..11d7e0efc --- /dev/null +++ b/src/igl/readOFF.cpp @@ -0,0 +1,268 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readOFF.h" +#include "list_to_matrix.h" + +template +IGL_INLINE bool igl::readOFF( + const std::string off_file_name, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & C) +{ + using namespace std; + FILE * off_file = fopen(off_file_name.c_str(),"r"); + if(NULL==off_file) + { + printf("IOError: %s could not be opened...\n",off_file_name.c_str()); + return false; + } + return readOFF(off_file,V,F,N,C); +} + +template +IGL_INLINE bool igl::readOFF( + FILE * off_file, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & C) +{ + using namespace std; + V.clear(); + F.clear(); + N.clear(); + C.clear(); + + // First line is always OFF + char header[1000]; + const std::string OFF("OFF"); + const std::string NOFF("NOFF"); + const std::string COFF("COFF"); + if(fscanf(off_file,"%s\n",header)!=1 + || !( + string(header).compare(0, OFF.length(), OFF)==0 || + string(header).compare(0, COFF.length(), COFF)==0 || + string(header).compare(0,NOFF.length(),NOFF)==0)) + { + printf("Error: readOFF() first line should be OFF or NOFF or COFF, not %s...",header); + fclose(off_file); + return false; + } + bool has_normals = string(header).compare(0,NOFF.length(),NOFF)==0; + bool has_vertexColors = string(header).compare(0,COFF.length(),COFF)==0; + // Second line is #vertices #faces #edges + int number_of_vertices; + int number_of_faces; + int number_of_edges; + char tic_tac_toe; + char line[1000]; + bool still_comments = true; + while(still_comments) + { + fgets(line,1000,off_file); + still_comments = (line[0] == '#' || line[0] == '\n'); + } + sscanf(line,"%d %d %d",&number_of_vertices,&number_of_faces,&number_of_edges); + V.resize(number_of_vertices); + if (has_normals) + N.resize(number_of_vertices); + if (has_vertexColors) + C.resize(number_of_vertices); + F.resize(number_of_faces); + //printf("%s %d %d %d\n",(has_normals ? "NOFF" : "OFF"),number_of_vertices,number_of_faces,number_of_edges); + // Read vertices + for(int i = 0;i= 3) + { + std::vector vertex; + vertex.resize(3); + vertex[0] = x; + vertex[1] = y; + vertex[2] = z; + V[i] = vertex; + + if (has_normals) + { + std::vector normal; + normal.resize(3); + normal[0] = nx; + normal[1] = ny; + normal[2] = nz; + N[i] = normal; + } + + if (has_vertexColors) + { + C[i].resize(3); + C[i][0] = nx / 255.0; + C[i][1] = ny / 255.0; + C[i][2] = nz / 255.0; + } + i++; + }else if( + fscanf(off_file,"%[#]",&tic_tac_toe)==1) + { + char comment[1000]; + fscanf(off_file,"%[^\n]",comment); + }else + { + printf("Error: bad line (%d)\n",i); + if(feof(off_file)) + { + fclose(off_file); + return false; + } + } + } + // Read faces + for(int i = 0;i face; + int valence; + if(fscanf(off_file,"%d",&valence)==1) + { + face.resize(valence); + for(int j = 0;j +IGL_INLINE bool igl::readOFF( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + std::vector > vV; + std::vector > vN; + std::vector > vF; + std::vector > vC; + bool success = igl::readOFF(str,vV,vF,vN,vC); + if(!success) + { + // readOFF(str,vV,vF,vN,vC) should have already printed an error + // message to stderr + return false; + } + bool V_rect = igl::list_to_matrix(vV,V); + if(!V_rect) + { + // igl::list_to_matrix(vV,V) already printed error message to std err + return false; + } + bool F_rect = igl::list_to_matrix(vF,F); + if(!F_rect) + { + // igl::list_to_matrix(vF,F) already printed error message to std err + return false; + } + return true; +} + + +template +IGL_INLINE bool igl::readOFF( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& N) +{ + std::vector > vV; + std::vector > vN; + std::vector > vF; + std::vector > vC; + bool success = igl::readOFF(str,vV,vF,vN,vC); + if(!success) + { + // readOFF(str,vV,vF,vC) should have already printed an error + // message to stderr + return false; + } + bool V_rect = igl::list_to_matrix(vV,V); + if(!V_rect) + { + // igl::list_to_matrix(vV,V) already printed error message to std err + return false; + } + bool F_rect = igl::list_to_matrix(vF,F); + if(!F_rect) + { + // igl::list_to_matrix(vF,F) already printed error message to std err + return false; + } + + if (vN.size()) + { + bool N_rect = igl::list_to_matrix(vN,N); + if(!N_rect) + { + // igl::list_to_matrix(vN,N) already printed error message to std err + return false; + } + } + + //Warning: RGB colors will be returned in the N matrix + if (vC.size()) + { + bool C_rect = igl::list_to_matrix(vC,N); + if(!C_rect) + { + // igl::list_to_matrix(vC,N) already printed error message to std err + return false; + } + } + + return true; +} +#endif + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::readOFF(std::basic_string, std::allocator >, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template bool igl::readOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readOFF, Eigen::Matrix >(std::string, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readOFF.h b/src/igl/readOFF.h new file mode 100644 index 000000000..3d8c41fa5 --- /dev/null +++ b/src/igl/readOFF.h @@ -0,0 +1,86 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READOFF_H +#define IGL_READOFF_H +#include "igl_inline.h" +// History: +// return type changed from void to bool Alec 18 Sept 2011 + +#ifndef IGL_NO_EIGEN +# include +#endif +#include +#include +#include + +namespace igl +{ + + // Read a mesh from an ascii OFF file, filling in vertex positions, normals + // and texture coordinates. Mesh may have faces of any number of degree + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to .obj file + // Outputs: + // V double matrix of vertex positions #V by 3 + // F #F list of face indices into vertex positions + // N list of vertex normals #V by 3 + // C list of rgb color values per vertex #V by 3 + // Returns true on success, false on errors + template + IGL_INLINE bool readOFF( + const std::string off_file_name, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & C); + // Inputs: + // off_file pointer to already opened .off file + // Outputs: + // off_file closed file + template + IGL_INLINE bool readOFF( + FILE * off_file, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & C); + + +#ifndef IGL_NO_EIGEN + // read mesh from a ascii off file + // Inputs: + // str path to .off file + // Outputs: + // V eigen double matrix #V by 3 + // F eigen int matrix #F by 3 + template + IGL_INLINE bool readOFF( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); + + template + IGL_INLINE bool readOFF( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& N); +#endif + +} + +#ifndef IGL_STATIC_LIBRARY +# include "readOFF.cpp" +#endif + +#endif diff --git a/src/igl/readPLY.cpp b/src/igl/readPLY.cpp new file mode 100644 index 000000000..515b3307a --- /dev/null +++ b/src/igl/readPLY.cpp @@ -0,0 +1,224 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readPLY.h" +#include "list_to_matrix.h" +#include "ply.h" +#include + +template < + typename Vtype, + typename Ftype, + typename Ntype, + typename UVtype> +IGL_INLINE bool igl::readPLY( + const std::string filename, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & UV) +{ + using namespace std; + // Largely follows ply2iv.c + FILE * ply_file = fopen(filename.c_str(),"r"); + if(ply_file == NULL) + { + return false; + } + return readPLY(ply_file,V,F,N,UV); +} + +template < + typename Vtype, + typename Ftype, + typename Ntype, + typename UVtype> +IGL_INLINE bool igl::readPLY( + FILE * ply_file, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & UV) +{ + using namespace std; + typedef struct Vertex { + double x,y,z; /* position */ + double nx,ny,nz; /* surface normal */ + double s,t; /* texture coordinates */ + void *other_props; /* other properties */ + } Vertex; + + typedef struct Face { + unsigned char nverts; /* number of vertex indices in list */ + int *verts; /* vertex index list */ + void *other_props; /* other properties */ + } Face; + + igl::ply::PlyProperty vert_props[] = { /* list of property information for a vertex */ + {"x", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,x), 0, 0, 0, 0}, + {"y", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,y), 0, 0, 0, 0}, + {"z", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,z), 0, 0, 0, 0}, + {"nx", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,nx), 0, 0, 0, 0}, + {"ny", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,ny), 0, 0, 0, 0}, + {"nz", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,nz), 0, 0, 0, 0}, + {"s", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,s), 0, 0, 0, 0}, + {"t", PLY_DOUBLE, PLY_DOUBLE, offsetof(Vertex,t), 0, 0, 0, 0}, + }; + + igl::ply::PlyProperty face_props[] = { /* list of property information for a face */ + {"vertex_indices", PLY_INT, PLY_INT, offsetof(Face,verts), + 1, PLY_UCHAR, PLY_UCHAR, offsetof(Face,nverts)}, + }; + + int nelems; + char ** elem_names; + igl::ply::PlyFile * in_ply = igl::ply::ply_read(ply_file,&nelems,&elem_names); + if(in_ply==NULL) + { + return false; + } + + bool has_normals = false; + bool has_texture_coords = false; + igl::ply::PlyProperty **plist; + int nprops; + int elem_count; + plist = ply_get_element_description (in_ply,"vertex", &elem_count, &nprops); + int native_binary_type = igl::ply::get_native_binary_type2(); + if (plist != NULL) + { + /* set up for getting vertex elements */ + ply_get_property (in_ply,"vertex",&vert_props[0]); + ply_get_property (in_ply,"vertex",&vert_props[1]); + ply_get_property (in_ply,"vertex",&vert_props[2]); + for (int j = 0; j < nprops; j++) + { + igl::ply::PlyProperty * prop = plist[j]; + if (igl::ply::equal_strings ("nx", prop->name) + || igl::ply::equal_strings ("ny", prop->name) + || igl::ply::equal_strings ("nz", prop->name)) + { + ply_get_property (in_ply,"vertex",&vert_props[3]); + ply_get_property (in_ply,"vertex",&vert_props[4]); + ply_get_property (in_ply,"vertex",&vert_props[5]); + has_normals = true; + } + if (igl::ply::equal_strings ("s", prop->name) || + igl::ply::equal_strings ("t", prop->name)) + { + ply_get_property(in_ply,"vertex",&vert_props[6]); + ply_get_property(in_ply,"vertex",&vert_props[7]); + has_texture_coords = true; + } + } + // Is this call necessary? + ply_get_other_properties(in_ply,"vertex", + offsetof(Vertex,other_props)); + V.resize(elem_count,std::vector(3)); + if(has_normals) + { + N.resize(elem_count,std::vector(3)); + }else + { + N.resize(0); + } + if(has_texture_coords) + { + UV.resize(elem_count,std::vector(2)); + }else + { + UV.resize(0); + } + + for(int j = 0;j +IGL_INLINE bool igl::readPLY( + const std::string filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & UV) +{ + std::vector > vV; + std::vector > vF; + std::vector > vN; + std::vector > vUV; + if(!readPLY(filename,vV,vF,vN,vUV)) + { + return false; + } + return + list_to_matrix(vV,V) && + list_to_matrix(vF,F) && + list_to_matrix(vN,N) && + list_to_matrix(vUV,UV); +} + +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE bool igl::readPLY( + const std::string filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F) +{ + Eigen::MatrixXd N,UV; + return readPLY(filename,V,F,N,UV); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::readPLY(std::basic_string, std::allocator > const, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); + +template bool igl::readPLY, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &); + +template bool igl::readPLY, Eigen::Matrix >(std::basic_string, std::allocator > const, Eigen::PlainObjectBase > &, Eigen::PlainObjectBase > &); +template bool igl::readPLY, Eigen::Matrix >(std::string, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readPLY.h b/src/igl/readPLY.h new file mode 100644 index 000000000..bcb69eee2 --- /dev/null +++ b/src/igl/readPLY.h @@ -0,0 +1,77 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READPLY_H +#define IGL_READPLY_H +#include "igl_inline.h" +#include +#include +#include +#include + +namespace igl +{ + // Read a mesh from a .ply file. + // + // Inputs: + // filename path to .ply file + // Outputs: + // V #V by 3 list of vertex positions + // F #F list of lists of triangle indices + // N #V by 3 list of vertex normals + // UV #V by 2 list of vertex texture coordinates + // Returns true iff success + template < + typename Vtype, + typename Ftype, + typename Ntype, + typename UVtype> + IGL_INLINE bool readPLY( + const std::string filename, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & UV); + template < + typename Vtype, + typename Ftype, + typename Ntype, + typename UVtype> + // Inputs: + // ply_file pointer to already opened .ply file + // Outputs: + // ply_file closed file + IGL_INLINE bool readPLY( + FILE * ply_file, + std::vector > & V, + std::vector > & F, + std::vector > & N, + std::vector > & UV); + template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedUV> + IGL_INLINE bool readPLY( + const std::string filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & N, + Eigen::PlainObjectBase & UV); + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE bool readPLY( + const std::string filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F); +} +#ifndef IGL_STATIC_LIBRARY +# include "readPLY.cpp" +#endif +#endif + diff --git a/src/igl/readSTL.cpp b/src/igl/readSTL.cpp new file mode 100644 index 000000000..fcdeddcdd --- /dev/null +++ b/src/igl/readSTL.cpp @@ -0,0 +1,290 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readSTL.h" +#include "list_to_matrix.h" + +#include +template +IGL_INLINE bool igl::readSTL( + const std::string & filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & N) +{ + using namespace std; + vector > vV; + vector > vN; + vector > vF; + if(!readSTL(filename,vV,vF,vN)) + { + return false; + } + + if(!list_to_matrix(vV,V)) + { + return false; + } + + if(!list_to_matrix(vF,F)) + { + return false; + } + + if(!list_to_matrix(vN,N)) + { + return false; + } + return true; +} + +template +IGL_INLINE bool igl::readSTL( + const std::string & filename, + std::vector > & V, + std::vector > & F, + std::vector > & N) +{ + using namespace std; + // Should test for ascii + + // Open file, and check for error + FILE * stl_file = fopen(filename.c_str(),"rb"); + if(NULL==stl_file) + { + fprintf(stderr,"IOError: %s could not be opened...\n", + filename.c_str()); + return false; + } + return readSTL(stl_file,V,F,N); +} + +template +IGL_INLINE bool igl::readSTL( + FILE * stl_file, + std::vector > & V, + std::vector > & F, + std::vector > & N) +{ + using namespace std; + //stl_file = freopen(NULL,"rb",stl_file); + if(NULL==stl_file) + { + fprintf(stderr,"IOError: stl file could not be reopened as binary (1) ...\n"); + return false; + } + + V.clear(); + F.clear(); + N.clear(); + + + // Specifically 80 character header + char header[80]; + char solid[80]; + bool is_ascii = true; + if(fread(header,1,80,stl_file) != 80) + { + cerr<<"IOError: too short (1)."<(buf); + fseek(stl_file,0,SEEK_END); + int file_size = ftell(stl_file); + if(file_size == 80 + 4 + (4*12 + 2) * num_faces) + { + is_ascii = false; + }else + { + is_ascii = true; + } + } + + if(is_ascii) + { + // Rewind to end of header + //stl_file = fopen(filename.c_str(),"r"); + //stl_file = freopen(NULL,"r",stl_file); + fseek(stl_file, 0, SEEK_SET); + if(NULL==stl_file) + { + fprintf(stderr,"IOError: stl file could not be reopened as ascii ...\n"); + return false; + } + // Read 80 header + // Eat file name +#ifndef IGL_LINE_MAX +# define IGL_LINE_MAX 2048 +#endif + char name[IGL_LINE_MAX]; + if(NULL==fgets(name,IGL_LINE_MAX,stl_file)) + { + cerr<<"IOError: ascii too short (2)."< n(3); + double nd[3]; + ret = fscanf(stl_file,"%s %s %lg %lg %lg",facet,normal,nd,nd+1,nd+2); + if(string("endsolid") == facet) + { + break; + } + if(ret != 5 || + !(string("facet") == facet || + string("faced") == facet) || + string("normal") != normal) + { + cout<<"facet: "< f; + while(true) + { + char word[IGL_LINE_MAX]; + int ret = fscanf(stl_file,"%s",word); + if(ret == 1 && string("endloop") == word) + { + break; + }else if(ret == 1 && string("vertex") == word) + { + vector v(3); + double vd[3]; + int ret = fscanf(stl_file,"%lg %lg %lg",vd,vd+1,vd+2); + if(ret != 3) + { + cerr<<"IOError: bad format (3)."<(3,0)); + N.resize(num_tri,vector(3,0)); + F.resize(num_tri,vector(3,0)); + for(int t = 0;t<(int)num_tri;t++) + { + // Read normal + float n[3]; + if(fread(n,sizeof(float),3,stl_file)!=3) + { + cerr<<"IOError: bad format (8)."<, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::readSTL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/readSTL.h b/src/igl/readSTL.h new file mode 100644 index 000000000..ca1f7c9e5 --- /dev/null +++ b/src/igl/readSTL.h @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READSTL_H +#define IGL_READSTL_H +#include "igl_inline.h" + +#ifndef IGL_NO_EIGEN +# include +#endif +#include +#include +#include + +namespace igl +{ + // Read a mesh from an ascii/binary stl file. + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Inputs: + // filename path to .stl file + // Outputs: + // V double matrix of vertex positions #V*3 by 3 + // F index matrix of triangle indices #F by 3 + // N double matrix of surface normals #F by 3 + // Returns true on success, false on errors + // + // Example: + // bool success = readSTL(filename,temp_V,F,N); + // remove_duplicate_vertices(temp_V,0,V,SVI,SVJ); + // for_each(F.data(),F.data()+F.size(),[&SVJ](int & f){f=SVJ(f);}); + // writeOBJ("Downloads/cat.obj",V,F); + template + IGL_INLINE bool readSTL( + const std::string & filename, + Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & N); + // Inputs: + // stl_file pointer to already opened .stl file + // Outputs: + // stl_file closed file + template + IGL_INLINE bool readSTL( + FILE * stl_file, + std::vector > & V, + std::vector > & F, + std::vector > & N); + template + IGL_INLINE bool readSTL( + const std::string & filename, + std::vector > & V, + std::vector > & F, + std::vector > & N); +} + +#ifndef IGL_STATIC_LIBRARY +# include "readSTL.cpp" +#endif + +#endif + diff --git a/src/igl/readTGF.cpp b/src/igl/readTGF.cpp new file mode 100644 index 000000000..814395b30 --- /dev/null +++ b/src/igl/readTGF.cpp @@ -0,0 +1,200 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readTGF.h" + +#include + +IGL_INLINE bool igl::readTGF( + const std::string tgf_filename, + std::vector > & C, + std::vector > & E, + std::vector & P, + std::vector > & BE, + std::vector > & CE, + std::vector > & PE) +{ + using namespace std; + // clear output + C.clear(); + E.clear(); + P.clear(); + BE.clear(); + CE.clear(); + PE.clear(); + + FILE * tgf_file = fopen(tgf_filename.c_str(),"r"); + if(NULL==tgf_file) + { + printf("IOError: %s could not be opened\n",tgf_filename.c_str()); + return false; + } + + bool reading_vertices = true; + bool reading_edges = true; + const int MAX_LINE_LENGTH = 500; + char line[MAX_LINE_LENGTH]; + // read until seeing end of file + while(fgets(line,MAX_LINE_LENGTH,tgf_file)!=NULL) + { + // comment signifies end of vertices, next line is start of edges + if(line[0] == '#') + { + if(reading_vertices) + { + reading_vertices = false; + reading_edges = true; + }else if(reading_edges) + { + reading_edges = false; + } + // process vertex line + }else if(reading_vertices) + { + int index; + vector position(3); + int count = + sscanf(line,"%d %lg %lg %lg", + &index, + &position[0], + &position[1], + &position[2]); + if(count != 4) + { + fprintf(stderr,"Error: readTGF.h: bad format in vertex line\n"); + fclose(tgf_file); + return false; + } + // index is ignored since vertices must already be in order + C.push_back(position); + }else if(reading_edges) + { + vector edge(2); + int is_BE = 0; + int is_PE = 0; + int is_CE = 0; + int count = sscanf(line,"%d %d %d %d %d\n", + &edge[0], + &edge[1], + &is_BE, + &is_PE, + &is_CE); + if(count<2) + { + fprintf(stderr,"Error: readTGF.h: bad format in edge line\n"); + fclose(tgf_file); + return false; + } + // .tgf is one indexed + edge[0]--; + edge[1]--; + E.push_back(edge); + if(is_BE == 1) + { + BE.push_back(edge); + } + if(is_PE == 1) + { + // PE should index P + fprintf(stderr, + "Warning: readTGF.h found pseudo edges but does not support " + "them\n"); + } + if(is_CE == 1) + { + // CE should index P + fprintf(stderr, + "Warning: readTGF.h found cage edges but does not support them\n"); + } + }else + { + // ignore faces + } + } + fclose(tgf_file); + // Construct P, indices not in BE + for(int i = 0;i<(int)C.size();i++) + { + bool in_edge = false; + for(int j = 0;j<(int)BE.size();j++) + { + if(i == BE[j][0] || i == BE[j][1]) + { + in_edge = true; + break; + } + } + if(!in_edge) + { + P.push_back(i); + } + } + return true; +} + +#ifndef IGL_NO_EIGEN +#include "list_to_matrix.h" + +IGL_INLINE bool igl::readTGF( + const std::string tgf_filename, + Eigen::MatrixXd & C, + Eigen::MatrixXi & E, + Eigen::VectorXi & P, + Eigen::MatrixXi & BE, + Eigen::MatrixXi & CE, + Eigen::MatrixXi & PE) +{ + std::vector > vC; + std::vector > vE; + std::vector vP; + std::vector > vBE; + std::vector > vCE; + std::vector > vPE; + bool success = readTGF(tgf_filename,vC,vE,vP,vBE,vCE,vPE); + if(!success) + { + return false; + } + + if(!list_to_matrix(vC,C)) + { + return false; + } + if(!list_to_matrix(vE,E)) + { + return false; + } + if(!list_to_matrix(vP,P)) + { + return false; + } + if(!list_to_matrix(vBE,BE)) + { + return false; + } + if(!list_to_matrix(vCE,CE)) + { + return false; + } + if(!list_to_matrix(vPE,PE)) + { + return false; + } + + return true; +} + +IGL_INLINE bool igl::readTGF( + const std::string tgf_filename, + Eigen::MatrixXd & C, + Eigen::MatrixXi & E) +{ + Eigen::VectorXi P; + Eigen::MatrixXi BE,CE,PE; + return readTGF(tgf_filename,C,E,P,BE,CE,PE); +} +#endif diff --git a/src/igl/readTGF.h b/src/igl/readTGF.h new file mode 100644 index 000000000..68c5ddef8 --- /dev/null +++ b/src/igl/readTGF.h @@ -0,0 +1,66 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READTGF_H +#define IGL_READTGF_H +#include "igl_inline.h" + +#include +#include +#ifndef IGL_NO_EIGEN +#include +#endif + +namespace igl +{ + // READTGF + // + // [V,E,P,BE,CE,PE] = readTGF(filename) + // + // Read a graph from a .tgf file + // + // Input: + // filename .tgf file name + // Output: + // V # vertices by 3 list of vertex positions + // E # edges by 2 list of edge indices + // P # point-handles list of point handle indices + // BE # bone-edges by 2 list of bone-edge indices + // CE # cage-edges by 2 list of cage-edge indices + // PE # pseudo-edges by 2 list of pseudo-edge indices + // + // Assumes that graph vertices are 3 dimensional + IGL_INLINE bool readTGF( + const std::string tgf_filename, + std::vector > & C, + std::vector > & E, + std::vector & P, + std::vector > & BE, + std::vector > & CE, + std::vector > & PE); + + #ifndef IGL_NO_EIGEN + IGL_INLINE bool readTGF( + const std::string tgf_filename, + Eigen::MatrixXd & C, + Eigen::MatrixXi & E, + Eigen::VectorXi & P, + Eigen::MatrixXi & BE, + Eigen::MatrixXi & CE, + Eigen::MatrixXi & PE); + IGL_INLINE bool readTGF( + const std::string tgf_filename, + Eigen::MatrixXd & C, + Eigen::MatrixXi & E); + #endif +} + +#ifndef IGL_STATIC_LIBRARY +# include "readTGF.cpp" +#endif + +#endif diff --git a/src/igl/readWRL.cpp b/src/igl/readWRL.cpp new file mode 100644 index 000000000..ce6de3e73 --- /dev/null +++ b/src/igl/readWRL.cpp @@ -0,0 +1,121 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "readWRL.h" +#include + +template +IGL_INLINE bool igl::readWRL( + const std::string wrl_file_name, + std::vector > & V, + std::vector > & F) +{ + using namespace std; + FILE * wrl_file = fopen(wrl_file_name.c_str(),"r"); + if(NULL==wrl_file) + { + printf("IOError: %s could not be opened...",wrl_file_name.c_str()); + return false; + } + return readWRL(wrl_file,V,F); +} + +template +IGL_INLINE bool igl::readWRL( + FILE * wrl_file, + std::vector > & V, + std::vector > & F) +{ + using namespace std; + + char line[1000]; + // Read lines until seeing "point [" + // treat other lines in file as "comments" + bool still_comments = true; + string needle("point ["); + string haystack; + while(still_comments) + { + if(fgets(line,1000,wrl_file) == NULL) + { + std::cerr<<"readWRL, reached EOF without finding \"point [\""< point; + point.resize(3); + point[0] = x; + point[1] = y; + point[2] = z; + V.push_back(point); + //printf("(%g, %g, %g)\n",x,y,z); + }else if(floats_read != 0) + { + printf("ERROR: unrecognized format...\n"); + return false; + } + } + // Read lines until seeing "coordIndex [" + // treat other lines in file as "comments" + still_comments = true; + needle = string("coordIndex ["); + while(still_comments) + { + fgets(line,1000,wrl_file); + haystack = string(line); + still_comments = string::npos == haystack.find(needle); + } + // read F + int ints_read = 1; + while(ints_read > 0) + { + // read new face indices (until hit -1) + vector face; + while(true) + { + // indices are 0-indexed + int i; + ints_read = fscanf(wrl_file," %d,",&i); + if(ints_read > 0) + { + if(i>=0) + { + face.push_back(i); + }else + { + F.push_back(face); + break; + } + }else + { + break; + } + } + } + + + + fclose(wrl_file); + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::readWRL(std::basic_string, std::allocator >, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +#endif diff --git a/src/igl/readWRL.h b/src/igl/readWRL.h new file mode 100644 index 000000000..45c1fa048 --- /dev/null +++ b/src/igl/readWRL.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READWRL_H +#define IGL_READWRL_H +#include "igl_inline.h" + +#include +#include +#include + +namespace igl +{ + // Read a mesh from an ascii wrl file, filling in vertex positions and face + // indices of the first model. Mesh may have faces of any number of degree + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to .wrl file + // Outputs: + // V double matrix of vertex positions #V by 3 + // F #F list of face indices into vertex positions + // Returns true on success, false on errors + template + IGL_INLINE bool readWRL( + const std::string wrl_file_name, + std::vector > & V, + std::vector > & F); + // Inputs: + // wrl_file pointer to already opened .wrl file + // Outputs: + // wrl_file closed file + template + IGL_INLINE bool readWRL( + FILE * wrl_file, + std::vector > & V, + std::vector > & F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "readWRL.cpp" +#endif + +#endif + diff --git a/src/igl/read_triangle_mesh.cpp b/src/igl/read_triangle_mesh.cpp new file mode 100644 index 000000000..b6e52539f --- /dev/null +++ b/src/igl/read_triangle_mesh.cpp @@ -0,0 +1,183 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "read_triangle_mesh.h" + +#include "list_to_matrix.h" +#include "readMESH.h" +#include "readOBJ.h" +#include "readOFF.h" +#include "readSTL.h" +#include "readPLY.h" +#include "readWRL.h" +#include "pathinfo.h" +#include "boundary_facets.h" +#include "polygon_mesh_to_triangle_mesh.h" + +#include +#include + + +template +IGL_INLINE bool igl::read_triangle_mesh( + const std::string str, + std::vector > & V, + std::vector > & F) +{ + using namespace std; + // dirname, basename, extension and filename + string d,b,e,f; + pathinfo(str,d,b,e,f); + // Convert extension to lower case + std::transform(e.begin(), e.end(), e.begin(), ::tolower); + vector > TC, N, C; + vector > FTC, FN; + if(e == "obj") + { + // Annoyingly obj can store 4 coordinates, truncate to xyz for this generic + // read_triangle_mesh + bool success = readOBJ(str,V,TC,N,F,FTC,FN); + for(auto & v : V) + { + v.resize(std::min(v.size(),(size_t)3)); + } + return success; + }else if(e == "off") + { + return readOFF(str,V,F,N,C); + } + cerr<<"Error: "<<__FUNCTION__<<": "<< + str<<" is not a recognized mesh file format."< +IGL_INLINE bool igl::read_triangle_mesh( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + std::string _1,_2,_3,_4; + return read_triangle_mesh(str,V,F,_1,_2,_3,_4); +} + +template +IGL_INLINE bool igl::read_triangle_mesh( + const std::string filename, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F, + std::string & dir, + std::string & base, + std::string & ext, + std::string & name) +{ + using namespace std; + using namespace Eigen; + + // dirname, basename, extension and filename + pathinfo(filename,dir,base,ext,name); + // Convert extension to lower case + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + FILE * fp = fopen(filename.c_str(),"rb"); + return read_triangle_mesh(ext,fp,V,F); +} + +template +IGL_INLINE bool igl::read_triangle_mesh( + const std::string & ext, + FILE * fp, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F) +{ + using namespace std; + using namespace Eigen; + vector > vV,vN,vTC,vC; + vector > vF,vFTC,vFN; + if(ext == "mesh") + { + // Convert extension to lower case + MatrixXi T; + if(!readMESH(fp,V,T,F)) + { + return 1; + } + //if(F.size() > T.size() || F.size() == 0) + { + boundary_facets(T,F); + } + }else if(ext == "obj") + { + if(!readOBJ(fp,vV,vTC,vN,vF,vFTC,vFN)) + { + return false; + } + // Annoyingly obj can store 4 coordinates, truncate to xyz for this generic + // read_triangle_mesh + for(auto & v : vV) + { + v.resize(std::min(v.size(),(size_t)3)); + } + }else if(ext == "off") + { + if(!readOFF(fp,vV,vF,vN,vC)) + { + return false; + } + }else if(ext == "ply") + { + if(!readPLY(fp,vV,vF,vN,vTC)) + { + return false; + } + }else if(ext == "stl") + { + if(!readSTL(fp,vV,vF,vN)) + { + return false; + } + }else if(ext == "wrl") + { + if(!readWRL(fp,vV,vF)) + { + return false; + } + }else + { + cerr<<"Error: unknown extension: "< 0) + { + if(!list_to_matrix(vV,V)) + { + return false; + } + polygon_mesh_to_triangle_mesh(vF,F); + } + return true; +} + +#endif + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::read_triangle_mesh(std::basic_string, std::allocator >, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::string, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template bool igl::read_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&, std::basic_string, std::allocator >&); +#endif diff --git a/src/igl/read_triangle_mesh.h b/src/igl/read_triangle_mesh.h new file mode 100644 index 000000000..26b84c5f1 --- /dev/null +++ b/src/igl/read_triangle_mesh.h @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_READ_TRIANGLE_MESH_H +#define IGL_READ_TRIANGLE_MESH_H +#include "igl_inline.h" + +#ifndef IGL_NO_EIGEN +# include +#endif +#include +#include +#include +// History: +// renamed read -> read_triangle_mesh Daniele 24 June 2014 +// return type changed from void to bool Alec 18 Sept 2011 + +namespace igl +{ + // read mesh from an ascii file with automatic detection of file format. + // supported: obj, off, stl, wrl, ply, mesh) + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to file + // Outputs: + // V eigen double matrix #V by 3 + // F eigen int matrix #F by 3 + // Returns true iff success + template + IGL_INLINE bool read_triangle_mesh( + const std::string str, + std::vector > & V, + std::vector > & F); +#ifndef IGL_NO_EIGEN + template + IGL_INLINE bool read_triangle_mesh( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); + // Outputs: + // dir directory path (see pathinfo.h) + // base base name (see pathinfo.h) + // ext extension (see pathinfo.h) + // name filename (see pathinfo.h) + template + IGL_INLINE bool read_triangle_mesh( + const std::string str, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F, + std::string & dir, + std::string & base, + std::string & ext, + std::string & name); + // Inputs: + // ext file extension + // fp pointer to already opened .ext file + // Outputs: + // fp closed file + template + IGL_INLINE bool read_triangle_mesh( + const std::string & ext, + FILE * fp, + Eigen::PlainObjectBase& V, + Eigen::PlainObjectBase& F); +#endif +} + +#ifndef IGL_STATIC_LIBRARY +# include "read_triangle_mesh.cpp" +#endif + +#endif diff --git a/src/igl/redux.h b/src/igl/redux.h new file mode 100644 index 000000000..38ce9395a --- /dev/null +++ b/src/igl/redux.h @@ -0,0 +1,70 @@ +#ifndef IGL_REDUX_H +#define IGL_REDUX_H +#include +#include +namespace igl +{ + // REDUX Perform reductions on the rows or columns of a SparseMatrix. This is + // _similar_ to DenseBase::redux, but different in two important ways: + // 1. (unstored) Zeros are **not** "visited", however if the first element + // in the column/row does not appear in the first row/column then the + // reduction is assumed to start with zero. In this way, "any", "all", + // "count"(non-zeros) work as expected. This means it is **not** possible + // to use this to count (implicit) zeros. + // 2. This redux is more powerful in the sense that A and B may have + // different types. This makes it possible to count the number of + // non-zeros in a SparseMatrix A into a VectorXi B. + // + // Inputs: + // A m by n sparse matrix + // dim dimension along which to sum (1 or 2) + // func function handle with the prototype `X(Y a, I i, J j, Z b)` where a + // is the running value, b is A(i,j) + // Output: + // S n-long sparse vector (if dim == 1) + // or + // S m-long sparse vector (if dim == 2) + template + inline void redux( + const Eigen::SparseMatrix & A, + const int dim, + const Func & func, + Eigen::PlainObjectBase & B); +} + +// Implementation + +#include "redux.h" +#include "for_each.h" + +template +inline void igl::redux( + const Eigen::SparseMatrix & A, + const int dim, + const Func & func, + Eigen::PlainObjectBase & B) +{ + assert((dim == 1 || dim == 2) && "dim must be 2 or 1"); + // Get size of input + int m = A.rows(); + int n = A.cols(); + // resize output + B = DerivedB::Zero(dim==1?n:m); + const auto func_wrap = [&func,&B,&dim](const int i, const int j, const int v) + { + if(dim == 1) + { + B(j) = i == 0? v : func(B(j),v); + }else + { + B(i) = j == 0? v : func(B(i),v); + } + }; + for_each(A,func_wrap); +} + + +//#ifndef IGL_STATIC_LIBRARY +//# include "redux.cpp" +//#endif +#endif diff --git a/src/igl/remesh_along_isoline.cpp b/src/igl/remesh_along_isoline.cpp new file mode 100644 index 000000000..7164ba8e5 --- /dev/null +++ b/src/igl/remesh_along_isoline.cpp @@ -0,0 +1,160 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "remesh_along_isoline.h" +#include "list_to_matrix.h" + +template < + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedU, + typename DerivedG, + typename DerivedJ, + typename BCtype, + typename DerivedSU, + typename DerivedL> + IGL_INLINE void igl::remesh_along_isoline( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & S, + const typename DerivedS::Scalar val, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & SU, + Eigen::PlainObjectBase & J, + Eigen::SparseMatrix & BC, + Eigen::PlainObjectBase & L) +{ + igl::remesh_along_isoline(V.rows(),F,S,val,G,SU,J,BC,L); + U = BC * V; +} + +template < + typename DerivedF, + typename DerivedS, + typename DerivedG, + typename DerivedJ, + typename BCtype, + typename DerivedSU, + typename DerivedL> + IGL_INLINE void igl::remesh_along_isoline( + const int num_vertices, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & S, + const typename DerivedS::Scalar val, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & SU, + Eigen::PlainObjectBase & J, + Eigen::SparseMatrix & BC, + Eigen::PlainObjectBase & L) +{ + // Lazy implementation using vectors + + //assert(val.size() == 1); + const int isoval_i = 0; + //auto isoval = val(isoval_i); + auto isoval = val; + std::vector > vG; + std::vector vJ; + std::vector vL; + std::vector > vBC; + int Ucount = 0; + for(int i = 0;i isoval; + // Find crossings + const int n = (p+1)%3; + const bool nsign = S(F(f,n)) > isoval; + if(psign != nsign) + { + P[count] = p; + Psign[count] = psign; + // record crossing + count++; + } + } + + assert(count == 0 || count == 2); + switch(count) + { + case 0: + { + // Easy case + std::vector row = {F(f,0),F(f,1),F(f,2)}; + vG.push_back(row); + vJ.push_back(f); + vL.push_back( S(F(f,0))>isoval ? isoval_i+1 : isoval_i ); + break; + } + case 2: + { + // Cut case + // flip so that P[1] is the one-off vertex + if(P[0] == 0 && P[1] == 2) + { + std::swap(P[0],P[1]); + std::swap(Psign[0],Psign[1]); + } + assert(Psign[0] != Psign[1]); + // Create two new vertices + for(int i = 0;i<2;i++) + { + const double bci = (isoval - S(F(f,(P[i]+1)%3)))/ + (S(F(f,P[i]))-S(F(f,(P[i]+1)%3))); + vBC.emplace_back(Ucount,F(f,P[i]),bci); + vBC.emplace_back(Ucount,F(f,(P[i]+1)%3),1.0-bci); + Ucount++; + } + const int v0 = F(f,P[0]); + const int v01 = Ucount-2; + assert(((P[0]+1)%3) == P[1]); + const int v1 = F(f,P[1]); + const int v12 = Ucount-1; + const int v2 = F(f,(P[1]+1)%3); + // v0 + // | \ + // | \ + // | \ + // v01 \ + // | \ + // | \ + // | \ + // v1--v12---v2 + typedef std::vector Row; + {Row row = {v01,v1,v12}; vG.push_back(row);vJ.push_back(f);vL.push_back(Psign[0]?isoval_i:isoval_i+1);} + {Row row = {v12,v2,v01}; vG.push_back(row);vJ.push_back(f);vL.push_back(Psign[1]?isoval_i:isoval_i+1);} + {Row row = {v2,v0,v01}; vG.push_back(row) ;vJ.push_back(f);vL.push_back(Psign[1]?isoval_i:isoval_i+1);} + break; + } + default: assert(false); + } + } + igl::list_to_matrix(vG,G); + igl::list_to_matrix(vJ,J); + igl::list_to_matrix(vL,L); + BC.resize(Ucount,num_vertices); + BC.setFromTriplets(vBC.begin(),vBC.end()); + SU = BC * S; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::remesh_along_isoline, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::SparseMatrix&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/remesh_along_isoline.h b/src/igl/remesh_along_isoline.h new file mode 100644 index 000000000..9a7c553b2 --- /dev/null +++ b/src/igl/remesh_along_isoline.h @@ -0,0 +1,81 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REMESH_ALONG_ISOLINE_H +#define IGL_REMESH_ALONG_ISOLINE_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Given a triangle mesh and a scalar field, remesh so that a given isovalue + // of the scalar field follows (new) edges of the output mesh. Effectively + // running "marching triangles" on mesh, but not in any coherent order. The + // output mesh should be as manifold as the input. + // + // Inputs: + // V #V by dim list of mesh vertex positions + // F #F by 3 list of mesh triangle indices into V + // S #V by 1 list of scalar field + // val value of S to remesh along + // Outputs: + // U #U by dim list of mesh vertex positions #U>=#V + // G #G by 3 list of mesh triangle indices into U, #G>=#F + // SU #U list of scalar field values over new mesh + // J #G list of indices into G revealing birth triangles + // BC #U by #V sparse matrix of barycentric coordinates so that U = BC*V + // L #G list of bools whether scalar field in triangle below or above val + template < + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedU, + typename DerivedG, + typename DerivedJ, + typename BCtype, + typename DerivedSU, + typename DerivedL> + IGL_INLINE void remesh_along_isoline( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & S, + const typename DerivedS::Scalar val, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & SU, + Eigen::PlainObjectBase & J, + Eigen::SparseMatrix & BC, + Eigen::PlainObjectBase & L); + // Input: + // n number of vertices (#V) + template < + typename DerivedF, + typename DerivedS, + typename DerivedG, + typename DerivedJ, + typename BCtype, + typename DerivedSU, + typename DerivedL> + IGL_INLINE void remesh_along_isoline( + const int n, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & S, + const typename DerivedS::Scalar val, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & SU, + Eigen::PlainObjectBase & J, + Eigen::SparseMatrix & BC, + Eigen::PlainObjectBase & L); +} + +#ifndef IGL_STATIC_LIBRARY +# include "remesh_along_isoline.h" +#endif + +#endif diff --git a/src/igl/remove_duplicate_vertices.cpp b/src/igl/remove_duplicate_vertices.cpp new file mode 100644 index 000000000..815ee51f8 --- /dev/null +++ b/src/igl/remove_duplicate_vertices.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "remove_duplicate_vertices.h" +#include "round.h" +#include "unique_rows.h" +#include "colon.h" +#include "slice.h" +#include + +template < + typename DerivedV, + typename DerivedSV, + typename DerivedSVI, + typename DerivedSVJ> +IGL_INLINE void igl::remove_duplicate_vertices( + const Eigen::MatrixBase& V, + const double epsilon, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SVI, + Eigen::PlainObjectBase& SVJ) +{ + if(epsilon > 0) + { + DerivedV rV,rSV; + round((V/(10.0*epsilon)).eval(),rV); + unique_rows(rV,rSV,SVI,SVJ); + slice(V,SVI,colon(0,V.cols()-1),SV); + }else + { + unique_rows(V,SV,SVI,SVJ); + } +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedSV, + typename DerivedSVI, + typename DerivedSVJ, + typename DerivedSF> +IGL_INLINE void igl::remove_duplicate_vertices( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const double epsilon, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SVI, + Eigen::PlainObjectBase& SVJ, + Eigen::PlainObjectBase& SF) +{ + using namespace Eigen; + using namespace std; + remove_duplicate_vertices(V,epsilon,SV,SVI,SVJ); + SF.resizeLike(F); + for(int f = 0;f, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_duplicate_vertices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/remove_duplicate_vertices.h b/src/igl/remove_duplicate_vertices.h new file mode 100644 index 000000000..9276ba729 --- /dev/null +++ b/src/igl/remove_duplicate_vertices.h @@ -0,0 +1,65 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REMOVE_DUPLICATE_VERTICES_H +#define IGL_REMOVE_DUPLICATE_VERTICES_H +#include "igl_inline.h" +#include +namespace igl +{ + // REMOVE_DUPLICATE_VERTICES Remove duplicate vertices upto a uniqueness + // tolerance (epsilon) + // + // Inputs: + // V #V by dim list of vertex positions + // epsilon uniqueness tolerance (significant digit), can probably think of + // this as a tolerance on L1 distance + // Outputs: + // SV #SV by dim new list of vertex positions + // SVI #V by 1 list of indices so SV = V(SVI,:) + // SVJ #SV by 1 list of indices so V = SV(SVJ,:) + // + // Example: + // % Mesh in (V,F) + // [SV,SVI,SVJ] = remove_duplicate_vertices(V,1e-7); + // % remap faces + // SF = SVJ(F); + // + template < + typename DerivedV, + typename DerivedSV, + typename DerivedSVI, + typename DerivedSVJ> + IGL_INLINE void remove_duplicate_vertices( + const Eigen::MatrixBase& V, + const double epsilon, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SVI, + Eigen::PlainObjectBase& SVJ); + // Wrapper that also remaps given faces (F) --> (SF) so that SF index SV + template < + typename DerivedV, + typename DerivedF, + typename DerivedSV, + typename DerivedSVI, + typename DerivedSVJ, + typename DerivedSF> + IGL_INLINE void remove_duplicate_vertices( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const double epsilon, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SVI, + Eigen::PlainObjectBase& SVJ, + Eigen::PlainObjectBase& SF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "remove_duplicate_vertices.cpp" +#endif + +#endif diff --git a/src/igl/remove_duplicates.cpp b/src/igl/remove_duplicates.cpp new file mode 100644 index 000000000..3e97a39e1 --- /dev/null +++ b/src/igl/remove_duplicates.cpp @@ -0,0 +1,88 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "remove_duplicates.h" +#include + +//template +//IGL_INLINE void igl::remove_duplicates( +// const Eigen::Matrix &V, +// const Eigen::Matrix &F, +// Eigen::Matrix &NV, +// Eigen::Matrix &NF, +// Eigen::Matrix &I, +// const double epsilon) +template +IGL_INLINE void igl::remove_duplicates( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::Matrix &I, + const double epsilon) +{ + using namespace std; + //// build collapse map + int n = V.rows(); + + I = Eigen::Matrix(n); + I[0] = 0; + + bool *VISITED = new bool[n]; + for (int i =0; i face; + NF.resizeLike(F); + for (int i =0; i , Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::Matrix::Scalar, -1, 1, 0, -1, 1>&, double); +template void igl::remove_duplicates, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::Matrix::Scalar, -1, 1, 0, -1, 1>&, double); +#endif diff --git a/src/igl/remove_duplicates.h b/src/igl/remove_duplicates.h new file mode 100644 index 000000000..0e1249ce9 --- /dev/null +++ b/src/igl/remove_duplicates.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REMOVE_DUPLICATES_H +#define IGL_REMOVE_DUPLICATES_H +#include "igl_inline.h" + +#include +namespace igl +{ + // [ NV, NF ] = remove_duplicates( V,F,epsilon ) + // Merge the duplicate vertices from V, fixing the topology accordingly + // + // Input: + // V,F: mesh description + // epsilon: minimal distance to consider two vertices identical + // + // Output: + // NV, NF: new mesh without duplicate vertices + +// template +// IGL_INLINE void remove_duplicates( +// const Eigen::Matrix &V, +// const Eigen::Matrix &F, +// Eigen::Matrix &NV, +// Eigen::Matrix &NF, +// Eigen::Matrix &I, +// const double epsilon = 2.2204e-15); + + template + IGL_INLINE void remove_duplicates( + const Eigen::PlainObjectBase &V, + const Eigen::PlainObjectBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::Matrix &I, + const double epsilon = 2.2204e-15); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "remove_duplicates.cpp" +#endif + +#endif diff --git a/src/igl/remove_unreferenced.cpp b/src/igl/remove_unreferenced.cpp new file mode 100644 index 000000000..3ee8260aa --- /dev/null +++ b/src/igl/remove_unreferenced.cpp @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "remove_unreferenced.h" +#include "slice.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF, + typename DerivedI> +IGL_INLINE void igl::remove_unreferenced( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::PlainObjectBase &I) +{ + Eigen::Matrix J; + remove_unreferenced(V,F,NV,NF,I,J); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF, + typename DerivedI, + typename DerivedJ> +IGL_INLINE void igl::remove_unreferenced( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::PlainObjectBase &I, + Eigen::PlainObjectBase &J) +{ + using namespace std; + const size_t n = V.rows(); + remove_unreferenced(n,F,I,J); + NF = F; + std::for_each(NF.data(),NF.data()+NF.size(), + [&I](typename DerivedNF::Scalar & a){a=I(a);}); + slice(V,J,1,NV); +} + +template < + typename DerivedF, + typename DerivedI, + typename DerivedJ> +IGL_INLINE void igl::remove_unreferenced( + const size_t n, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &I, + Eigen::PlainObjectBase &J) +{ + // Mark referenced vertices + typedef Eigen::Matrix MatrixXb; + MatrixXb mark = MatrixXb::Zero(n,1); + for(int i=0; i, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +// generated by autoexplicit.sh +// generated by autoexplicit.sh +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +//template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix >(unsigned long, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::remove_unreferenced, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/remove_unreferenced.h b/src/igl/remove_unreferenced.h new file mode 100644 index 000000000..2284190ab --- /dev/null +++ b/src/igl/remove_unreferenced.h @@ -0,0 +1,82 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +// remove_unreferenced.h +// Preview3D +// +// Created by Daniele Panozzo on 17/11/11. + +#ifndef IGL_REMOVE_UNREFERENCED_H +#define IGL_REMOVE_UNREFERENCED_H +#include "igl_inline.h" + +#include +namespace igl +{ + // Remove unreferenced vertices from V, updating F accordingly + // + // Input: + // V #V by dim list of mesh vertex positions + // F #F by ss list of simplices (Values of -1 are quitely skipped) + // Outputs: + // NV #NV by dim list of mesh vertex positions + // NF #NF by ss list of simplices + // IM #V by 1 list of indices such that: NF = IM(F) and NT = IM(T) + // and V(find(IM<=size(NV,1)),:) = NV + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF, + typename DerivedI> + IGL_INLINE void remove_unreferenced( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::PlainObjectBase &I); + template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF, + typename DerivedI, + typename DerivedJ> + IGL_INLINE void remove_unreferenced( + const Eigen::MatrixBase &V, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &NV, + Eigen::PlainObjectBase &NF, + Eigen::PlainObjectBase &I, + Eigen::PlainObjectBase &J); + // Inputs: + // n number of vertices (possibly greater than F.maxCoeff()+1) + // F #F by ss list of simplices + // Outputs: + // IM #V by 1 list of indices such that: NF = IM(F) and NT = IM(T) + // and V(find(IM<=size(NV,1)),:) = NV + // J #RV by 1 list, such that RV = V(J,:) + // + template < + typename DerivedF, + typename DerivedI, + typename DerivedJ> + IGL_INLINE void remove_unreferenced( + const size_t n, + const Eigen::MatrixBase &F, + Eigen::PlainObjectBase &I, + Eigen::PlainObjectBase &J); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "remove_unreferenced.cpp" +#endif + +#endif diff --git a/src/igl/reorder.cpp b/src/igl/reorder.cpp new file mode 100644 index 000000000..ee74b2096 --- /dev/null +++ b/src/igl/reorder.cpp @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "reorder.h" +#include "SortableRow.h" +#ifndef IGL_NO_EIGEN +#include +#endif + +// This implementation is O(n), but also uses O(n) extra memory +template< class T > +IGL_INLINE void igl::reorder( + const std::vector & unordered, + std::vector const & index_map, + std::vector & ordered) +{ + // copy for the reorder according to index_map, because unsorted may also be + // sorted + std::vector copy = unordered; + ordered.resize(index_map.size()); + for(int i = 0; i<(int)index_map.size();i++) + { + ordered[i] = copy[index_map[i]]; + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::reorder(std::vector > const&, std::vector > const&, std::vector >&); +template void igl::reorder(std::vector > const&, std::vector > const&, std::vector >&); +template void igl::reorder(std::vector > const&, std::vector > const&, std::vector >&); +# ifndef IGL_NO_EIGEN + template void igl::reorder > >(std::vector >, std::allocator > > > const&, std::vector > const&, std::vector >, std::allocator > > >&); + template void igl::reorder > >(std::vector >, std::allocator > > > const&, std::vector > const&, std::vector >, std::allocator > > >&); +# endif +template void igl::reorder(std::vector > const&, std::vector > const&, std::vector >&); +#ifdef WIN32 +template void igl::reorder(class std::vector > const &,class std::vector > const &,class std::vector > &); +#endif +#endif diff --git a/src/igl/reorder.h b/src/igl/reorder.h new file mode 100644 index 000000000..50f7d7b0f --- /dev/null +++ b/src/igl/reorder.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REORDER_H +#define IGL_REORDER_H +#include "igl_inline.h" +#include +// For size_t +#include +#include + +namespace igl +{ + // Act like matlab's Y = X(I) for std vectors + // where I contains a vector of indices so that after, + // Y[j] = X[I[j]] for index j + // this implies that Y.size() == I.size() + // X and Y are allowed to be the same reference + template< class T > + IGL_INLINE void reorder( + const std::vector & unordered, + std::vector const & index_map, + std::vector & ordered); +} + +#ifndef IGL_STATIC_LIBRARY +# include "reorder.cpp" +#endif + +#endif diff --git a/src/igl/repdiag.cpp b/src/igl/repdiag.cpp new file mode 100644 index 000000000..283ae1e75 --- /dev/null +++ b/src/igl/repdiag.cpp @@ -0,0 +1,97 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "repdiag.h" +#include + +template +IGL_INLINE void igl::repdiag( + const Eigen::SparseMatrix& A, + const int d, + Eigen::SparseMatrix& B) +{ + using namespace std; + using namespace Eigen; + int m = A.rows(); + int n = A.cols(); +#if false + vector > IJV; + IJV.reserve(A.nonZeros()*d); + // Loop outer level + for (int k=0; k::InnerIterator it(A,k); it; ++it) + { + for(int i = 0;i(i*m+it.row(),i*n+it.col(),it.value())); + } + } + } + B.resize(m*d,n*d); + B.setFromTriplets(IJV.begin(),IJV.end()); +#else + // This will not work for RowMajor + B.resize(m*d,n*d); + Eigen::VectorXi per_col = Eigen::VectorXi::Zero(n*d); + for (int k=0; k::InnerIterator it(A,k); it; ++it) + { + for(int r = 0;r::InnerIterator it(A,k); it; ++it) + { + B.insert(it.row()+mr,k+nr) = it.value(); + } + } + } + B.makeCompressed(); +#endif +} + +template +IGL_INLINE void igl::repdiag( + const Eigen::Matrix & A, + const int d, + Eigen::Matrix & B) +{ + int m = A.rows(); + int n = A.cols(); + B.resize(m*d,n*d); + B.array() *= 0; + for(int i = 0;i +IGL_INLINE Mat igl::repdiag(const Mat & A, const int d) +{ + Mat B; + repdiag(A,d,B); + return B; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::repdiag(Eigen::SparseMatrix const&, int, Eigen::SparseMatrix&); +// generated by autoexplicit.sh +template Eigen::SparseMatrix igl::repdiag >(Eigen::SparseMatrix const&, int); +#endif diff --git a/src/igl/repdiag.h b/src/igl/repdiag.h new file mode 100644 index 000000000..779443374 --- /dev/null +++ b/src/igl/repdiag.h @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REPDIAG_H +#define IGL_REPDIAG_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + // REPDIAG repeat a matrix along the diagonal a certain number of times, so + // that if A is a m by n matrix and we want to repeat along the diagonal d + // times, we get a m*d by n*d matrix B such that: + // B( (k*m+1):(k*m+1+m-1), (k*n+1):(k*n+1+n-1)) = A + // for k from 0 to d-1 + // + // Inputs: + // A m by n matrix we are repeating along the diagonal. May be dense or + // sparse + // d number of times to repeat A along the diagonal + // Outputs: + // B m*d by n*d matrix with A repeated d times along the diagonal, + // will be dense or sparse to match A + // + + // Sparse version + template + IGL_INLINE void repdiag( + const Eigen::SparseMatrix& A, + const int d, + Eigen::SparseMatrix& B); + // Dense version + template + IGL_INLINE void repdiag( + const Eigen::Matrix & A, + const int d, + Eigen::Matrix & B); + // Wrapper with B as output + template + IGL_INLINE Mat repdiag(const Mat & A, const int d); +} + +#ifndef IGL_STATIC_LIBRARY +# include "repdiag.cpp" +#endif + +#endif diff --git a/src/igl/repmat.cpp b/src/igl/repmat.cpp new file mode 100644 index 000000000..f79840444 --- /dev/null +++ b/src/igl/repmat.cpp @@ -0,0 +1,67 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "repmat.h" + +template +IGL_INLINE void igl::repmat( + const Eigen::MatrixBase & A, + const int r, + const int c, + Eigen::PlainObjectBase & B) +{ + assert(r>0); + assert(c>0); + // Make room for output + B.resize(r*A.rows(),c*A.cols()); + + // copy tiled blocks + for(int i = 0;i +IGL_INLINE void igl::repmat( + const Eigen::SparseMatrix & A, + const int r, + const int c, + Eigen::SparseMatrix & B) +{ + assert(r>0); + assert(c>0); + B.resize(r*A.rows(),c*A.cols()); + B.reserve(r*c*A.nonZeros()); + for(int i = 0;i::InnerIterator it(A,k); it; ++it) + { + B.insert(i*A.rows()+it.row(),j*A.cols() + it.col()) = it.value(); + } + } + } + } + B.finalize(); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::repmat, Eigen::Matrix >(Eigen::MatrixBase > const&, int, int, Eigen::PlainObjectBase >&); +template void igl::repmat, Eigen::Matrix >(Eigen::MatrixBase > const&, int, int, Eigen::PlainObjectBase >&); +template void igl::repmat, Eigen::Matrix >(Eigen::MatrixBase > const&, int, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/repmat.h b/src/igl/repmat.h new file mode 100644 index 000000000..1509875aa --- /dev/null +++ b/src/igl/repmat.h @@ -0,0 +1,53 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_REPMAT_H +#define IGL_REPMAT_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include + +namespace igl +{ + // At least for Dense matrices this is replaced by `replicate` e.g., dst = src.replicate(n,m); + // http://forum.kde.org/viewtopic.php?f=74&t=90876#p173517 + + // Ideally this is a super overloaded function that behaves just like + // matlab's repmat + + // Replicate and tile a matrix + // + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // A m by n input matrix + // r number of row-direction copies + // c number of col-direction copies + // Outputs: + // B r*m by c*n output matrix + // + template + IGL_INLINE void repmat( + const Eigen::MatrixBase & A, + const int r, + const int c, + Eigen::PlainObjectBase & B); + template + IGL_INLINE void repmat( + const Eigen::SparseMatrix & A, + const int r, + const int c, + Eigen::SparseMatrix & B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "repmat.cpp" +#endif + +#endif diff --git a/src/igl/resolve_duplicated_faces.cpp b/src/igl/resolve_duplicated_faces.cpp new file mode 100644 index 000000000..3b26b8ecc --- /dev/null +++ b/src/igl/resolve_duplicated_faces.cpp @@ -0,0 +1,96 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// + +#include "resolve_duplicated_faces.h" + +#include "slice.h" +#include "unique_simplices.h" + +template< + typename DerivedF1, + typename DerivedF2, + typename DerivedJ > +IGL_INLINE void igl::resolve_duplicated_faces( + const Eigen::PlainObjectBase& F1, + Eigen::PlainObjectBase& F2, + Eigen::PlainObjectBase& J) { + + //typedef typename DerivedF1::Scalar Index; + Eigen::VectorXi IA,IC; + DerivedF1 uF; + igl::unique_simplices(F1,uF,IA,IC); + + const size_t num_faces = F1.rows(); + const size_t num_unique_faces = uF.rows(); + assert((size_t) IA.rows() == num_unique_faces); + // faces on top of each unique face + std::vector > uF2F(num_unique_faces); + // signed counts + Eigen::VectorXi counts = Eigen::VectorXi::Zero(num_unique_faces); + Eigen::VectorXi ucounts = Eigen::VectorXi::Zero(num_unique_faces); + // loop over all faces + for (size_t i=0; i kept_faces; + for (size_t i=0; i 0) { + kept_faces.push_back(abs(fid)-1); + found = true; + break; + } + } + assert(found); + } else if (counts[i] == -1) { + bool found = false; + for (auto fid : uF2F[i]) { + if (fid < 0) { + kept_faces.push_back(abs(fid)-1); + found = true; + break; + } + } + assert(found); + } else { + assert(counts[i] == 0); + } + } + + const size_t num_kept = kept_faces.size(); + J.resize(num_kept, 1); + std::copy(kept_faces.begin(), kept_faces.end(), J.data()); + igl::slice(F1, J, 1, F2); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::resolve_duplicated_faces, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::resolve_duplicated_faces, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::resolve_duplicated_faces, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::resolve_duplicated_faces, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::resolve_duplicated_faces, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>>(class Eigen::PlainObjectBase> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/resolve_duplicated_faces.h b/src/igl/resolve_duplicated_faces.h new file mode 100644 index 000000000..03ea3d6e6 --- /dev/null +++ b/src/igl/resolve_duplicated_faces.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Qingnan Zhou +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +// +#ifndef IGL_COPYLEFT_RESOLVE_DUPLICATED_FACES +#define IGL_COPYLEFT_RESOLVE_DUPLICATED_FACES + +#include "igl_inline.h" +#include + +namespace igl { + + // Resolve duplicated faces according to the following rules per unique face: + // + // 1. If the number of positively oriented faces equals the number of + // negatively oriented faces, remove all duplicated faces at this triangle. + // 2. If the number of positively oriented faces equals the number of + // negatively oriented faces plus 1, keeps one of the positively oriented + // face. + // 3. If the number of positively oriented faces equals the number of + // negatively oriented faces minus 1, keeps one of the negatively oriented + // face. + // 4. If the number of postively oriented faces differ with the number of + // negativley oriented faces by more than 1, the mesh is not orientable. + // An exception will be thrown. + // + // Inputs: + // F1 #F1 by 3 array of input faces. + // + // Outputs: + // F2 #F2 by 3 array of output faces without duplicated faces. + // J #F2 list of indices into F1. + template< + typename DerivedF1, + typename DerivedF2, + typename DerivedJ > + IGL_INLINE void resolve_duplicated_faces( + const Eigen::PlainObjectBase& F1, + Eigen::PlainObjectBase& F2, + Eigen::PlainObjectBase& J); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "resolve_duplicated_faces.cpp" +#endif + +#endif diff --git a/src/igl/rgb_to_hsv.cpp b/src/igl/rgb_to_hsv.cpp new file mode 100644 index 000000000..fbf32bfec --- /dev/null +++ b/src/igl/rgb_to_hsv.cpp @@ -0,0 +1,102 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "rgb_to_hsv.h" + +template +IGL_INLINE void igl::rgb_to_hsv(const R * rgb, H * hsv) +{ + // http://en.literateprograms.org/RGB_to_HSV_color_space_conversion_%28C%29 + R rgb_max = 0.0; + R rgb_min = 1.0; + rgb_max = (rgb[0]>rgb_max?rgb[0]:rgb_max); + rgb_max = (rgb[1]>rgb_max?rgb[1]:rgb_max); + rgb_max = (rgb[2]>rgb_max?rgb[2]:rgb_max); + rgb_min = (rgb[0]rgb_max?rgb_n[0]:rgb_max); + rgb_max = (rgb_n[1]>rgb_max?rgb_n[1]:rgb_max); + rgb_max = (rgb_n[2]>rgb_max?rgb_n[2]:rgb_max); + rgb_min = 1; + rgb_min = (rgb_n[0]rgb_max?rgb_n[0]:rgb_max); + rgb_max = (rgb_n[1]>rgb_max?rgb_n[1]:rgb_max); + rgb_max = (rgb_n[2]>rgb_max?rgb_n[2]:rgb_max); + rgb_min = 1; + rgb_min = (rgb_n[0] +IGL_INLINE void igl::rgb_to_hsv( + const Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & H) +{ + assert(R.cols() == 3); + H.resizeLike(R); + for(typename DerivedR::Index r = 0;r(float const*, double*); +template void igl::rgb_to_hsv(double const*, double*); +template void igl::rgb_to_hsv, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::rgb_to_hsv, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::rgb_to_hsv, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/rgb_to_hsv.h b/src/igl/rgb_to_hsv.h new file mode 100644 index 000000000..ed695ad60 --- /dev/null +++ b/src/igl/rgb_to_hsv.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_RGB_TO_HSV_H +#define IGL_RGB_TO_HSV_H +#include "igl_inline.h" +#include +namespace igl +{ + // Convert RGB to HSV + // + // Inputs: + // r red value ([0,1]) + // g green value ([0,1]) + // b blue value ([0,1]) + // Outputs: + // h hue value (degrees: [0,360]) + // s saturation value ([0,1]) + // v value value ([0,1]) + template + IGL_INLINE void rgb_to_hsv(const R * rgb, H * hsv); + template + IGL_INLINE void rgb_to_hsv( + const Eigen::PlainObjectBase & R, + Eigen::PlainObjectBase & H); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "rgb_to_hsv.cpp" +#endif + +#endif + diff --git a/src/igl/rotate_by_quat.cpp b/src/igl/rotate_by_quat.cpp new file mode 100644 index 000000000..b77c812ea --- /dev/null +++ b/src/igl/rotate_by_quat.cpp @@ -0,0 +1,55 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "rotate_by_quat.h" + +#include "quat_conjugate.h" +#include "quat_mult.h" +#include "normalize_quat.h" +#include + +template +IGL_INLINE void igl::rotate_by_quat( + const Q_type *v, + const Q_type *q, + Q_type *out) +{ + // Quaternion form of v, copy data in v, (as a result out can be same pointer + // as v) + Q_type quat_v[4] = {v[0],v[1],v[2],0}; + + // normalize input + Q_type normalized_q[4]; + +#ifndef NDEBUG + bool normalized = +#endif + igl::normalize_quat(q,normalized_q); +#ifndef NDEBUG + assert(normalized); +#endif + + // Conjugate of q + Q_type q_conj[4]; + igl::quat_conjugate(normalized_q,q_conj); + + // Rotate of vector v by quaternion q is: + // q*v*conj(q) + // Compute q*v + Q_type q_mult_quat_v[4]; + igl::quat_mult(normalized_q,quat_v,q_mult_quat_v); + // Compute (q*v) * conj(q) + igl::quat_mult(q_mult_quat_v,q_conj,out); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::rotate_by_quat(double const*, double const*, double*); +// generated by autoexplicit.sh +template void igl::rotate_by_quat(float const*, float const*, float*); +#endif diff --git a/src/igl/rotate_by_quat.h b/src/igl/rotate_by_quat.h new file mode 100644 index 000000000..b45204839 --- /dev/null +++ b/src/igl/rotate_by_quat.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ROTATE_BY_QUAT_H +#define IGL_ROTATE_BY_QUAT_H +#include "igl_inline.h" + +namespace igl +{ + // Compute rotation of a given vector/point by a quaternion + // A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), + // such that q = x*i + y*j + z*k + w + // Inputs: + // v input 3d point/vector + // q input quaternion + // Outputs: + // out result of rotation, allowed to be same as v + template + IGL_INLINE void rotate_by_quat( + const Q_type *v, + const Q_type *q, + Q_type *out); +}; + +#ifndef IGL_STATIC_LIBRARY +# include "rotate_by_quat.cpp" +#endif + +#endif diff --git a/src/igl/rotate_vectors.cpp b/src/igl/rotate_vectors.cpp new file mode 100644 index 000000000..b236d79e6 --- /dev/null +++ b/src/igl/rotate_vectors.cpp @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "rotate_vectors.h" +IGL_INLINE Eigen::MatrixXd igl::rotate_vectors( + const Eigen::MatrixXd& V, + const Eigen::VectorXd& A, + const Eigen::MatrixXd& B1, + const Eigen::MatrixXd& B2) +{ + Eigen::MatrixXd RV(V.rows(),V.cols()); + + for (unsigned i=0; i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_ROTATE_VECTORS_H +#define IGL_ROTATE_VECTORS_H +#include "igl_inline.h" +#include +namespace igl +{ + // Rotate the vectors V by A radiants on the tangent plane spanned by B1 and + // B2 + // + // Inputs: + // V #V by 3 eigen Matrix of vectors + // A #V eigen vector of rotation angles or a single angle to be applied + // to all vectors + // B1 #V by 3 eigen Matrix of base vector 1 + // B2 #V by 3 eigen Matrix of base vector 2 + // + // Output: + // Returns the rotated vectors + // + IGL_INLINE Eigen::MatrixXd rotate_vectors( + const Eigen::MatrixXd& V, + const Eigen::VectorXd& A, + const Eigen::MatrixXd& B1, + const Eigen::MatrixXd& B2); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "rotate_vectors.cpp" +#endif + +#endif diff --git a/src/igl/rotation_matrix_from_directions.cpp b/src/igl/rotation_matrix_from_directions.cpp new file mode 100644 index 000000000..4ecc1a5d9 --- /dev/null +++ b/src/igl/rotation_matrix_from_directions.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo , Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "rotation_matrix_from_directions.h" +#include +#include + +template +IGL_INLINE Eigen::Matrix igl::rotation_matrix_from_directions( + const Eigen::Matrix v0, + const Eigen::Matrix v1) +{ + Eigen::Matrix rotM; + const double epsilon=1e-8; + Scalar dot=v0.normalized().dot(v1.normalized()); + ///control if there is no rotation + if ((v0-v1).norm()::Identity(); + return rotM; + } + if ((v0+v1).norm()::Identity(); + rotM(0,0) = 1.; + std::cerr<<"igl::rotation_matrix_from_directions: rotating around x axis by 180o"< axis; + axis=v0.cross(v1); + axis.normalize(); + + ///construct rotation matrix + Scalar u=axis(0); + Scalar v=axis(1); + Scalar w=axis(2); + Scalar phi=acos(dot); + Scalar rcos = cos(phi); + Scalar rsin = sin(phi); + + rotM(0,0) = rcos + u*u*(1-rcos); + rotM(1,0) = w * rsin + v*u*(1-rcos); + rotM(2,0) = -v * rsin + w*u*(1-rcos); + rotM(0,1) = -w * rsin + u*v*(1-rcos); + rotM(1,1) = rcos + v*v*(1-rcos); + rotM(2,1) = u * rsin + w*v*(1-rcos); + rotM(0,2) = v * rsin + u*w*(1-rcos); + rotM(1,2) = -u * rsin + v*w*(1-rcos); + rotM(2,2) = rcos + w*w*(1-rcos); + + return rotM; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template Eigen::Matrix igl::rotation_matrix_from_directions(const Eigen::Matrix, const Eigen::Matrix); +#endif diff --git a/src/igl/rotation_matrix_from_directions.h b/src/igl/rotation_matrix_from_directions.h new file mode 100644 index 000000000..ca1397272 --- /dev/null +++ b/src/igl/rotation_matrix_from_directions.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson, Daniele Panozzo, Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ROTATION_MATRIX_FROM_DIRECTIONS_H +#define IGL_ROTATION_MATRIX_FROM_DIRECTIONS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Given 2 vectors centered on origin calculate the rotation matrix from + // first to the second + // + // Inputs: + // v0 3D column vector + // v1 3D column vector + // Output: + // 3 by 3 rotation matrix that takes v0 to v1 + // + template + IGL_INLINE Eigen::Matrix rotation_matrix_from_directions( + const Eigen::Matrix v0, + const Eigen::Matrix v1); +} + +#ifndef IGL_STATIC_LIBRARY +#include "rotation_matrix_from_directions.cpp" +#endif +#endif diff --git a/src/igl/round.cpp b/src/igl/round.cpp new file mode 100644 index 000000000..acde47316 --- /dev/null +++ b/src/igl/round.cpp @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "round.h" +#include + + +// http://stackoverflow.com/a/485549 +template +IGL_INLINE DerivedX igl::round(const DerivedX r) +{ + return (r > 0.0) ? std::floor(r + 0.5) : std::ceil(r - 0.5); +} + +template < typename DerivedX, typename DerivedY> +IGL_INLINE void igl::round( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y) +{ + Y.resizeLike(X); + // loop over rows + for(int i = 0;i, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::round, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/round.h b/src/igl/round.h new file mode 100644 index 000000000..cdaac731b --- /dev/null +++ b/src/igl/round.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ROUND_H +#define IGL_ROUND_H +#include "igl_inline.h" +#include +namespace igl +{ + // Round a scalar value + // + // Inputs: + // x number + // Returns x rounded to integer + template + DerivedX round(const DerivedX r); + // Round a given matrix to nearest integers + // + // Inputs: + // X m by n matrix of scalars + // Outputs: + // Y m by n matrix of rounded integers + template < typename DerivedX, typename DerivedY> + IGL_INLINE void round( + const Eigen::PlainObjectBase& X, + Eigen::PlainObjectBase& Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "round.cpp" +#endif + +#endif diff --git a/src/igl/rows_to_matrix.cpp b/src/igl/rows_to_matrix.cpp new file mode 100644 index 000000000..14c369e40 --- /dev/null +++ b/src/igl/rows_to_matrix.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "rows_to_matrix.h" + +#include +#include + +#include "max_size.h" +#include "min_size.h" + +template +IGL_INLINE bool igl::rows_to_matrix(const std::vector & V,Mat & M) +{ + // number of columns + int m = V.size(); + if(m == 0) + { + fprintf(stderr,"Error: rows_to_matrix() list is empty()\n"); + return false; + } + // number of rows + int n = igl::min_size(V); + if(n != igl::max_size(V)) + { + fprintf(stderr,"Error: rows_to_matrix()" + " list elements are not all the same size\n"); + return false; + } + assert(n != -1); + // Resize output + M.resize(m,n); + + // Loop over rows + int i = 0; + typename std::vector::const_iterator iter = V.begin(); + while(iter != V.end()) + { + M.row(i) = V[i]; + // increment index and iterator + i++; + iter++; + } + + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/rows_to_matrix.h b/src/igl/rows_to_matrix.h new file mode 100644 index 000000000..8f62b5a2d --- /dev/null +++ b/src/igl/rows_to_matrix.h @@ -0,0 +1,34 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_ROWS_TO_MATRIX_H +#define IGL_ROWS_TO_MATRIX_H +#include "igl_inline.h" +#include +namespace igl +{ + // Convert a list (std::vector) of row vectors of the same length to a matrix + // Template: + // Row row vector type, must implement: + // .size() + // Mat Matrix type, must implement: + // .resize(m,n) + // .row(i) = Row + // Inputs: + // V a m-long list of vectors of size n + // Outputs: + // M an m by n matrix + // Returns true on success, false on errors + template + IGL_INLINE bool rows_to_matrix(const std::vector & V,Mat & M); +} + +#ifndef IGL_STATIC_LIBRARY +# include "rows_to_matrix.cpp" +#endif + +#endif diff --git a/src/igl/sample_edges.cpp b/src/igl/sample_edges.cpp new file mode 100644 index 000000000..a20df1426 --- /dev/null +++ b/src/igl/sample_edges.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sample_edges.h" + +IGL_INLINE void igl::sample_edges( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & E, + const int k, + Eigen::MatrixXd & S) +{ + using namespace Eigen; + // Resize output + S.resize(V.rows() + k * E.rows(),V.cols()); + // Copy V at front of S + S.block(0,0,V.rows(),V.cols()) = V; + + // loop over edges + for(int i = 0;i +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SAMPLE_EDGES_H +#define IGL_SAMPLE_EDGES_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Compute samples_per_edge extra points along each edge in E defined over + // vertices of V. + // + // Inputs: + // V vertices over which edges are defined, # vertices by dim + // E edge list, # edges by 2 + // k number of extra samples to be computed along edge not + // including start and end points + // Output: + // S sampled vertices, size less than # edges * (2+k) by dim always begins + // with V so that E is also defined over S + IGL_INLINE void sample_edges( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & E, + const int k, + Eigen::MatrixXd & S); +} +#ifndef IGL_STATIC_LIBRARY +# include "sample_edges.cpp" +#endif + +#endif diff --git a/src/igl/seam_edges.cpp b/src/igl/seam_edges.cpp new file mode 100644 index 000000000..8cbca143c --- /dev/null +++ b/src/igl/seam_edges.cpp @@ -0,0 +1,211 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Yotam Gingold +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "seam_edges.h" +#include +#include +#include + +// Yotam has verified that this function produces the exact same output as +// `find_seam_fast.py` for `cow_triangled.obj`. +template < + typename DerivedV, + typename DerivedTC, + typename DerivedF, + typename DerivedFTC, + typename Derivedseams, + typename Derivedboundaries, + typename Derivedfoldovers> +IGL_INLINE void igl::seam_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& TC, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FTC, + Eigen::PlainObjectBase& seams, + Eigen::PlainObjectBase& boundaries, + Eigen::PlainObjectBase& foldovers) +{ + // Assume triangles. + assert( F.cols() == 3 ); + assert( F.cols() == FTC.cols() ); + assert( F.rows() == FTC.rows() ); + + // Assume 2D texture coordinates (foldovers tests). + assert( TC.cols() == 2 ); + typedef Eigen::Matrix< typename DerivedTC::Scalar, 2, 1 > Vector2S; + // Computes the orientation of `c` relative to the line between `a` and `b`. + // Assumes 2D vector input. + // Based on: https://www.cs.cmu.edu/~quake/robust.html + const auto& Orientation = []( + const Vector2S& a, + const Vector2S& b, + const Vector2S& c ) -> typename DerivedTC::Scalar + { + const Vector2S row0 = a - c; + const Vector2S row1 = b - c; + return row0(0)*row1(1) - row1(0)*row0(1); + }; + + seams .setZero( 3*F.rows(), 4 ); + boundaries.setZero( 3*F.rows(), 2 ); + foldovers .setZero( 3*F.rows(), 4 ); + + int num_seams = 0; + int num_boundaries = 0; + int num_foldovers = 0; + + // A map from a pair of vertex indices to the index (face and endpoints) + // into face_position_indices. + // The following should be true for every key, value pair: + // key == face_position_indices[ value ] + // This gives us a "reverse map" so that we can look up other face + // attributes based on position edges. + // The value are written in the format returned by numpy.where(), + // which stores multi-dimensional indices such as array[a0,b0], array[a1,b1] + // as ( (a0,a1), (b0,b1) ). + + // We need to make a hash function for our directed edges. + // We'll use i*V.rows() + j. + typedef std::pair< typename DerivedF::Scalar, typename DerivedF::Scalar > + directed_edge; + const int numV = V.rows(); + const int numF = F.rows(); + const auto& edge_hasher = + [numV]( directed_edge const& e ) { return e.first*numV + e.second; }; + // When we pass a hash function object, we also need to specify the number of + // buckets. The Euler characteristic says that the number of undirected edges + // is numV + numF -2*genus. + std::unordered_map,decltype(edge_hasher) > + directed_position_edge2face_position_index(2*( numV + numF ), edge_hasher); + for( int fi = 0; fi < F.rows(); ++fi ) + { + for( int i = 0; i < 3; ++i ) + { + const int j = ( i+1 ) % 3; + directed_position_edge2face_position_index[ + std::make_pair( F(fi,i), F(fi,j) ) ] = std::make_pair( fi, i ); + } + } + + // First find all undirected position edges (collect a canonical orientation + // of the directed edges). + std::unordered_set< directed_edge, decltype( edge_hasher ) > + undirected_position_edges( numV + numF, edge_hasher ); + for( const auto& el : directed_position_edge2face_position_index ) + { + // The canonical orientation is the one where the smaller of + // the two vertex indices is first. + undirected_position_edges.insert( std::make_pair( + std::min( el.first.first, el.first.second ), + std::max( el.first.first, el.first.second ) ) ); + } + + // Now we will iterate over all position edges. + // Seam edges are the edges whose two opposite directed edges have different + // texcoord indices (or one doesn't exist at all in the case of a mesh + // boundary). + for( const auto& vp_edge : undirected_position_edges ) + { + // We should only see canonical edges, + // where the first vertex index is smaller. + assert( vp_edge.first < vp_edge.second ); + + const auto vp_edge_reverse = std::make_pair(vp_edge.second, vp_edge.first); + // If it and its opposite exist as directed edges, check if their + // texture coordinate indices match. + if( directed_position_edge2face_position_index.count( vp_edge ) && + directed_position_edge2face_position_index.count( vp_edge_reverse ) ) + { + const auto forwards = + directed_position_edge2face_position_index[ vp_edge ]; + const auto backwards = + directed_position_edge2face_position_index[ vp_edge_reverse ]; + + // NOTE: They should never be equal. + assert( forwards != backwards ); + + // If the texcoord indices match (are similarly flipped), + // this edge is not a seam. It could be a foldover. + if( + std::make_pair( + FTC( forwards.first, forwards.second ), + FTC( forwards.first, ( forwards.second+1 ) % 3 ) ) + == + std::make_pair( + FTC( backwards.first, ( backwards.second+1 ) % 3 ), + FTC( backwards.first, backwards.second ) )) + { + // Check for foldovers in UV space. + // Get the edge (a,b) and the two opposite vertices's texture + // coordinates. + const Vector2S a = TC.row( FTC( forwards.first, forwards.second ) ); + const Vector2S b = + TC.row( FTC( forwards.first, (forwards.second+1) % 3 ) ); + const Vector2S c_forwards = + TC.row( FTC( forwards .first, (forwards .second+2) % 3 ) ); + const Vector2S c_backwards = + TC.row( FTC( backwards.first, (backwards.second+2) % 3 ) ); + // If the opposite vertices' texture coordinates fall on the same side + // of the edge, we have a UV-space foldover. + const auto orientation_forwards = Orientation( a, b, c_forwards ); + const auto orientation_backwards = Orientation( a, b, c_backwards ); + if( ( orientation_forwards > 0 && orientation_backwards > 0 ) || + ( orientation_forwards < 0 && orientation_backwards < 0 ) + ) { + foldovers( num_foldovers, 0 ) = forwards.first; + foldovers( num_foldovers, 1 ) = forwards.second; + foldovers( num_foldovers, 2 ) = backwards.first; + foldovers( num_foldovers, 3 ) = backwards.second; + num_foldovers += 1; + } + } + // Otherwise, we have a non-matching seam edge. + else + { + seams( num_seams, 0 ) = forwards.first; + seams( num_seams, 1 ) = forwards.second; + seams( num_seams, 2 ) = backwards.first; + seams( num_seams, 3 ) = backwards.second; + num_seams += 1; + } + } + // Otherwise, the edge and its opposite aren't both in the directed edges. + // One of them should be. + else if( directed_position_edge2face_position_index.count( vp_edge ) ) + { + const auto forwards = directed_position_edge2face_position_index[vp_edge]; + boundaries( num_boundaries, 0 ) = forwards.first; + boundaries( num_boundaries, 1 ) = forwards.second; + num_boundaries += 1; + } else if( + directed_position_edge2face_position_index.count( vp_edge_reverse ) ) + { + const auto backwards = + directed_position_edge2face_position_index[ vp_edge_reverse ]; + boundaries( num_boundaries, 0 ) = backwards.first; + boundaries( num_boundaries, 1 ) = backwards.second; + num_boundaries += 1; + } else { + // This should never happen! One of these two must have been seen. + assert( + directed_position_edge2face_position_index.count( vp_edge ) || + directed_position_edge2face_position_index.count( vp_edge_reverse ) + ); + } + } + + seams .conservativeResize( num_seams, Eigen::NoChange_t() ); + boundaries.conservativeResize( num_boundaries, Eigen::NoChange_t() ); + foldovers .conservativeResize( num_foldovers, Eigen::NoChange_t() ); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::seam_edges, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/seam_edges.h b/src/igl/seam_edges.h new file mode 100644 index 000000000..15c82826c --- /dev/null +++ b/src/igl/seam_edges.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Yotam Gingold +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SEAM_EDGES_H +#define IGL_SEAM_EDGES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Finds all UV-space boundaries of a mesh. + // + // Inputs: + // V #V by dim list of positions of the input mesh. + // TC #TC by 2 list of 2D texture coordinates of the input mesh + // F #F by 3 list of triange indices into V representing a + // manifold-with-boundary triangle mesh + // FTC #F by 3 list of indices into TC for each corner + // Outputs: + // seams Edges where the forwards and backwards directions have different + // texture coordinates, as a #seams-by-4 matrix of indices. Each row is + // organized as [ forward_face_index, forward_face_vertex_index, + // backwards_face_index, backwards_face_vertex_index ] such that one side + // of the seam is the edge: + // F[ seams( i, 0 ), seams( i, 1 ) ], F[ seams( i, 0 ), (seams( i, 1 ) + 1) % 3 ] + // and the other side is the edge: + // F[ seams( i, 2 ), seams( i, 3 ) ], F[ seams( i, 2 ), (seams( i, 3 ) + 1) % 3 ] + // boundaries Edges with only one incident triangle, as a #boundaries-by-2 + // matrix of indices. Each row is organized as + // [ face_index, face_vertex_index ] + // such that the edge is: + // F[ boundaries( i, 0 ), boundaries( i, 1 ) ], F[ boundaries( i, 0 ), (boundaries( i, 1 ) + 1) % 3 ] + // foldovers Edges where the two incident triangles fold over each other + // in UV-space, as a #foldovers-by-4 matrix of indices. + // Each row is organized as [ forward_face_index, forward_face_vertex_index, + // backwards_face_index, backwards_face_vertex_index ] + // such that one side of the foldover is the edge: + // F[ foldovers( i, 0 ), foldovers( i, 1 ) ], F[ foldovers( i, 0 ), (foldovers( i, 1 ) + 1) % 3 ] + // and the other side is the edge: + // F[ foldovers( i, 2 ), foldovers( i, 3 ) ], F[ foldovers( i, 2 ), (foldovers( i, 3 ) + 1) % 3 ] + template < + typename DerivedV, + typename DerivedTC, + typename DerivedF, + typename DerivedFTC, + typename Derivedseams, + typename Derivedboundaries, + typename Derivedfoldovers> + IGL_INLINE void seam_edges( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& TC, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& FTC, + Eigen::PlainObjectBase& seams, + Eigen::PlainObjectBase& boundaries, + Eigen::PlainObjectBase& foldovers); +} +#ifndef IGL_STATIC_LIBRARY +# include "seam_edges.cpp" +#endif +#endif diff --git a/src/igl/segment_segment_intersect.cpp b/src/igl/segment_segment_intersect.cpp new file mode 100644 index 000000000..8438c0496 --- /dev/null +++ b/src/igl/segment_segment_intersect.cpp @@ -0,0 +1,67 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Francisca Gil Ureta +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "segment_segment_intersect.h" + +#include + +template +IGL_INLINE bool igl::segments_intersect( + const Eigen::PlainObjectBase &p, + const Eigen::PlainObjectBase &r, + const Eigen::PlainObjectBase &q, + const Eigen::PlainObjectBase &s, + double &a_t, + double &a_u, + double eps +) +{ + // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect + // Search intersection between two segments + // p + t*r : t \in [0,1] + // q + u*s : u \in [0,1] + + // p + t * r = q + u * s // x s + // t(r x s) = (q - p) x s + // t = (q - p) x s / (r x s) + + // (r x s) ~ 0 --> directions are parallel, they will never cross + Eigen::RowVector3d rxs = r.cross(s); + if (rxs.norm() <= eps) + return false; + + int sign; + + double u; + // u = (q − p) × r / (r × s) + Eigen::RowVector3d u1 = (q - p).cross(r); + sign = ((u1.dot(rxs)) > 0) ? 1 : -1; + u = u1.norm() / rxs.norm(); + u = u * sign; + + if ((u - 1.) > eps || u < -eps) + return false; + + double t; + // t = (q - p) x s / (r x s) + Eigen::RowVector3d t1 = (q - p).cross(s); + sign = ((t1.dot(rxs)) > 0) ? 1 : -1; + t = t1.norm() / rxs.norm(); + t = t * sign; + + if (t < -eps || fabs(t) < eps) + return false; + + a_t = t; + a_u = u; + + return true; +}; + +#ifdef IGL_STATIC_LIBRARY +template bool igl::segments_intersect, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, double&, double&, double); +#endif diff --git a/src/igl/segment_segment_intersect.h b/src/igl/segment_segment_intersect.h new file mode 100644 index 000000000..714629215 --- /dev/null +++ b/src/igl/segment_segment_intersect.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Francisca Gil Ureta +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_SEGMENT_SEGMENT_INTERSECT_H +#define IGL_SEGMENT_SEGMENT_INTERSECT_H + + +#include "igl_inline.h" +#include +namespace igl +{ + + // Determine whether two line segments A,B intersect + // A: p + t*r : t \in [0,1] + // B: q + u*s : u \in [0,1] + // Inputs: + // p 3-vector origin of segment A + // r 3-vector direction of segment A + // q 3-vector origin of segment B + // s 3-vector direction of segment B + // eps precision + // Outputs: + // t scalar point of intersection along segment A, t \in [0,1] + // u scalar point of intersection along segment B, u \in [0,1] + // Returns true if intersection + template + IGL_INLINE bool segments_intersect( + const Eigen::PlainObjectBase &p, + const Eigen::PlainObjectBase &r, + const Eigen::PlainObjectBase &q, + const Eigen::PlainObjectBase &s, + double &t, + double &u, + double eps = 1e-6 + ); + +} +#ifndef IGL_STATIC_LIBRARY +# include "segment_segment_intersect.cpp" +#endif +#endif //IGL_SEGMENT_SEGMENT_INTERSECT_H diff --git a/src/igl/serialize.h b/src/igl/serialize.h new file mode 100644 index 000000000..54967293b --- /dev/null +++ b/src/igl/serialize.h @@ -0,0 +1,1258 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SERIALIZE_H +#define IGL_SERIALIZE_H + +// ----------------------------------------------------------------------------- +// Functions to save and load a serialization of fundamental c++ data types to +// and from a binary file. STL containers, Eigen matrix types and nested data +// structures are also supported. To serialize a user defined class implement +// the interface Serializable or SerializableBase. +// +// See also: xml/serialize_xml.h +// ----------------------------------------------------------------------------- +// TODOs: +// * arbitrary pointer graph structures +// ----------------------------------------------------------------------------- + +// Known issues: This is not written in libigl-style so it isn't (easily) +// "dualized" into the static library. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "igl_inline.h" + +// non-intrusive serialization helper macros + +#define SERIALIZE_TYPE(Type,Params) \ +namespace igl { namespace serialization { \ + void _serialization(bool s,Type& obj,std::vector& buffer) {Params} \ + template<> inline void serialize(const Type& obj,std::vector& buffer) { \ + _serialization(true,const_cast(obj),buffer); \ + } \ + template<> inline void deserialize(Type& obj,const std::vector& buffer) { \ + _serialization(false,obj,const_cast&>(buffer)); \ + } \ +}} + +#define SERIALIZE_TYPE_SOURCE(Type,Params) \ +namespace igl { namespace serialization { \ + void _serialization(bool s,Type& obj,std::vector& buffer) {Params} \ + void _serialize(const Type& obj,std::vector& buffer) { \ + _serialization(true,const_cast(obj),buffer); \ + } \ + void _deserialize(Type& obj,const std::vector& buffer) { \ + _serialization(false,obj,const_cast&>(buffer)); \ + } \ +}} + +#define SERIALIZE_MEMBER(Object) igl::serializer(s,obj.Object,std::string(#Object),buffer); +#define SERIALIZE_MEMBER_NAME(Object,Name) igl::serializer(s,obj.Object,std::string(Name),buffer); + + +namespace igl +{ + struct IndexedPointerBase; + + // Serializes the given object either to a file or to a provided buffer + // Templates: + // T type of the object to serialize + // Inputs: + // obj object to serialize + // objectName unique object name,used for the identification + // overwrite set to true to overwrite an existing file + // filename name of the file containing the serialization + // Outputs: + // buffer binary serialization + // + template + inline bool serialize(const T& obj,const std::string& filename); + template + inline bool serialize(const T& obj,const std::string& objectName,const std::string& filename,bool overwrite = false); + template + inline bool serialize(const T& obj,const std::string& objectName,std::vector& buffer); + template + inline bool serialize(const T& obj,const std::string& objectName,std::vector& buffer); + + // Deserializes the given data from a file or buffer back to the provided object + // + // Templates: + // T type of the object to serialize + // Inputs: + // buffer binary serialization + // objectName unique object name, used for the identification + // filename name of the file containing the serialization + // Outputs: + // obj object to load back serialization to + // + template + inline bool deserialize(T& obj,const std::string& filename); + template + inline bool deserialize(T& obj,const std::string& objectName,const std::string& filename); + template + inline bool deserialize(T& obj,const std::string& objectName,const std::vector& buffer); + + // Wrapper to expose both, the de- and serialization as one function + // + template + inline bool serializer(bool serialize,T& obj,const std::string& filename); + template + inline bool serializer(bool serialize,T& obj,const std::string& objectName,const std::string& filename,bool overwrite = false); + template + inline bool serializer(bool serialize,T& obj,const std::string& objectName,std::vector& buffer); + + // User defined types have to either overload the function igl::serialization::serialize() + // and igl::serialization::deserialize() for their type (non-intrusive serialization): + // + // namespace igl { namespace serialization + // { + // template<> + // inline void serialize(const UserType& obj,std::vector& buffer) { + // ::igl::serialize(obj.var,"var",buffer); + // } + // + // template<> + // inline void deserialize(UserType& obj,const std::vector& buffer) { + // ::igl::deserialize(obj.var,"var",buffer); + // } + // }} + // + // or use this macro for convenience: + // + // SERIALIZE_TYPE(UserType, + // SERIALIZE_MEMBER(var) + // ) + // + // or to derive from the class Serializable and add their the members + // in InitSerialization like the following: + // + // class UserType : public igl::Serializable { + // + // int var; + // + // void InitSerialization() { + // this->Add(var,"var"); + // } + // }; + + // Base interface for user defined types + struct SerializableBase + { + virtual void Serialize(std::vector& buffer) const = 0; + virtual void Deserialize(const std::vector& buffer) = 0; + }; + + // Convenient interface for user defined types + class Serializable: public SerializableBase + { + private: + + template + struct SerializationObject : public SerializableBase + { + bool Binary; + std::string Name; + std::unique_ptr Object; + + void Serialize(std::vector& buffer) const override { + igl::serialize(*Object,Name,buffer); + } + + void Deserialize(const std::vector& buffer) override { + igl::deserialize(*Object,Name,buffer); + } + }; + + mutable bool initialized; + mutable std::vector objects; + + public: + + // You **MUST** Override this function to add your member variables which + // should be serialized + // + // http://stackoverflow.com/a/6634382/148668 + virtual void InitSerialization() = 0; + + // Following functions can be overridden to handle the specific events. + // Return false to prevent the de-/serialization of an object. + inline virtual bool PreSerialization() const; + inline virtual void PostSerialization() const; + inline virtual bool PreDeserialization(); + inline virtual void PostDeserialization(); + + // Default implementation of SerializableBase interface + inline void Serialize(std::vector& buffer) const override final; + inline void Deserialize(const std::vector& buffer) override final; + + // Default constructor, destructor, assignment and copy constructor + inline Serializable(); + inline Serializable(const Serializable& obj); + inline ~Serializable(); + inline Serializable& operator=(const Serializable& obj); + + // Use this function to add your variables which should be serialized + template + inline void Add(T& obj,std::string name,bool binary = false); + }; + + // structure for pointer handling + struct IndexedPointerBase + { + enum { BEGIN,END } Type; + size_t Index; + }; + template + struct IndexedPointer: public IndexedPointerBase + { + const T* Object; + }; + + // internal functions + namespace serialization + { + // compile time type checks + template + struct is_stl_container { static const bool value = false; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + + template + struct is_eigen_type { static const bool value = false; }; + template + struct is_eigen_type > { static const bool value = true; }; + template + struct is_eigen_type > { static const bool value = true; }; + + template + struct is_smart_ptr { static const bool value = false; }; + template + struct is_smart_ptr > { static const bool value = true; }; + template + struct is_smart_ptr > { static const bool value = true; }; + template + struct is_smart_ptr > { static const bool value = true; }; + + template + struct is_serializable { + static const bool value = std::is_fundamental::value || std::is_same::value || std::is_enum::value || std::is_base_of::value + || is_stl_container::value || is_eigen_type::value || std::is_pointer::value || serialization::is_smart_ptr::value; + }; + + // non serializable types + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter); + + // fundamental types + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter); + + // std::string + inline size_t getByteSize(const std::string& obj); + inline void serialize(const std::string& obj,std::vector& buffer,std::vector::iterator& iter); + inline void deserialize(std::string& obj,std::vector::const_iterator& iter); + + // enum types + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter); + + // SerializableBase + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter); + + // stl containers + // std::pair + template + inline size_t getByteSize(const std::pair& obj); + template + inline void serialize(const std::pair& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(std::pair& obj,std::vector::const_iterator& iter); + + // std::vector + template + inline size_t getByteSize(const std::vector& obj); + template + inline void serialize(const std::vector& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(std::vector& obj,std::vector::const_iterator& iter); + template + inline void deserialize(std::vector& obj,std::vector::const_iterator& iter); + + // std::set + template + inline size_t getByteSize(const std::set& obj); + template + inline void serialize(const std::set& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(std::set& obj,std::vector::const_iterator& iter); + + // std::map + template + inline size_t getByteSize(const std::map& obj); + template + inline void serialize(const std::map& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(std::map& obj,std::vector::const_iterator& iter); + + // std::list + template + inline size_t getByteSize(const std::list& obj); + template + inline void serialize(const std::list& obj, std::vector& buffer, std::vector::iterator& iter); + template + inline void deserialize(std::list& obj, std::vector::const_iterator& iter); + + // Eigen types + template + inline size_t getByteSize(const Eigen::Matrix& obj); + template + inline void serialize(const Eigen::Matrix& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(Eigen::Matrix& obj,std::vector::const_iterator& iter); + + template + inline size_t getByteSize(const Eigen::SparseMatrix& obj); + template + inline void serialize(const Eigen::SparseMatrix& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(Eigen::SparseMatrix& obj,std::vector::const_iterator& iter); + + template + inline size_t getByteSize(const Eigen::Quaternion& obj); + template + inline void serialize(const Eigen::Quaternion& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(Eigen::Quaternion& obj,std::vector::const_iterator& iter); + + // raw pointers + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter); + + // std::shared_ptr and std::unique_ptr + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj); + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter); + template class T0, typename T1> + inline typename std::enable_if >::value>::type deserialize(T0& obj,std::vector::const_iterator& iter); + + // std::weak_ptr + template + inline size_t getByteSize(const std::weak_ptr& obj); + template + inline void serialize(const std::weak_ptr& obj,std::vector& buffer,std::vector::iterator& iter); + template + inline void deserialize(std::weak_ptr& obj,std::vector::const_iterator& iter); + + // functions to overload for non-intrusive serialization + template + inline void serialize(const T& obj,std::vector& buffer); + template + inline void deserialize(T& obj,const std::vector& buffer); + + // helper functions + template + inline void updateMemoryMap(T& obj,size_t size); + } +} + +// Always include inlines for these functions + +// IMPLEMENTATION + +namespace igl +{ + template + inline bool serialize(const T& obj,const std::string& filename) + { + return serialize(obj,"obj",filename,true); + } + + template + inline bool serialize(const T& obj,const std::string& objectName,const std::string& filename,bool overwrite) + { + bool success = false; + + std::vector buffer; + + std::ios_base::openmode mode = std::ios::out | std::ios::binary; + + if(overwrite) + mode |= std::ios::trunc; + else + mode |= std::ios::app; + + std::ofstream file(filename.c_str(),mode); + + if(file.is_open()) + { + serialize(obj,objectName,buffer); + + file.write(&buffer[0],buffer.size()); + + file.close(); + + success = true; + } + else + { + std::cerr << "serialization: file " << filename << " not found!" << std::endl; + } + + return success; + } + + template + inline bool serialize(const T& obj,const std::string& objectName,std::vector& buffer) + { + // serialize object data + size_t size = serialization::getByteSize(obj); + std::vector tmp(size); + auto it = tmp.begin(); + serialization::serialize(obj,tmp,it); + + std::string objectType(typeid(obj).name()); + size_t newObjectSize = tmp.size(); + size_t newHeaderSize = serialization::getByteSize(objectName) + serialization::getByteSize(objectType) + sizeof(size_t); + size_t curSize = buffer.size(); + size_t newSize = curSize + newHeaderSize + newObjectSize; + + buffer.resize(newSize); + + std::vector::iterator iter = buffer.begin()+curSize; + + // serialize object header (name/type/size) + serialization::serialize(objectName,buffer,iter); + serialization::serialize(objectType,buffer,iter); + serialization::serialize(newObjectSize,buffer,iter); + + // copy serialized data to buffer + iter = std::copy(tmp.begin(),tmp.end(),iter); + + return true; + } + + template + inline bool deserialize(T& obj,const std::string& filename) + { + return deserialize(obj,"obj",filename); + } + + template + inline bool deserialize(T& obj,const std::string& objectName,const std::string& filename) + { + bool success = false; + + std::ifstream file(filename.c_str(),std::ios::binary); + + if(file.is_open()) + { + file.seekg(0,std::ios::end); + std::streamoff size = file.tellg(); + file.seekg(0,std::ios::beg); + + std::vector buffer(size); + file.read(&buffer[0],size); + + deserialize(obj,objectName,buffer); + file.close(); + + success = true; + } + else + { + std::cerr << "serialization: file " << filename << " not found!" << std::endl; + } + + return success; + } + + template + inline bool deserialize(T& obj,const std::string& objectName,const std::vector& buffer) + { + bool success = false; + + // find suitable object header + auto objectIter = buffer.cend(); + auto iter = buffer.cbegin(); + while(iter != buffer.end()) + { + std::string name; + std::string type; + size_t size; + serialization::deserialize(name,iter); + serialization::deserialize(type,iter); + serialization::deserialize(size,iter); + + if(name == objectName && type == typeid(obj).name()) + { + objectIter = iter; + //break; // find first suitable object header + } + + iter+=size; + } + + if(objectIter != buffer.end()) + { + serialization::deserialize(obj,objectIter); + success = true; + } + else + { + obj = T(); + } + + return success; + } + + // Wrapper function which combines both, de- and serialization + template + inline bool serializer(bool s,T& obj,const std::string& filename) + { + return s ? serialize(obj,filename) : deserialize(obj,filename); + } + + template + inline bool serializer(bool s,T& obj,const std::string& objectName,const std::string& filename,bool overwrite) + { + return s ? serialize(obj,objectName,filename,overwrite) : deserialize(obj,objectName,filename); + } + + template + inline bool serializer(bool s,T& obj,const std::string& objectName,std::vector& buffer) + { + return s ? serialize(obj,objectName,buffer) : deserialize(obj,objectName,buffer); + } + + inline bool Serializable::PreSerialization() const + { + return true; + } + + inline void Serializable::PostSerialization() const + { + } + + inline bool Serializable::PreDeserialization() + { + return true; + } + + inline void Serializable::PostDeserialization() + { + } + + inline void Serializable::Serialize(std::vector& buffer) const + { + if(this->PreSerialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(const auto& v : objects) + { + v->Serialize(buffer); + } + + this->PostSerialization(); + } + } + + inline void Serializable::Deserialize(const std::vector& buffer) + { + if(this->PreDeserialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(auto& v : objects) + { + v->Deserialize(buffer); + } + + this->PostDeserialization(); + } + } + + inline Serializable::Serializable() + { + initialized = false; + } + + inline Serializable::Serializable(const Serializable& obj) + { + initialized = false; + objects.clear(); + } + + inline Serializable::~Serializable() + { + initialized = false; + objects.clear(); + } + + inline Serializable& Serializable::operator=(const Serializable& obj) + { + if(this != &obj) + { + if(initialized) + { + initialized = false; + objects.clear(); + } + } + return *this; + } + + template + inline void Serializable::Add(T& obj,const std::string name,bool binary) + { + auto object = new SerializationObject(); + object->Binary = binary; + object->Name = name; + object->Object = std::unique_ptr(&obj); + + objects.push_back(object); + } + + namespace serialization + { + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + return sizeof(std::vector::size_type); + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + // data + std::vector tmp; + serialize<>(obj,tmp); + + // size + size_t size = buffer.size(); + serialization::serialize(tmp.size(),buffer,iter); + size_t cur = iter - buffer.begin(); + + buffer.resize(size+tmp.size()); + iter = buffer.begin()+cur; + iter = std::copy(tmp.begin(),tmp.end(),iter); + } + + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter) + { + std::vector::size_type size; + serialization::deserialize<>(size,iter); + + std::vector tmp; + tmp.resize(size); + std::copy(iter,iter+size,tmp.begin()); + + deserialize<>(obj,tmp); + iter += size; + } + + // fundamental types + + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + return sizeof(T); + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + //serialization::updateMemoryMap(obj,sizeof(T)); + const uint8_t* ptr = reinterpret_cast(&obj); + iter = std::copy(ptr,ptr+sizeof(T),iter); + } + + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter) + { + uint8_t* ptr = reinterpret_cast(&obj); + std::copy(iter,iter+sizeof(T),ptr); + iter += sizeof(T); + } + + // std::string + + inline size_t getByteSize(const std::string& obj) + { + return getByteSize(obj.length())+obj.length()*sizeof(uint8_t); + } + + inline void serialize(const std::string& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.length(),buffer,iter); + for(const auto& cur : obj) + { + serialization::serialize(cur,buffer,iter); + } + } + + inline void deserialize(std::string& obj,std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size,iter); + + std::string str(size,'\0'); + for(size_t i=0; i + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + return sizeof(T); + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + const uint8_t* ptr = reinterpret_cast(&obj); + iter = std::copy(ptr,ptr+sizeof(T),iter); + } + + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter) + { + uint8_t* ptr = reinterpret_cast(&obj); + std::copy(iter,iter+sizeof(T),ptr); + iter += sizeof(T); + } + + // SerializableBase + + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + return sizeof(std::vector::size_type); + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + // data + std::vector tmp; + obj.Serialize(tmp); + + // size + size_t size = buffer.size(); + serialization::serialize(tmp.size(),buffer,iter); + size_t cur = iter - buffer.begin(); + + buffer.resize(size+tmp.size()); + iter = buffer.begin()+cur; + iter = std::copy(tmp.begin(),tmp.end(),iter); + } + + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter) + { + std::vector::size_type size; + serialization::deserialize(size,iter); + + std::vector tmp; + tmp.resize(size); + std::copy(iter,iter+size,tmp.begin()); + + obj.Deserialize(tmp); + iter += size; + } + + // STL containers + + // std::pair + + template + inline size_t getByteSize(const std::pair& obj) + { + return getByteSize(obj.first)+getByteSize(obj.second); + } + + template + inline void serialize(const std::pair& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.first,buffer,iter); + serialization::serialize(obj.second,buffer,iter); + } + + template + inline void deserialize(std::pair& obj,std::vector::const_iterator& iter) + { + serialization::deserialize(obj.first,iter); + serialization::deserialize(obj.second,iter); + } + + // std::vector + + template + inline size_t getByteSize(const std::vector& obj) + { + return std::accumulate(obj.begin(),obj.end(),sizeof(size_t),[](const size_t& acc,const T1& cur) { return acc+getByteSize(cur); }); + } + + template + inline void serialize(const std::vector& obj,std::vector& buffer,std::vector::iterator& iter) + { + size_t size = obj.size(); + serialization::serialize(size,buffer,iter); + for(const T1& cur : obj) + { + serialization::serialize(cur,buffer,iter); + } + } + + template + inline void deserialize(std::vector& obj,std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size,iter); + + obj.resize(size); + for(T1& v : obj) + { + serialization::deserialize(v,iter); + } + } + + template + inline void deserialize(std::vector& obj,std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size,iter); + + obj.resize(size); + for(int i=0;i + inline size_t getByteSize(const std::set& obj) + { + return std::accumulate(obj.begin(),obj.end(),getByteSize(obj.size()),[](const size_t& acc,const T& cur) { return acc+getByteSize(cur); }); + } + + template + inline void serialize(const std::set& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.size(),buffer,iter); + for(const T& cur : obj) + { + serialization::serialize(cur,buffer,iter); + } + } + + template + inline void deserialize(std::set& obj,std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size,iter); + + obj.clear(); + for(size_t i=0; i + inline size_t getByteSize(const std::map& obj) + { + return std::accumulate(obj.begin(),obj.end(),sizeof(size_t),[](const size_t& acc,const std::pair& cur) { return acc+getByteSize(cur); }); + } + + template + inline void serialize(const std::map& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.size(),buffer,iter); + for(const auto& cur : obj) + { + serialization::serialize(cur,buffer,iter); + } + } + + template + inline void deserialize(std::map& obj,std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size,iter); + + obj.clear(); + for(size_t i=0; i pair; + serialization::deserialize(pair,iter); + obj.insert(pair); + } + } + + //std::list + + template + inline size_t getByteSize(const std::list& obj) + { + return std::accumulate(obj.begin(), obj.end(), getByteSize(obj.size()), [](const size_t& acc, const T& cur) { return acc + getByteSize(cur); }); + } + + template + inline void serialize(const std::list& obj, std::vector& buffer, std::vector::iterator& iter) + { + serialization::serialize(obj.size(), buffer, iter); + for (const T& cur : obj) + { + serialization::serialize(cur, buffer, iter); + } + } + + template + inline void deserialize(std::list& obj, std::vector::const_iterator& iter) + { + size_t size; + serialization::deserialize(size, iter); + + obj.clear(); + for (size_t i = 0; i < size; ++i) + { + T val; + serialization::deserialize(val, iter); + obj.emplace_back(val); + } + } + + + // Eigen types + template + inline size_t getByteSize(const Eigen::Matrix& obj) + { + // space for numbers of rows,cols and data + return 2*sizeof(typename Eigen::Matrix::Index)+sizeof(T)*obj.rows()*obj.cols(); + } + + template + inline void serialize(const Eigen::Matrix& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.rows(),buffer,iter); + serialization::serialize(obj.cols(),buffer,iter); + size_t size = sizeof(T)*obj.rows()*obj.cols(); + auto ptr = reinterpret_cast(obj.data()); + iter = std::copy(ptr,ptr+size,iter); + } + + template + inline void deserialize(Eigen::Matrix& obj,std::vector::const_iterator& iter) + { + typename Eigen::Matrix::Index rows,cols; + serialization::deserialize(rows,iter); + serialization::deserialize(cols,iter); + size_t size = sizeof(T)*rows*cols; + obj.resize(rows,cols); + auto ptr = reinterpret_cast(obj.data()); + std::copy(iter,iter+size,ptr); + iter+=size; + } + + template + inline size_t getByteSize(const Eigen::SparseMatrix& obj) + { + // space for numbers of rows,cols,nonZeros and tripplets with data (rowIdx,colIdx,value) + size_t size = sizeof(typename Eigen::SparseMatrix::Index); + return 3*size+(sizeof(T)+2*size)*obj.nonZeros(); + } + + template + inline void serialize(const Eigen::SparseMatrix& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.rows(),buffer,iter); + serialization::serialize(obj.cols(),buffer,iter); + serialization::serialize(obj.nonZeros(),buffer,iter); + + for(int k=0;k::InnerIterator it(obj,k);it;++it) + { + serialization::serialize(it.row(),buffer,iter); + serialization::serialize(it.col(),buffer,iter); + serialization::serialize(it.value(),buffer,iter); + } + } + } + + template + inline void deserialize(Eigen::SparseMatrix& obj,std::vector::const_iterator& iter) + { + typename Eigen::SparseMatrix::Index rows,cols,nonZeros; + serialization::deserialize(rows,iter); + serialization::deserialize(cols,iter); + serialization::deserialize(nonZeros,iter); + + obj.resize(rows,cols); + obj.setZero(); + + std::vector > triplets; + for(int i=0;i::Index rowId,colId; + serialization::deserialize(rowId,iter); + serialization::deserialize(colId,iter); + T value; + serialization::deserialize(value,iter); + triplets.push_back(Eigen::Triplet(rowId,colId,value)); + } + obj.setFromTriplets(triplets.begin(),triplets.end()); + } + + template + inline size_t getByteSize(const Eigen::Quaternion& obj) + { + return sizeof(T)*4; + } + + template + inline void serialize(const Eigen::Quaternion& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj.w(),buffer,iter); + serialization::serialize(obj.x(),buffer,iter); + serialization::serialize(obj.y(),buffer,iter); + serialization::serialize(obj.z(),buffer,iter); + } + + template + inline void deserialize(Eigen::Quaternion& obj,std::vector::const_iterator& iter) + { + serialization::deserialize(obj.w(),iter); + serialization::deserialize(obj.x(),iter); + serialization::deserialize(obj.y(),iter); + serialization::deserialize(obj.z(),iter); + } + + // pointers + + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + size_t size = sizeof(bool); + + if(obj) + size += getByteSize(*obj); + + return size; + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialization::serialize(obj == nullptr,buffer,iter); + + if(obj) + serialization::serialize(*obj,buffer,iter); + } + + template + inline typename std::enable_if::value>::type deserialize(T& obj,std::vector::const_iterator& iter) + { + bool isNullPtr; + serialization::deserialize(isNullPtr,iter); + + if(isNullPtr) + { + if(obj) + { + std::cout << "serialization: possible memory leak in serialization for '" << typeid(obj).name() << "'" << std::endl; + obj = nullptr; + } + } + else + { + if(obj) + { + std::cout << "serialization: possible memory corruption in deserialization for '" << typeid(obj).name() << "'" << std::endl; + } + else + { + obj = new typename std::remove_pointer::type(); + } + serialization::deserialize(*obj,iter); + } + } + + // std::shared_ptr and std::unique_ptr + + template + inline typename std::enable_if::value,size_t>::type getByteSize(const T& obj) + { + return getByteSize(obj.get()); + } + + template + inline typename std::enable_if::value>::type serialize(const T& obj,std::vector& buffer,std::vector::iterator& iter) + { + serialize(obj.get(),buffer,iter); + } + + template class T0,typename T1> + inline typename std::enable_if >::value>::type deserialize(T0& obj,std::vector::const_iterator& iter) + { + bool isNullPtr; + serialization::deserialize(isNullPtr,iter); + + if(isNullPtr) + { + obj.reset(); + } + else + { + obj = T0(new T1()); + serialization::deserialize(*obj,iter); + } + } + + // std::weak_ptr + + template + inline size_t getByteSize(const std::weak_ptr& obj) + { + return sizeof(size_t); + } + + template + inline void serialize(const std::weak_ptr& obj,std::vector& buffer,std::vector::iterator& iter) + { + + } + + template + inline void deserialize(std::weak_ptr& obj,std::vector::const_iterator& iter) + { + + } + + // functions to overload for non-intrusive serialization + template + inline void serialize(const T& obj,std::vector& buffer) + { + std::cerr << typeid(obj).name() << " is not serializable: derive from igl::Serializable or spezialize the template function igl::serialization::serialize(const T& obj,std::vector& buffer)" << std::endl; + } + + template + inline void deserialize(T& obj,const std::vector& buffer) + { + std::cerr << typeid(obj).name() << " is not deserializable: derive from igl::Serializable or spezialize the template function igl::serialization::deserialize(T& obj, const std::vector& buffer)" << std::endl; + } + + // helper functions + + template + inline void updateMemoryMap(T& obj,size_t size,std::map& memoryMap) + { + // check if object is already serialized + auto startPtr = new IndexedPointer(); + startPtr->Object = &obj; + auto startBasePtr = static_cast(startPtr); + startBasePtr->Type = IndexedPointerBase::BEGIN; + auto startAddress = reinterpret_cast(&obj); + auto p = std::pair(startAddress,startBasePtr); + + auto el = memoryMap.insert(p); + auto iter = ++el.first; // next elememt + if(el.second && (iter == memoryMap.end() || iter->second->Type != IndexedPointerBase::END)) + { + // not yet serialized + auto endPtr = new IndexedPointer(); + auto endBasePtr = static_cast(endPtr); + endBasePtr->Type = IndexedPointerBase::END; + auto endAddress = reinterpret_cast(&obj) + size - 1; + auto p = std::pair(endAddress,endBasePtr); + + // insert end address + memoryMap.insert(el.first,p); + } + else + { + // already serialized + + // remove inserted address + memoryMap.erase(el.first); + } + } + } +} + +#endif diff --git a/src/igl/setdiff.cpp b/src/igl/setdiff.cpp new file mode 100644 index 000000000..12404175a --- /dev/null +++ b/src/igl/setdiff.cpp @@ -0,0 +1,83 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "setdiff.h" +#include "LinSpaced.h" +#include "list_to_matrix.h" +#include "sort.h" +#include "unique.h" + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedIA> +IGL_INLINE void igl::setdiff( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA) +{ + using namespace Eigen; + using namespace std; + // boring base cases + if(A.size() == 0) + { + C.resize(0,1); + IA.resize(0,1); + return; + } + + // Get rid of any duplicates + typedef Matrix VectorA; + typedef Matrix VectorB; + VectorA uA; + VectorB uB; + typedef DerivedIA IAType; + IAType uIA,uIuA,uIB,uIuB; + unique(A,uA,uIA,uIuA); + unique(B,uB,uIB,uIuB); + + // Sort both + VectorA sA; + VectorB sB; + IAType sIA,sIB; + sort(uA,1,true,sA,sIA); + sort(uB,1,true,sB,sIB); + + vector vC; + vector vIA; + int bi = 0; + // loop over sA + bool past = false; + bool sBempty = sB.size()==0; + for(int a = 0;asB(bi)) + { + bi++; + past = bi>=sB.size(); + } + if(sBempty || past || sA(a), Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::setdiff, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::setdiff, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::setdiff, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/setdiff.h b/src/igl/setdiff.h new file mode 100644 index 000000000..20e8d6485 --- /dev/null +++ b/src/igl/setdiff.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SETDIFF_H +#define IGL_SETDIFF_H +#include "igl_inline.h" +#include +namespace igl +{ + // Set difference of elements of matrices + // + // Inputs: + // A m-long vector of indices + // B n-long vector of indices + // Outputs: + // C (k<=m)-long vector of unique elements appearing in A but not in B + // IA (k<=m)-long list of indices into A so that C = A(IA) + // + template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedIA> + IGL_INLINE void setdiff( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA); +} + +#ifndef IGL_STATIC_LIBRARY +# include "setdiff.cpp" +#endif +#endif diff --git a/src/igl/setunion.cpp b/src/igl/setunion.cpp new file mode 100644 index 000000000..eb0e721d8 --- /dev/null +++ b/src/igl/setunion.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "setunion.h" +#include "unique.h" + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedIA, + typename DerivedIB> +IGL_INLINE void igl::setunion( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IB) +{ + DerivedC CS(A.size()+B.size(),1); + { + int k = 0; + for(int j = 0;j, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/setunion.h b/src/igl/setunion.h new file mode 100644 index 000000000..f28bff653 --- /dev/null +++ b/src/igl/setunion.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SETUNION_H +#define IGL_SETUNION_H +#include "igl_inline.h" +#include +namespace igl +{ + // Union of elements of matrices (like matlab's `union`) + // + // Inputs: + // A m-long vector of indices + // B n-long vector of indices + // Outputs: + // C (k>=m)-long vector of unique elements appearing in A and/or B + // IA (=m)-long list of indices into A so that C = sort([A(IA);B(IB)]) + // IB (=m)-long list of indices into B so that C = sort([A(IA);B(IB)]) + // + template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedIA, + typename DerivedIB> + IGL_INLINE void setunion( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IB); +} + +#ifndef IGL_STATIC_LIBRARY +# include "setunion.cpp" +#endif +#endif + + diff --git a/src/igl/setxor.cpp b/src/igl/setxor.cpp new file mode 100644 index 000000000..5429abdc2 --- /dev/null +++ b/src/igl/setxor.cpp @@ -0,0 +1,32 @@ +#include "setxor.h" +#include "setdiff.h" +#include "setunion.h" +#include "slice.h" + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedIA, + typename DerivedIB> +IGL_INLINE void igl::setxor( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IB) +{ + DerivedC AB,BA; + DerivedIA IAB,IBA; + setdiff(A,B,AB,IAB); + setdiff(B,A,BA,IBA); + setunion(AB,BA,C,IA,IB); + slice(IAB,DerivedIA(IA),IA); + slice(IBA,DerivedIB(IB),IB); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::setxor, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/setxor.h b/src/igl/setxor.h new file mode 100644 index 000000000..bbd1089fa --- /dev/null +++ b/src/igl/setxor.h @@ -0,0 +1,43 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SETXOR_H +#define IGL_SETXOR_H +#include "igl_inline.h" +#include +namespace igl +{ + // Set xor of elements of matrices + // + // Inputs: + // A m-long vector of indices + // B n-long vector of indices + // Outputs: + // C (k<=m)-long vector of unique elements appearing in A but not in B or + // B but not in A + // IA ( + IGL_INLINE void setxor( + const Eigen::DenseBase & A, + const Eigen::DenseBase & B, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IB); +} + +#ifndef IGL_STATIC_LIBRARY +# include "setxor.cpp" +#endif +#endif + diff --git a/src/igl/shape_diameter_function.cpp b/src/igl/shape_diameter_function.cpp new file mode 100644 index 000000000..db061fe39 --- /dev/null +++ b/src/igl/shape_diameter_function.cpp @@ -0,0 +1,182 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "shape_diameter_function.h" +#include "random_dir.h" +#include "barycenter.h" +#include "ray_mesh_intersect.h" +#include "per_vertex_normals.h" +#include "per_face_normals.h" +#include "EPS.h" +#include "Hit.h" +#include "parallel_for.h" +#include +#include +#include + +template < + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::shape_diameter_function( + const std::function< + double( + const Eigen::Vector3f&, + const Eigen::Vector3f&) + > & shoot_ray, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + using namespace Eigen; + const int n = P.rows(); + // Resize output + S.resize(n,1); + // Embree seems to be parallel when constructing but not when tracing rays + const MatrixXf D = random_dir_stratified(num_samples).cast(); + + const auto & inner = [&P,&N,&num_samples,&D,&S,&shoot_ray](const int p) + { + const Vector3f origin = P.row(p).template cast(); + const Vector3f normal = N.row(p).template cast(); + int num_hits = 0; + double total_distance = 0; + for(int s = 0;s 0) + { + // reverse ray + d *= -1; + } + const double dist = shoot_ray(origin,d); + if(std::isfinite(dist)) + { + total_distance += dist; + num_hits++; + } + } + S(p) = total_distance/(double)num_hits; + }; + parallel_for(n,inner,1000); +} + +template < + typename DerivedV, + int DIM, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::shape_diameter_function( + const igl::AABB & aabb, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + const auto & shoot_ray = [&aabb,&V,&F]( + const Eigen::Vector3f& _s, + const Eigen::Vector3f& dir)->double + { + Eigen::Vector3f s = _s+1e-4*dir; + igl::Hit hit; + if(aabb.intersect_ray( + V, + F, + s .cast().eval(), + dir.cast().eval(), + hit)) + { + return hit.t; + }else + { + return std::numeric_limits::infinity(); + } + }; + return shape_diameter_function(shoot_ray,P,N,num_samples,S); + +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > +IGL_INLINE void igl::shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + if(F.rows() < 100) + { + // Super naive + const auto & shoot_ray = [&V,&F]( + const Eigen::Vector3f& _s, + const Eigen::Vector3f& dir)->double + { + Eigen::Vector3f s = _s+1e-4*dir; + igl::Hit hit; + if(ray_mesh_intersect(s,dir,V,F,hit)) + { + return hit.t; + }else + { + return std::numeric_limits::infinity(); + } + }; + return shape_diameter_function(shoot_ray,P,N,num_samples,S); + } + AABB aabb; + aabb.init(V,F); + return shape_diameter_function(aabb,V,F,P,N,num_samples,S); +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedS> +IGL_INLINE void igl::shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const bool per_face, + const int num_samples, + Eigen::PlainObjectBase & S) +{ + if (per_face) + { + DerivedV N; + igl::per_face_normals(V, F, N); + DerivedV P; + igl::barycenter(V, F, P); + return igl::shape_diameter_function(V, F, P, N, num_samples, S); + } + else + { + DerivedV N; + igl::per_vertex_normals(V, F, N); + return igl::shape_diameter_function(V, F, V, N, num_samples, S); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(std::function const&, Eigen::Matrix const&)> const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::shape_diameter_function, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool, int, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/shape_diameter_function.h b/src/igl/shape_diameter_function.h new file mode 100644 index 000000000..5460cda7b --- /dev/null +++ b/src/igl/shape_diameter_function.h @@ -0,0 +1,95 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SHAPE_DIAMETER_FUNCTION_H +#define IGL_SHAPE_DIAMETER_FUNCTION_H +#include "igl_inline.h" +#include "AABB.h" +#include +#include +namespace igl +{ + // Compute shape diamater function per given point. In the parlence of the + // paper "Consistent Mesh Partitioning and Skeletonisation using the Shape + // Diameter Function" [Shapiro et al. 2008], this implementation uses a 180° + // cone and a _uniform_ average (_not_ a average weighted by inverse angles). + // + // Inputs: + // shoot_ray function handle that outputs hits of a given ray against a + // mesh (embedded in function handles as captured variable/data) + // P #P by 3 list of origin points + // N #P by 3 list of origin normals + // Outputs: + // S #P list of shape diamater function values between bounding box + // diagonal (perfect sphere) and 0 (perfect needle hook) + // + template < + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void shape_diameter_function( + const std::function< + double( + const Eigen::Vector3f&, + const Eigen::Vector3f&) + > & shoot_ray, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Inputs: + // AABB axis-aligned bounding box hierarchy around (V,F) + template < + typename DerivedV, + int DIM, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void shape_diameter_function( + const igl::AABB & aabb, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // Inputs: + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh face indices into V + template < + typename DerivedV, + typename DerivedF, + typename DerivedP, + typename DerivedN, + typename DerivedS > + IGL_INLINE void shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & N, + const int num_samples, + Eigen::PlainObjectBase & S); + // per_face whether to compute per face (S is #F by 1) or per vertex (S is + // #V by 1) + template < + typename DerivedV, + typename DerivedF, + typename DerivedS> + IGL_INLINE void shape_diameter_function( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const bool per_face, + const int num_samples, + Eigen::PlainObjectBase & S); +}; +#ifndef IGL_STATIC_LIBRARY +# include "shape_diameter_function.cpp" +#endif + +#endif + diff --git a/src/igl/shapeup.cpp b/src/igl/shapeup.cpp new file mode 100644 index 000000000..f352d22bf --- /dev/null +++ b/src/igl/shapeup.cpp @@ -0,0 +1,238 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Amir Vaxman +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace igl +{ + + //This projection does nothing but render points into projP. Mostly used for "echoing" the global step + IGL_INLINE bool shapeup_identity_projection(const Eigen::PlainObjectBase& P, const Eigen::PlainObjectBase& SC, const Eigen::PlainObjectBase& S, Eigen::PlainObjectBase& projP){ + projP.conservativeResize(SC.rows(), 3*SC.maxCoeff()); + for (int i=0;i& P, const Eigen::PlainObjectBase& SC, const Eigen::PlainObjectBase& S, Eigen::PlainObjectBase& projP){ + projP.conservativeResize(SC.rows(), 3*SC.maxCoeff()); + for (int currRow=0;currRow svd(corrMat, Eigen::ComputeFullU | Eigen::ComputeFullV); + Eigen::MatrixXd R=svd.matrixU()*svd.matrixV().transpose(); + //getting scale by edge length change average. TODO: by singular values + Eigen::VectorXd sourceEdgeLengths(N); + Eigen::VectorXd targetEdgeLengths(N); + for (int j=0;j + IGL_INLINE bool shapeup_precomputation(const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& SC, + const Eigen::PlainObjectBase& S, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& b, + const Eigen::PlainObjectBase& wShape, + const Eigen::PlainObjectBase& wSmooth, + ShapeupData & sudata) + { + using namespace std; + using namespace Eigen; + sudata.P=P; + sudata.SC=SC; + sudata.S=S; + sudata.b=b; + typedef typename DerivedP::Scalar Scalar; + + //checking for consistency of the input + assert(SC.rows()==S.rows()); + assert(SC.rows()==wShape.rows()); + assert(E.rows()==wSmooth.rows()); + assert(b.rows()!=0); //would lead to matrix becoming SPD + + sudata.DShape.conservativeResize(SC.sum(), P.rows()); //Shape matrix (integration); + sudata.DClose.conservativeResize(b.rows(), P.rows()); //Closeness matrix for positional constraints + sudata.DSmooth.conservativeResize(E.rows(), P.rows()); //smoothness matrix + + //Building shape matrix + std::vector > DShapeTriplets; + int currRow=0; + for (int i=0;i(currRow+j, S(i,k), (1.0-avgCoeff))); + else + DShapeTriplets.push_back(Triplet(currRow+j, S(i,k), (-avgCoeff))); + } + } + currRow+=SC(i); + + } + + sudata.DShape.setFromTriplets(DShapeTriplets.begin(), DShapeTriplets.end()); + + //Building closeness matrix + std::vector > DCloseTriplets; + for (int i=0;i(i,b(i), 1.0)); + + sudata.DClose.setFromTriplets(DCloseTriplets.begin(), DCloseTriplets.end()); + + //Building smoothness matrix + std::vector > DSmoothTriplets; + for (int i=0; i(i, E(i, 0), -1)); + DSmoothTriplets.push_back(Triplet(i, E(i, 1), 1)); + } + + SparseMatrix tempMat; + igl::cat(1, sudata.DShape, sudata.DClose, tempMat); + igl::cat(1, tempMat, sudata.DSmooth, sudata.A); + + //weight matrix + vector > WTriplets; + + //one weight per set in S. + currRow=0; + for (int i=0;i(currRow+j,currRow+j,sudata.shapeCoeff*wShape(i))); + currRow+=SC(i); + } + + for (int i=0;i(SC.sum()+i, SC.sum()+i, sudata.closeCoeff)); + + for (int i=0;i(SC.sum()+b.size()+i, SC.sum()+b.size()+i, sudata.smoothCoeff*wSmooth(i))); + + sudata.W.conservativeResize(SC.sum()+b.size()+E.rows(), SC.sum()+b.size()+E.rows()); + sudata.W.setFromTriplets(WTriplets.begin(), WTriplets.end()); + + sudata.At=sudata.A.transpose(); //for efficieny, as we use the transpose a lot in the iteration + sudata.Q=sudata.At*sudata.W*sudata.A; + + return min_quad_with_fixed_precompute(sudata.Q,VectorXi(),SparseMatrix(),true,sudata.solver_data); + } + + + template < + typename DerivedP, + typename DerivedSC, + typename DerivedS> + IGL_INLINE bool shapeup_solve(const Eigen::PlainObjectBase& bc, + const std::function&, const Eigen::PlainObjectBase&, const Eigen::PlainObjectBase&, Eigen::PlainObjectBase&)>& local_projection, + const Eigen::PlainObjectBase& P0, + const ShapeupData & sudata, + const bool quietIterations, + Eigen::PlainObjectBase& P) + { + using namespace Eigen; + using namespace std; + MatrixXd currP=P0; + MatrixXd prevP=P0; + MatrixXd projP; + + assert(bc.rows()==sudata.b.rows()); + + MatrixXd rhs(sudata.A.rows(), 3); rhs.setZero(); + rhs.block(sudata.DShape.rows(), 0, sudata.b.rows(),3)=bc; //this stays constant throughout the iterations + + if (!quietIterations){ + cout<<"Shapeup Iterations, "<(); + if (!quietIterations) + cout << "Iteration "<, typename Eigen::Matrix, typename Eigen::Matrix, typename Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, igl::ShapeupData&); + +template bool igl::shapeup_solve, typename Eigen::Matrix, typename Eigen::Matrix >(const Eigen::PlainObjectBase >& bc, const std::function >&, const Eigen::PlainObjectBase >&, const Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >& ) >& local_projection, const Eigen::PlainObjectBase >& P0, const igl::ShapeupData & sudata, const bool quietIterations, Eigen::PlainObjectBase >& P); +#endif diff --git a/src/igl/shapeup.h b/src/igl/shapeup.h new file mode 100644 index 000000000..7230a8dc7 --- /dev/null +++ b/src/igl/shapeup.h @@ -0,0 +1,128 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Amir Vaxman +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SHAPEUP_H +#define IGL_SHAPEUP_H + +#include +#include +#include +#include +#include +#include +#include + + +//This file implements the following algorithm: + +//Boaziz et al. +//Shape-Up: Shaping Discrete Geometry with Projections +//Computer Graphics Forum (Proc. SGP) 31(5), 2012 + +namespace igl +{ + struct ShapeupData{ + //input data + Eigen::MatrixXd P; + Eigen::VectorXi SC; + Eigen::MatrixXi S; + Eigen::VectorXi b; + int maxIterations; //referring to number of local-global pairs. + double pTolerance; //algorithm stops when max(|P_k-P_{k-1}|) DShape, DClose, DSmooth, Q, A, At, W; + + min_quad_with_fixed_data solver_data; + + ShapeupData(): + maxIterations(50), + pTolerance(10e-6), + shapeCoeff(1.0), + closeCoeff(100.0), + smoothCoeff(0.0){} + }; + + //Every function here defines a local projection for ShapeUp, and must have the following structure to qualify: + //Input: + // P #P by 3 the set of points, either the initial solution, or from previous iteration. + // SC #Set by 1 cardinalities of sets in S + // S #Sets by max(SC) independent sets where the local projection applies. Values beyond column SC(i)-1 in row S(i,:) are "don't care" + //Output: + // projP #S by 3*max(SC) in format xyzxyzxyz, where the projected points correspond to each set in S in the same order. + typedef std::function&, const Eigen::PlainObjectBase&, const Eigen::PlainObjectBase&, Eigen::PlainObjectBase&)> shapeup_projection_function; + + + //This projection does nothing but render points into projP. Mostly used for "echoing" the global step + IGL_INLINE bool shapeup_identity_projection(const Eigen::PlainObjectBase& P, const Eigen::PlainObjectBase& SC, const Eigen::PlainObjectBase& S, Eigen::PlainObjectBase& projP); + + //the projection assumes that the sets are vertices of polygons in cyclic order + IGL_INLINE bool shapeup_regular_face_projection(const Eigen::PlainObjectBase& P, const Eigen::PlainObjectBase& SC, const Eigen::PlainObjectBase& S, Eigen::PlainObjectBase& projP); + + + //This function precomputation the necessary matrices for the ShapeUp process, and prefactorizes them. + + //input: + // P #P by 3 point positions + // SC #Set by 1 cardinalities of sets in S + // S #Sets by max(SC) independent sets where the local projection applies. Values beyond column SC(i)-1 in row S(i,:) are "don't care" + // E #E by 2 the "edges" of the set P; used for the smoothness energy. + // b #b by 1 boundary (fixed) vertices from P. + // wShape, #Set by 1 + // wSmooth #b by 1 weights for constraints from S and positional constraints (used in the global step) + + // Output: + // sudata struct ShapeupData the data necessary to solve the system in shapeup_solve + + template < + typename DerivedP, + typename DerivedSC, + typename DerivedS, + typename Derivedw> + IGL_INLINE bool shapeup_precomputation(const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& SC, + const Eigen::PlainObjectBase& S, + const Eigen::PlainObjectBase& E, + const Eigen::PlainObjectBase& b, + const Eigen::PlainObjectBase& wShape, + const Eigen::PlainObjectBase& wSmooth, + ShapeupData & sudata); + + + + //This function solve the shapeup project optimization. shapeup_precompute must be called before with the same sudata, or results are unpredictable + + //Input: + //bc #b by 3 fixed point values corresonding to "b" in sudata + //local_projection function pointer taking (P,SC,S,projP), + // where the first three parameters are as defined, and "projP" is the output, as a #S by 3*max(SC) function in format xyzxyzxyz, and where it returns the projected points corresponding to each set in S in the same order. + //NOTE: the input values in P0 don't need to correspond to prescribed values in bc; the iterations will project them automatically (by design). + //P0 #P by 3 initial solution (point positions) + //sudata the ShapeUpData structure computed in shapeup_precomputation() + //quietIterations flagging if to output iteration information. + + //Output: + //P the solution to the problem, indices corresponding to P0. + template < + typename DerivedP, + typename DerivedSC, + typename DerivedS> + IGL_INLINE bool shapeup_solve(const Eigen::PlainObjectBase& bc, + const std::function&, const Eigen::PlainObjectBase&, const Eigen::PlainObjectBase&, Eigen::PlainObjectBase&)>& local_projection, + const Eigen::PlainObjectBase& P0, + const ShapeupData & sudata, + const bool quietIterations, + Eigen::PlainObjectBase& P); + +} + +#ifndef IGL_STATIC_LIBRARY +#include "shapeup.cpp" +#endif + +#endif diff --git a/src/igl/shortest_edge_and_midpoint.cpp b/src/igl/shortest_edge_and_midpoint.cpp new file mode 100644 index 000000000..db5338d0d --- /dev/null +++ b/src/igl/shortest_edge_and_midpoint.cpp @@ -0,0 +1,23 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "shortest_edge_and_midpoint.h" + +IGL_INLINE void igl::shortest_edge_and_midpoint( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & /*F*/, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & /*EMAP*/, + const Eigen::MatrixXi & /*EF*/, + const Eigen::MatrixXi & /*EI*/, + double & cost, + Eigen::RowVectorXd & p) +{ + cost = (V.row(E(e,0))-V.row(E(e,1))).norm(); + p = 0.5*(V.row(E(e,0))+V.row(E(e,1))); +} diff --git a/src/igl/shortest_edge_and_midpoint.h b/src/igl/shortest_edge_and_midpoint.h new file mode 100644 index 000000000..45a4a2abf --- /dev/null +++ b/src/igl/shortest_edge_and_midpoint.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SHORTEST_EDGE_AND_MIDPOINT_H +#define IGL_SHORTEST_EDGE_AND_MIDPOINT_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Cost and placement function compatible with igl::decimate. The cost of + // collapsing an edge is its length (prefer to collapse short edges) and the + // placement strategy for the new vertex is the midpoint of the collapsed + // edge. + // + // Inputs: + // e index into E of edge to be considered for collapse + // V #V by dim list of vertex positions + // F #F by 3 list of faces (ignored) + // E #E by 2 list of edge indices into V + // EMAP #F*3 list of half-edges indices into E (ignored) + // EF #E by 2 list of edge-face flaps into F (ignored) + // EI #E by 2 list of edge-face opposite corners (ignored) + // Outputs: + // cost set to edge length + // p placed point set to edge midpoint + IGL_INLINE void shortest_edge_and_midpoint( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & /*F*/, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & /*EMAP*/, + const Eigen::MatrixXi & /*EF*/, + const Eigen::MatrixXi & /*EI*/, + double & cost, + Eigen::RowVectorXd & p); +} + +#ifndef IGL_STATIC_LIBRARY +# include "shortest_edge_and_midpoint.cpp" +#endif +#endif + + diff --git a/src/igl/signed_angle.cpp b/src/igl/signed_angle.cpp new file mode 100644 index 000000000..69f8aa6f5 --- /dev/null +++ b/src/igl/signed_angle.cpp @@ -0,0 +1,74 @@ +#include "signed_angle.h" +#include "PI.h" +#include + +template < + typename DerivedA, + typename DerivedB, + typename DerivedP> +IGL_INLINE typename DerivedA::Scalar igl::signed_angle( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & P) +{ + typedef typename DerivedA::Scalar SType; + // Gather vectors to source and destination + SType o2A[2]; + SType o2B[2]; + // and lengths + SType o2Al = 0; + SType o2Bl = 0; + for(int i = 0;i<2;i++) + { + o2A[i] = P(i) - A(i); + o2B[i] = P(i) - B(i); + o2Al += o2A[i]*o2A[i]; + o2Bl += o2B[i]*o2B[i]; + } + o2Al = sqrt(o2Al); + o2Bl = sqrt(o2Bl); + // Normalize + for(int i = 0;i<2;i++) + { + // Matlab crashes on NaN + if(o2Al!=0) + { + o2A[i] /= o2Al; + } + if(o2Bl!=0) + { + o2B[i] /= o2Bl; + } + } + return + -atan2(o2B[0]*o2A[1]-o2B[1]*o2A[0],o2B[0]*o2A[0]+o2B[1]*o2A[1])/ + (2.*igl::PI); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::signed_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::signed_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::signed_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::signed_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, false>::Scalar igl::signed_angle const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::signed_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::signed_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::signed_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::signed_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +template Eigen::Block const, 1, 3, false>::Scalar igl::signed_angle const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase > const&); +template Eigen::Block const, 1, 3, true>::Scalar igl::signed_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +#ifdef WIN32 +template float igl::signed_angle const ,1,3,1>,class Eigen::Block const ,1,3,1>,class Eigen::Matrix >(class Eigen::MatrixBase const ,1,3,1> > const &,class Eigen::MatrixBase const ,1,3,1> > const &,class Eigen::MatrixBase > const &); +template float igl::signed_angle const ,1,3,0>,class Eigen::Block const ,1,3,0>,class Eigen::Matrix >(class Eigen::MatrixBase const ,1,3,0> > const &,class Eigen::MatrixBase const ,1,3,0> > const &,class Eigen::MatrixBase > const &); +#endif +#endif diff --git a/src/igl/signed_angle.h b/src/igl/signed_angle.h new file mode 100644 index 000000000..547ba00a4 --- /dev/null +++ b/src/igl/signed_angle.h @@ -0,0 +1,27 @@ +#ifndef IGL_SIGNED_ANGLE_H +#define IGL_SIGNED_ANGLE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the signed angle subtended by the oriented 3d triangle (A,B,C) at some point P + // + // Inputs: + // A 2D position of corner + // B 2D position of corner + // P 2D position of query point + // returns signed angle + template < + typename DerivedA, + typename DerivedB, + typename DerivedP> + IGL_INLINE typename DerivedA::Scalar signed_angle( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & P); +} +#ifndef IGL_STATIC_LIBRARY +# include "signed_angle.cpp" +#endif +#endif + diff --git a/src/igl/signed_distance.cpp b/src/igl/signed_distance.cpp new file mode 100644 index 000000000..f638b1f58 --- /dev/null +++ b/src/igl/signed_distance.cpp @@ -0,0 +1,463 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "signed_distance.h" +#include "get_seconds.h" +#include "per_edge_normals.h" +#include "parallel_for.h" +#include "per_face_normals.h" +#include "per_vertex_normals.h" +#include "point_mesh_squared_distance.h" +#include "pseudonormal_test.h" + + +template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> +IGL_INLINE void igl::signed_distance( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const SignedDistanceType sign_type, + const typename DerivedV::Scalar lower_bound, + const typename DerivedV::Scalar upper_bound, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N) +{ + using namespace Eigen; + using namespace std; + const int dim = V.cols(); + assert((V.cols() == 3||V.cols() == 2) && "V should have 3d or 2d positions"); + assert((P.cols() == 3||P.cols() == 2) && "P should have 3d or 2d positions"); + assert(V.cols() == P.cols() && "V should have same dimension as P"); + // Only unsigned distance is supported for non-triangles + if(sign_type != SIGNED_DISTANCE_TYPE_UNSIGNED) + { + assert(F.cols() == dim && "F should have co-dimension 0 simplices"); + } + typedef Eigen::Matrix RowVector3S; + + // Prepare distance computation + AABB tree3; + AABB tree2; + switch(dim) + { + default: + case 3: + tree3.init(V,F); + break; + case 2: + tree2.init(V,F); + break; + } + + Eigen::Matrix FN,VN,EN; + Eigen::Matrix E; + Eigen::Matrix EMAP; + WindingNumberAABB hier3; + switch(sign_type) + { + default: + assert(false && "Unknown SignedDistanceType"); + case SIGNED_DISTANCE_TYPE_UNSIGNED: + // do nothing + break; + case SIGNED_DISTANCE_TYPE_DEFAULT: + case SIGNED_DISTANCE_TYPE_WINDING_NUMBER: + switch(dim) + { + default: + case 3: + hier3.set_mesh(V,F); + hier3.grow(); + break; + case 2: + // no precomp, no hierarchy + break; + } + break; + case SIGNED_DISTANCE_TYPE_PSEUDONORMAL: + switch(dim) + { + default: + case 3: + // "Signed Distance Computation Using the Angle Weighted Pseudonormal" + // [Bærentzen & Aanæs 2005] + per_face_normals(V,F,FN); + per_vertex_normals(V,F,PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE,FN,VN); + per_edge_normals( + V,F,PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM,FN,EN,E,EMAP); + break; + case 2: + FN.resize(F.rows(),2); + VN = DerivedV::Zero(V.rows(),2); + for(int e = 0;e q2; + switch(P.cols()) + { + default: + case 3: + q3.head(P.row(p).size()) = P.row(p); + break; + case 2: + q2 = P.row(p).head(2); + break; + } + typename DerivedV::Scalar s=1,sqrd=0; + Eigen::Matrix c; + RowVector3S c3; + Eigen::Matrix c2; + int i=-1; + // in all cases compute squared unsiged distances + sqrd = dim==3? + tree3.squared_distance(V,F,q3,low_sqr_d,up_sqr_d,i,c3): + tree2.squared_distance(V,F,q2,low_sqr_d,up_sqr_d,i,c2); + if(sqrd >= up_sqr_d || sqrd <= low_sqr_d) + { + // Out of bounds gets a nan (nans on grids can be flood filled later using + // igl::flood_fill) + S(p) = std::numeric_limits::quiet_NaN(); + I(p) = F.rows()+1; + C.row(p).setConstant(0); + }else + { + // Determine sign + switch(sign_type) + { + default: + assert(false && "Unknown SignedDistanceType"); + case SIGNED_DISTANCE_TYPE_UNSIGNED: + break; + case SIGNED_DISTANCE_TYPE_DEFAULT: + case SIGNED_DISTANCE_TYPE_WINDING_NUMBER: + { + Scalar w = 0; + if(dim == 3) + { + s = 1.-2.*hier3.winding_number(q3.transpose()); + }else + { + assert(!V.derived().IsRowMajor); + assert(!F.derived().IsRowMajor); + s = 1.-2.*winding_number(V,F,q2); + } + break; + } + case SIGNED_DISTANCE_TYPE_PSEUDONORMAL: + { + RowVector3S n3; + Eigen::Matrix n2; + dim==3 ? + pseudonormal_test(V,F,FN,VN,EN,EMAP,q3,i,c3,s,n3): + pseudonormal_test(V,E,EN,VN,q2,i,c2,s,n2); + Eigen::Matrix n; + (dim==3 ? n = n3 : n = n2); + N.row(p) = n; + break; + } + } + I(p) = i; + S(p) = s*sqrt(sqrd); + C.row(p) = (dim==3 ? c=c3 : c=c2); + } + } + ,10000); +} + +template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> +IGL_INLINE void igl::signed_distance( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const SignedDistanceType sign_type, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N) +{ + typedef typename DerivedV::Scalar Scalar; + Scalar lower = std::numeric_limits::min(); + Scalar upper = std::numeric_limits::max(); + return signed_distance(P,V,F,sign_type,lower,upper,S,I,C,N); +} + + +template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename Derivedq> +IGL_INLINE typename DerivedV::Scalar igl::signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q) +{ + typename DerivedV::Scalar s,sqrd; + Eigen::Matrix n,c; + int i = -1; + signed_distance_pseudonormal(tree,V,F,FN,VN,EN,EMAP,q,s,sqrd,i,c,n); + return s*sqrt(sqrd); +} + +template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> +IGL_INLINE void igl::signed_distance_pseudonormal( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const AABB & tree, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N) +{ + using namespace Eigen; + const size_t np = P.rows(); + S.resize(np,1); + I.resize(np,1); + N.resize(np,3); + C.resize(np,3); + typedef typename AABB::RowVectorDIMS RowVector3S; +# pragma omp parallel for if(np>1000) + for(size_t p = 0;p +IGL_INLINE void igl::signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c, + Eigen::PlainObjectBase & n) +{ + using namespace Eigen; + using namespace std; + //typedef Eigen::Matrix RowVector3S; + // Alec: Why was this constructor around q necessary? + //sqrd = tree.squared_distance(V,F,RowVector3S(q),i,(RowVector3S&)c); + // Alec: Why was this constructor around c necessary? + //sqrd = tree.squared_distance(V,F,q,i,(RowVector3S&)c); + sqrd = tree.squared_distance(V,F,q,i,c); + pseudonormal_test(V,F,FN,VN,EN,EMAP,q,i,c,s,n); +} + +template < + typename DerivedV, + typename DerivedE, + typename DerivedEN, + typename DerivedVN, + typename Derivedq, + typename Scalar, + typename Derivedc, + typename Derivedn> +IGL_INLINE void igl::signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c, + Eigen::PlainObjectBase & n) +{ + using namespace Eigen; + using namespace std; + typedef Eigen::Matrix RowVector2S; + sqrd = tree.squared_distance(V,E,RowVector2S(q),i,(RowVector2S&)c); + pseudonormal_test(V,E,EN,VN,q,i,c,s,n); +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedq> +IGL_INLINE typename DerivedV::Scalar igl::signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const igl::WindingNumberAABB & hier, + const Eigen::MatrixBase & q) +{ + typedef typename DerivedV::Scalar Scalar; + Scalar s,sqrd; + Eigen::Matrix c; + int i=-1; + signed_distance_winding_number(tree,V,F,hier,q,s,sqrd,i,c); + return s*sqrt(sqrd); +} + + +template < + typename DerivedV, + typename DerivedF, + typename Derivedq, + typename Scalar, + typename Derivedc> +IGL_INLINE void igl::signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const igl::WindingNumberAABB & hier, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c) +{ + using namespace Eigen; + using namespace std; + typedef Eigen::Matrix RowVector3S; + sqrd = tree.squared_distance(V,F,RowVector3S(q),i,(RowVector3S&)c); + const Scalar w = hier.winding_number(q.transpose()); + s = 1.-2.*w; +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedq, + typename Scalar, + typename Derivedc> +IGL_INLINE void igl::signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c) +{ + using namespace Eigen; + using namespace std; + typedef Eigen::Matrix RowVector2S; + sqrd = tree.squared_distance(V,F,RowVector2S(q),i,(RowVector2S&)c); + Scalar w; + // TODO: using .data() like this is very dangerous... This is assuming + // colmajor order + assert(!V.derived().IsRowMajor); + assert(!F.derived().IsRowMajor); + s = 1.-2.*winding_number(V,F,q); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::Matrix::Scalar, Eigen::Matrix::Scalar, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::signed_distance_pseudonormal, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix >(igl::AABB, 3> const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double&, double&, int&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template Eigen::Matrix::Scalar igl::signed_distance_pseudonormal, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(igl::AABB, 3> const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template void igl::signed_distance_pseudonormal, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::AABB, 3> const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::signed_distance, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::SignedDistanceType, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::signed_distance_winding_number, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(igl::AABB, 3> const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::WindingNumberAABB, Eigen::Matrix, Eigen::Matrix > const&, Eigen::MatrixBase > const&, double&, double&, int&, Eigen::PlainObjectBase >&); +template Eigen::Matrix::Scalar igl::signed_distance_winding_number, Eigen::Matrix, Eigen::Matrix >(igl::AABB, 3> const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::WindingNumberAABB, Eigen::Matrix, Eigen::Matrix > const&, Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/signed_distance.h b/src/igl/signed_distance.h new file mode 100644 index 000000000..3b178d304 --- /dev/null +++ b/src/igl/signed_distance.h @@ -0,0 +1,245 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SIGNED_DISTANCE_H +#define IGL_SIGNED_DISTANCE_H + +#include "igl_inline.h" +#include "AABB.h" +#include "WindingNumberAABB.h" +#include +#include +namespace igl +{ + enum SignedDistanceType + { + // Use fast pseudo-normal test [Bærentzen & Aanæs 2005] + SIGNED_DISTANCE_TYPE_PSEUDONORMAL = 0, + SIGNED_DISTANCE_TYPE_WINDING_NUMBER = 1, + SIGNED_DISTANCE_TYPE_DEFAULT = 2, + SIGNED_DISTANCE_TYPE_UNSIGNED = 3, + NUM_SIGNED_DISTANCE_TYPE = 4 + }; + // Computes signed distance to a mesh + // + // Inputs: + // P #P by 3 list of query point positions + // V #V by 3 list of vertex positions + // F #F by ss list of triangle indices, ss should be 3 unless sign_type == + // SIGNED_DISTANCE_TYPE_UNSIGNED + // sign_type method for computing distance _sign_ S + // lower_bound lower bound of distances needed {std::numeric_limits::min} + // upper_bound lower bound of distances needed {std::numeric_limits::max} + // Outputs: + // S #P list of smallest signed distances + // I #P list of facet indices corresponding to smallest distances + // C #P by 3 list of closest points + // N #P by 3 list of closest normals (only set if + // sign_type=SIGNED_DISTANCE_TYPE_PSEUDONORMAL) + // + // Known bugs: This only computes distances to triangles. So unreferenced + // vertices and degenerate triangles are ignored. + template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> + IGL_INLINE void signed_distance( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const SignedDistanceType sign_type, + const typename DerivedV::Scalar lower_bound, + const typename DerivedV::Scalar upper_bound, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N); + // Default bounds + template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> + IGL_INLINE void signed_distance( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const SignedDistanceType sign_type, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N); + // Computes signed distance to mesh + // + // Inputs: + // tree AABB acceleration tree (see AABB.h) + // F #F by 3 list of triangle indices + // FN #F by 3 list of triangle normals + // VN #V by 3 list of vertex normals (ANGLE WEIGHTING) + // EN #E by 3 list of edge normals (UNIFORM WEIGHTING) + // EMAP #F*3 mapping edges in F to E + // q Query point + // Returns signed distance to mesh + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename Derivedq> + IGL_INLINE typename DerivedV::Scalar signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q); + template < + typename DerivedP, + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename DerivedS, + typename DerivedI, + typename DerivedC, + typename DerivedN> + IGL_INLINE void signed_distance_pseudonormal( + const Eigen::MatrixBase & P, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const AABB & tree, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + Eigen::PlainObjectBase & S, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & N); + // Outputs: + // s sign + // sqrd squared distance + // i closest primitive + // c closest point + // n normal + template < + typename DerivedV, + typename DerivedF, + typename DerivedFN, + typename DerivedVN, + typename DerivedEN, + typename DerivedEMAP, + typename Derivedq, + typename Scalar, + typename Derivedc, + typename Derivedn> + IGL_INLINE void signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & FN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & EMAP, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c, + Eigen::PlainObjectBase & n); + template < + typename DerivedV, + typename DerivedE, + typename DerivedEN, + typename DerivedVN, + typename Derivedq, + typename Scalar, + typename Derivedc, + typename Derivedn> + IGL_INLINE void signed_distance_pseudonormal( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EN, + const Eigen::MatrixBase & VN, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c, + Eigen::PlainObjectBase & n); + // Inputs: + // tree AABB acceleration tree (see cgal/point_mesh_squared_distance.h) + // hier Winding number evaluation hierarchy + // q Query point + // Returns signed distance to mesh + template < + typename DerivedV, + typename DerivedF, + typename Derivedq> + IGL_INLINE typename DerivedV::Scalar signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const igl::WindingNumberAABB & hier, + const Eigen::MatrixBase & q); + // Outputs: + // s sign + // sqrd squared distance + // pp closest point and primitve + template < + typename DerivedV, + typename DerivedF, + typename Derivedq, + typename Scalar, + typename Derivedc> + IGL_INLINE void signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const igl::WindingNumberAABB & hier, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c); + template < + typename DerivedV, + typename DerivedF, + typename Derivedq, + typename Scalar, + typename Derivedc> + IGL_INLINE void signed_distance_winding_number( + const AABB & tree, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & q, + Scalar & s, + Scalar & sqrd, + int & i, + Eigen::PlainObjectBase & c); +} + +#ifndef IGL_STATIC_LIBRARY +# include "signed_distance.cpp" +#endif + +#endif diff --git a/src/igl/simplify_polyhedron.cpp b/src/igl/simplify_polyhedron.cpp new file mode 100644 index 000000000..38ba118e7 --- /dev/null +++ b/src/igl/simplify_polyhedron.cpp @@ -0,0 +1,107 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "simplify_polyhedron.h" +#include "decimate.h" +#include "circulation.h" +#include "per_face_normals.h" +#include "infinite_cost_stopping_condition.h" +#include + +IGL_INLINE void igl::simplify_polyhedron( + const Eigen::MatrixXd & OV, + const Eigen::MatrixXi & OF, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::VectorXi & J) +{ + // TODO: to generalize to open meshes, 0-cost should keep all incident + // boundary edges on their original lines. (for non-manifold meshes, + // igl::decimate needs to be generalized) + + Eigen::MatrixXd N; + // Function for computing cost of collapsing edge (0 if at least one + // direction doesn't change pointset, inf otherwise) and placement (in lowest + // cost direction). + const auto & perfect= [&N]( + const int e, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + double & cost, + Eigen::RowVectorXd & p) + { + // Function for ocmputing cost (0 or inf) of collapsing edge by placing + // vertex at `positive` end of edge. + const auto & perfect_directed = [&N]( + const int e, + const bool positive, + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const Eigen::MatrixXi & E, + const Eigen::VectorXi & EMAP, + const Eigen::MatrixXi & EF, + const Eigen::MatrixXi & EI, + double & cost, + Eigen::RowVectorXd & p) + { + const auto vi = E(e,positive); + const auto vj = E(e,!positive); + p = V.row(vj); + std::vector faces = igl::circulation(e,positive,F,E,EMAP,EF,EI); + cost = 0; + for(auto f : faces) + { + // Skip the faces being collapsed + if(f == EF(e,0) || f == EF(e,1)) + { + continue; + } + const Eigen::RowVectorXd nbefore = N.row(f); + // Face with vi replaced with vj + const Eigen::RowVector3i fafter( + F(f,0) == vi ? vj : F(f,0), + F(f,1) == vi ? vj : F(f,1), + F(f,2) == vi ? vj : F(f,2)); + Eigen::RowVectorXd nafter; + igl::per_face_normals(V,fafter,nafter); + const double epsilon = 1e-10; + // if normal changed then not feasible, break + if((nbefore-nafter).norm() > epsilon) + { + cost = std::numeric_limits::infinity(); + break; + } + } + }; + p.resize(3); + double cost0, cost1; + Eigen::RowVectorXd p0, p1; + perfect_directed(e,false,V,F,E,EMAP,EF,EI,cost0,p0); + perfect_directed(e,true,V,F,E,EMAP,EF,EI,cost1,p1); + if(cost0 < cost1) + { + cost = cost0; + p = p0; + }else + { + cost = cost1; + p = p1; + } + }; + igl::per_face_normals(OV,OF,N); + Eigen::VectorXi I; + igl::decimate( + OV,OF, + perfect, + igl::infinite_cost_stopping_condition(perfect), + V,F,J,I); +} + diff --git a/src/igl/simplify_polyhedron.h b/src/igl/simplify_polyhedron.h new file mode 100644 index 000000000..5b5018192 --- /dev/null +++ b/src/igl/simplify_polyhedron.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SIMPLIFY_POLYHEDRON_H +#define IGL_SIMPLIFY_POLYHEDRON_H +#include "igl_inline.h" +#include +namespace igl +{ + // Simplify a polyhedron represented as a triangle mesh (OV,OF) by collapsing + // any edge that doesn't contribute to defining surface's pointset. This + // _would_ also make sense for open and non-manifold meshes, but the current + // implementation only works with closed manifold surfaces with well defined + // triangle normals. + // + // Inputs: + // OV #OV by 3 list of input mesh vertex positions + // OF #OF by 3 list of input mesh triangle indices into OV + // Outputs: + // V #V by 3 list of output mesh vertex positions + // F #F by 3 list of input mesh triangle indices into V + // J #F list of indices into OF of birth parents + IGL_INLINE void simplify_polyhedron( + const Eigen::MatrixXd & OV, + const Eigen::MatrixXi & OF, + Eigen::MatrixXd & V, + Eigen::MatrixXi & F, + Eigen::VectorXi & J); +} +#ifndef IGL_STATIC_LIBRARY +# include "simplify_polyhedron.cpp" +#endif +#endif diff --git a/src/igl/slice.cpp b/src/igl/slice.cpp new file mode 100644 index 000000000..df3a8e9ce --- /dev/null +++ b/src/igl/slice.cpp @@ -0,0 +1,362 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slice.h" +#include "colon.h" + +#include +#include + +template +IGL_INLINE void igl::slice( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::SparseMatrix& Y) +{ +#if 1 + int xm = X.rows(); + int xn = X.cols(); + int ym = R.size(); + int yn = C.size(); + + // special case when R or C is empty + if(ym == 0 || yn == 0) + { + Y.resize(ym,yn); + return; + } + + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() < xm); + assert(C.minCoeff() >= 0); + assert(C.maxCoeff() < xn); + + // Build reindexing maps for columns and rows, -1 means not in map + std::vector > RI; + RI.resize(xm); + for(int i = 0;i > CI; + CI.resize(xn); + // initialize to -1 + for(int i = 0;i dyn_Y(ym,yn); + // Take a guess at the number of nonzeros (this assumes uniform distribution + // not banded or heavily diagonal) + dyn_Y.reserve((X.nonZeros()/(X.rows()*X.cols())) * (ym*yn)); + // Iterate over outside + for(int k=0; k::InnerIterator it (X,k); it; ++it) + { + std::vector::iterator rit, cit; + for(rit = RI[it.row()].begin();rit != RI[it.row()].end(); rit++) + { + for(cit = CI[it.col()].begin();cit != CI[it.col()].end(); cit++) + { + dyn_Y.coeffRef(*rit,*cit) = it.value(); + } + } + } + } + Y = Eigen::SparseMatrix(dyn_Y); +#else + + // Alec: This is _not_ valid for arbitrary R,C since they don't necessary + // representation a strict permutation of the rows and columns: rows or + // columns could be removed or replicated. The removal of rows seems to be + // handled here (although it's not clear if there is a performance gain when + // the #removals >> #remains). If this is sufficiently faster than the + // correct code above, one could test whether all entries in R and C are + // unique and apply the permutation version if appropriate. + // + + int xm = X.rows(); + int xn = X.cols(); + int ym = R.size(); + int yn = C.size(); + + // special case when R or C is empty + if(ym == 0 || yn == 0) + { + Y.resize(ym,yn); + return; + } + + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() < xm); + assert(C.minCoeff() >= 0); + assert(C.maxCoeff() < xn); + + // initialize row and col permutation vectors + Eigen::VectorXi rowIndexVec = igl::LinSpaced(xm,0,xm-1); + Eigen::VectorXi rowPermVec = igl::LinSpaced(xm,0,xm-1); + for(int i=0;i rowPerm(rowIndexVec); + + Eigen::VectorXi colIndexVec = igl::LinSpaced(xn,0,xn-1); + Eigen::VectorXi colPermVec = igl::LinSpaced(xn,0,xn-1); + for(int i=0;i colPerm(colPermVec); + + Eigen::SparseMatrix M = (rowPerm * X); + Y = (M * colPerm).block(0,0,ym,yn); +#endif +} + +template +IGL_INLINE void igl::slice( + const MatX& X, + const Eigen::DenseBase & R, + const int dim, + MatY& Y) +{ + Eigen::Matrix C; + switch(dim) + { + case 1: + // boring base case + if(X.cols() == 0) + { + Y.resize(R.size(),0); + return; + } + igl::colon(0,X.cols()-1,C); + return slice(X,R,C,Y); + case 2: + // boring base case + if(X.rows() == 0) + { + Y.resize(0,R.size()); + return; + } + igl::colon(0,X.rows()-1,C); + return slice(X,C,R,Y); + default: + assert(false && "Unsupported dimension"); + return; + } +} + +template < + typename DerivedX, + typename DerivedR, + typename DerivedC, + typename DerivedY> +IGL_INLINE void igl::slice( + const Eigen::DenseBase & X, + const Eigen::DenseBase & R, + const Eigen::DenseBase & C, + Eigen::PlainObjectBase & Y) +{ +#ifndef NDEBUG + int xm = X.rows(); + int xn = X.cols(); +#endif + int ym = R.size(); + int yn = C.size(); + + // special case when R or C is empty + if(ym == 0 || yn == 0) + { + Y.resize(ym,yn); + return; + } + + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() < xm); + assert(C.minCoeff() >= 0); + assert(C.maxCoeff() < xn); + + // Resize output + Y.resize(ym,yn); + // loop over output rows, then columns + for(int i = 0;i +IGL_INLINE void igl::slice( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + Eigen::PlainObjectBase & Y) +{ + // phony column indices + Eigen::Matrix C; + C.resize(1); + C(0) = 0; + return igl::slice(X,R,C,Y); +} + +template +IGL_INLINE DerivedX igl::slice( + const Eigen::DenseBase & X, + const Eigen::Matrix & R) +{ + DerivedX Y; + igl::slice(X,R,Y); + return Y; +} + +template +IGL_INLINE DerivedX igl::slice( + const Eigen::DenseBase& X, + const Eigen::Matrix & R, + const int dim) +{ + DerivedX Y; + igl::slice(X,R,dim,Y); + return Y; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&, int); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Array >(Eigen::Array const&, Eigen::DenseBase > const&, int, Eigen::Array&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Array const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Array >(Eigen::Array const&, Eigen::DenseBase > const&, int, Eigen::Array&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::SparseMatrix >(Eigen::SparseMatrix const&, Eigen::DenseBase > const&, int, Eigen::SparseMatrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&); +// generated by autoexplicit.sh +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&, int); +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&); +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&, int); +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice >, Eigen::Matrix, Eigen::PlainObjectBase > >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +template void igl::slice, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice, std::complex >(Eigen::SparseMatrix, 0, int> const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix, 0, int>&); +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&, int); +template void igl::slice, Eigen::Matrix, Eigen::SparseMatrix >(Eigen::SparseMatrix const&, Eigen::DenseBase > const&, int, Eigen::SparseMatrix&); +template void igl::slice(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +template void igl::slice, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::SparseMatrix >(Eigen::SparseMatrix const&, Eigen::DenseBase > const&, int, Eigen::SparseMatrix&); +template void igl::slice, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::slice, Eigen::Matrix, Eigen::SparseMatrix >(Eigen::SparseMatrix const&, Eigen::DenseBase > const&, int, Eigen::SparseMatrix&); +template void igl::slice, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::slice(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +template void igl::slice, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix igl::slice >(Eigen::DenseBase > const&, Eigen::Matrix const&); +template void igl::slice >, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::DenseBase > const&, int, Eigen::Matrix&); +template void igl::slice, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::slice(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +#ifdef WIN32 +template void igl::slice, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::PlainObjectBase>>(class Eigen::Matrix<__int64, -1, 1, 0, -1, 1> const &, class Eigen::DenseBase> const &, int, class Eigen::PlainObjectBase> &); +template void igl::slice>, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::PlainObjectBase>>(class Eigen::PlainObjectBase> const &, class Eigen::DenseBase> const &, int, class Eigen::PlainObjectBase> &); +#endif +#endif diff --git a/src/igl/slice.h b/src/igl/slice.h new file mode 100644 index 000000000..a25dfa411 --- /dev/null +++ b/src/igl/slice.h @@ -0,0 +1,87 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SLICE_H +#define IGL_SLICE_H +#include "igl_inline.h" + +#include +namespace igl +{ + // Act like the matlab X(row_indices,col_indices) operator, where + // row_indices, col_indices are non-negative integer indices. + // + // Inputs: + // X m by n matrix + // R list of row indices + // C list of column indices + // Output: + // Y #R by #C matrix + // + // See also: slice_mask + template < + typename TX, + typename TY> + IGL_INLINE void slice( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::SparseMatrix& Y); + // Wrapper to only slice in one direction + // + // Inputs: + // dim dimension to slice in 1 or 2, dim=1 --> X(R,:), dim=2 --> X(:,R) + // + // Note: For now this is just a cheap wrapper. + template < + typename MatX, + typename DerivedR, + typename MatY> + IGL_INLINE void slice( + const MatX& X, + const Eigen::DenseBase & R, + const int dim, + MatY& Y); + template < + typename DerivedX, + typename DerivedR, + typename DerivedC, + typename DerivedY> + IGL_INLINE void slice( + const Eigen::DenseBase & X, + const Eigen::DenseBase & R, + const Eigen::DenseBase & C, + Eigen::PlainObjectBase & Y); + + template + IGL_INLINE void slice( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + Eigen::PlainObjectBase & Y); + // VectorXi Y = slice(X,R); + // + // This templating is bad because the return type might not have the same + // size as `DerivedX`. This will probably only work if DerivedX has Dynamic + // as it's non-trivial sizes or if the number of rows in R happens to equal + // the number of rows in `DerivedX`. + template + IGL_INLINE DerivedX slice( + const Eigen::DenseBase & X, + const Eigen::Matrix & R); + template + IGL_INLINE DerivedX slice( + const Eigen::DenseBase& X, + const Eigen::Matrix & R, + const int dim); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "slice.cpp" +#endif + +#endif diff --git a/src/igl/slice_cached.cpp b/src/igl/slice_cached.cpp new file mode 100644 index 000000000..e0f13d804 --- /dev/null +++ b/src/igl/slice_cached.cpp @@ -0,0 +1,57 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slice_cached.h" + +#include +#include +#include +#include "slice.h" + +template +IGL_INLINE void igl::slice_cached_precompute( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::MatrixBase& data, + Eigen::SparseMatrix& Y + ) +{ + // Create a sparse matrix whose entries are the ids + Eigen::SparseMatrix TS = X.template cast(); + + TS.makeCompressed(); + for (unsigned i=0;i TS_sliced; + igl::slice(TS,R,C,TS_sliced); + Y = TS_sliced.cast(); + + data.resize(TS_sliced.nonZeros()); + for (unsigned i=0;i +IGL_INLINE void igl::slice_cached( + const Eigen::SparseMatrix& X, + const Eigen::MatrixBase& data, + Eigen::SparseMatrix& Y + ) +{ + for (unsigned i=0; i >(Eigen::SparseMatrix const&, Eigen::MatrixBase > const&, Eigen::SparseMatrix&); +template void igl::slice_cached_precompute >(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::MatrixBase >&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/slice_cached.h b/src/igl/slice_cached.h new file mode 100644 index 000000000..84ced8dc9 --- /dev/null +++ b/src/igl/slice_cached.h @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SLICE_CACHED_H +#define IGL_SLICE_CACHED_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + + // Act like the matlab X(row_indices,col_indices) operator, where row_indices, + // col_indices are non-negative integer indices. This is a fast version of + // igl::slice that can analyze and store the sparsity structure. It is slower + // at the irst evaluation (slice_cached_precompute), but faster on the + // subsequent ones. + // + // Inputs: + // X m by n matrix + // R list of row indices + // C list of column indices + // + // Output: + // Y #R by #C matrix + // data Temporary data used by slice_cached to repeat this operation + // + // Usage: + // + // // Construct and slice up Laplacian + // SparseMatrix L,L_sliced; + // igl::cotmatrix(V,F,L); + + // // Normal igl::slice call + // igl::slice(L,in,in,L_in_in); + + // // Fast version + // static VectorXi data; // static or saved in a global state + // if (data.size() == 0) + // igl::slice_cached_precompute(L,in,in,data,L_sliced); + // else + // igl::slice_cached(L,data,L_sliced); + +template +IGL_INLINE void slice_cached_precompute( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::MatrixBase& data, + Eigen::SparseMatrix& Y + ); + +template +IGL_INLINE void slice_cached( + const Eigen::SparseMatrix& X, + const Eigen::MatrixBase& data, + Eigen::SparseMatrix& Y + ); +} + +#ifndef IGL_STATIC_LIBRARY +# include "slice_cached.cpp" +#endif + +#endif diff --git a/src/igl/slice_into.cpp b/src/igl/slice_into.cpp new file mode 100644 index 000000000..aa12d9303 --- /dev/null +++ b/src/igl/slice_into.cpp @@ -0,0 +1,153 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slice_into.h" +#include "colon.h" + +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include + +template +IGL_INLINE void igl::slice_into( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::SparseMatrix& Y) +{ + +#ifndef NDEBUG + int xm = X.rows(); + int xn = X.cols(); + assert(R.size() == xm); + assert(C.size() == xn); + int ym = Y.size(); + int yn = Y.size(); + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() < ym); + assert(C.minCoeff() >= 0); + assert(C.maxCoeff() < yn); +#endif + + // create temporary dynamic sparse matrix + Eigen::DynamicSparseMatrix dyn_Y(Y); + // Iterate over outside + for(int k=0; k::InnerIterator it (X,k); it; ++it) + { + dyn_Y.coeffRef(R(it.row()),C(it.col())) = it.value(); + } + } + Y = Eigen::SparseMatrix(dyn_Y); +} + +template +IGL_INLINE void igl::slice_into( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::PlainObjectBase & Y) +{ + + int xm = X.rows(); + int xn = X.cols(); +#ifndef NDEBUG + assert(R.size() == xm); + assert(C.size() == xn); + int ym = Y.size(); + int yn = Y.size(); + assert(R.minCoeff() >= 0); + assert(R.maxCoeff() < ym); + assert(C.minCoeff() >= 0); + assert(C.maxCoeff() < yn); +#endif + + // Build reindexing maps for columns and rows, -1 means not in map + Eigen::Matrix RI; + RI.resize(xm); + for(int i = 0;i +IGL_INLINE void igl::slice_into( + const MatX& X, + const Eigen::Matrix & R, + const int dim, + MatY& Y) +{ + Eigen::VectorXi C; + switch(dim) + { + case 1: + assert(R.size() == X.rows()); + // boring base case + if(X.cols() == 0) + { + return; + } + igl::colon(0,X.cols()-1,C); + return slice_into(X,R,C,Y); + case 2: + assert(R.size() == X.cols()); + // boring base case + if(X.rows() == 0) + { + return; + } + igl::colon(0,X.rows()-1,C); + return slice_into(X,C,R,Y); + default: + assert(false && "Unsupported dimension"); + return; + } +} + +template +IGL_INLINE void igl::slice_into( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + Eigen::PlainObjectBase & Y) +{ + // phony column indices + Eigen::Matrix C; + C.resize(1); + C(0) = 0; + return igl::slice_into(X,R,C,Y); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::slice_into, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::Matrix&); +// generated by autoexplicit.sh +template void igl::slice_into, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice_into, -1, -1, true>, Eigen::PlainObjectBase > >(Eigen::Block, -1, -1, true> const&, Eigen::Matrix const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice_into, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::slice_into, Eigen::PlainObjectBase > >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_into(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +template void igl::slice_into, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::Matrix&); +template void igl::slice_into, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice_into, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::Matrix&); +template void igl::slice_into, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice_into, Eigen::SparseMatrix >(Eigen::SparseMatrix const&, Eigen::Matrix const&, int, Eigen::SparseMatrix&); +template void igl::slice_into(Eigen::SparseMatrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +template void igl::slice_into, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice_into, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +template void igl::slice_into, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, int, Eigen::Matrix&); +template void igl::slice_into, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Matrix const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/slice_into.h b/src/igl/slice_into.h new file mode 100644 index 000000000..83bd8f523 --- /dev/null +++ b/src/igl/slice_into.h @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SLICE_INTO_H +#define IGL_SLICE_INTO_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Act like the matlab Y(row_indices,col_indices) = X + // + // Inputs: + // X xm by xn rhs matrix + // R list of row indices + // C list of column indices + // Y ym by yn lhs matrix + // Output: + // Y ym by yn lhs matrix, same as input but Y(R,C) = X + template + IGL_INLINE void slice_into( + const Eigen::SparseMatrix& X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::SparseMatrix& Y); + + template + IGL_INLINE void slice_into( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + const Eigen::Matrix & C, + Eigen::PlainObjectBase & Y); + // Wrapper to only slice in one direction + // + // Inputs: + // dim dimension to slice in 1 or 2, dim=1 --> X(R,:), dim=2 --> X(:,R) + // + // Note: For now this is just a cheap wrapper. + template + IGL_INLINE void slice_into( + const MatX & X, + const Eigen::Matrix & R, + const int dim, + MatY& Y); + + template + IGL_INLINE void slice_into( + const Eigen::DenseBase & X, + const Eigen::Matrix & R, + Eigen::PlainObjectBase & Y); +} + +#ifndef IGL_STATIC_LIBRARY +# include "slice_into.cpp" +#endif + +#endif diff --git a/src/igl/slice_mask.cpp b/src/igl/slice_mask.cpp new file mode 100644 index 000000000..63cb82045 --- /dev/null +++ b/src/igl/slice_mask.cpp @@ -0,0 +1,168 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slice_mask.h" +#include "slice.h" +#include "find.h" +#include + +template +IGL_INLINE void igl::slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const Eigen::Array & C, + Eigen::PlainObjectBase & Y) +{ + int xm = X.rows(); + int xn = X.cols(); + int ym = R.count(); + int yn = C.count(); + assert(R.size() == X.rows() && "R.size() should match X.rows()"); + assert(C.size() == X.cols() && "C.size() should match X.cols()"); + Y.resize(ym,yn); + { + int yi = 0; + for(int i = 0;i +IGL_INLINE void igl::slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const int dim, + Eigen::PlainObjectBase & Y) +{ + switch(dim) + { + case 1: + { + const int ym = R.count(); + assert(X.rows() == R.size() && "X.rows() should match R.size()"); + Y.resize(ym,X.cols()); + { + int yi = 0; + for(int i = 0;i +IGL_INLINE DerivedX igl::slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const Eigen::Array & C) +{ + DerivedX Y; + igl::slice_mask(X,R,C,Y); + return Y; +} + +template +IGL_INLINE DerivedX igl::slice_mask( + const Eigen::DenseBase& X, + const Eigen::Array & R, + const int dim) +{ + DerivedX Y; + igl::slice_mask(X,R,dim,Y); + return Y; +} + + +template +IGL_INLINE void igl::slice_mask( + const Eigen::SparseMatrix & X, + const Eigen::Array & R, + const int dim, + Eigen::SparseMatrix & Y) +{ + // Cheapskate solution + Eigen::VectorXi Ri; + find(R,Ri); + return slice(X,Ri,dim,Y); +} + +template +IGL_INLINE void igl::slice_mask( + const Eigen::SparseMatrix & X, + const Eigen::Array & R, + const Eigen::Array & C, + Eigen::SparseMatrix & Y) +{ + // Cheapskate solution + Eigen::VectorXi Ri; + find(R,Ri); + Eigen::VectorXi Ci; + find(C,Ci); + return slice(X,Ri,Ci,Y); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Matrix igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int); +// generated by autoexplicit.sh +template Eigen::Matrix igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int); +// generated by autoexplicit.sh +template Eigen::Array igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int); +// generated by autoexplicit.sh +template void igl::slice_mask(Eigen::SparseMatrix const&, Eigen::Array const&, int, Eigen::SparseMatrix&); +template void igl::slice_mask, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask, Eigen::Array >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, Eigen::Array const&, Eigen::PlainObjectBase >&); +template void igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +template void igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, Eigen::Array const&, Eigen::PlainObjectBase >&); +template void igl::slice_mask >(Eigen::DenseBase > const&, Eigen::Array const&, int, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/slice_mask.h b/src/igl/slice_mask.h new file mode 100644 index 000000000..0a0dcda7c --- /dev/null +++ b/src/igl/slice_mask.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SLICE_MASK_H +#define IGL_SLICE_MASK_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + // Act like the matlab X(row_mask,col_mask) operator, where + // row_mask, col_mask are non-negative integer indices. + // + // Inputs: + // X m by n matrix + // R m list of row bools + // C n list of column bools + // Output: + // Y #trues-in-R by #trues-in-C matrix + // + // See also: slice_mask + + template + IGL_INLINE void slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const Eigen::Array & C, + Eigen::PlainObjectBase & Y); + template + IGL_INLINE void slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const int dim, + Eigen::PlainObjectBase & Y); + // + // This templating is bad because the return type might not have the same + // size as `DerivedX`. This will probably only work if DerivedX has Dynamic + // as it's non-trivial sizes or if the number of rows in R happens to equal + // the number of rows in `DerivedX`. + template + IGL_INLINE DerivedX slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const Eigen::Array & C); + template + IGL_INLINE DerivedX slice_mask( + const Eigen::DenseBase & X, + const Eigen::Array & R, + const int dim); + template + IGL_INLINE void slice_mask( + const Eigen::SparseMatrix & X, + const Eigen::Array & R, + const int dim, + Eigen::SparseMatrix & Y); + template + IGL_INLINE void slice_mask( + const Eigen::SparseMatrix & X, + const Eigen::Array & R, + const Eigen::Array & C, + Eigen::SparseMatrix & Y); +} + + +#ifndef IGL_STATIC_LIBRARY +# include "slice_mask.cpp" +#endif + +#endif diff --git a/src/igl/slice_tets.cpp b/src/igl/slice_tets.cpp new file mode 100644 index 000000000..dbe042fa1 --- /dev/null +++ b/src/igl/slice_tets.cpp @@ -0,0 +1,356 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slice_tets.h" +#include "LinSpaced.h" +#include "sort.h" +#include "edges.h" +#include "slice.h" +#include "cat.h" +#include "ismember.h" +#include "unique_rows.h" +#include +#include +#include + +template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ, + typename BCType> +IGL_INLINE void igl::slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J, + Eigen::SparseMatrix & BC) +{ + Eigen::MatrixXi sE; + Eigen::Matrix lambda; + igl::slice_tets(V,T,S,SV,SF,J,sE,lambda); + const int ns = SV.rows(); + std::vector > BCIJV(ns*2); + for(int i = 0;i(i,sE(i,0), lambda(i)); + BCIJV[2*i+1] = Eigen::Triplet(i,sE(i,1),1.0-lambda(i)); + } + BC.resize(SV.rows(),V.rows()); + BC.setFromTriplets(BCIJV.begin(),BCIJV.end()); +} + +template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ> +IGL_INLINE void igl::slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J) +{ + Eigen::MatrixXi sE; + Eigen::Matrix lambda; + igl::slice_tets(V,T,S,SV,SF,J,sE,lambda); +} + +template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ, + typename DerivedsE, + typename Derivedlambda> +IGL_INLINE void igl::slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J, + Eigen::PlainObjectBase& sE, + Eigen::PlainObjectBase& lambda) +{ + + using namespace Eigen; + using namespace std; + assert(V.cols() == 3 && "V should be #V by 3"); + assert(T.cols() == 4 && "T should be #T by 4"); + + static const Eigen::Matrix flipped_order = + (Eigen::Matrix(12,4)<< + 3,2,0,1, + 3,1,2,0, + 3,0,1,2, + 2,3,1,0, + 2,1,0,3, + 2,0,3,1, + 1,3,0,2, + 1,2,3,0, + 1,0,2,3, + 0,3,2,1, + 0,2,1,3, + 0,1,3,2 + ).finished(); + + // number of tets + const size_t m = T.rows(); + + typedef typename DerivedS::Scalar Scalar; + typedef typename DerivedT::Scalar Index; + typedef Matrix VectorXS; + typedef Matrix MatrixX4S; + typedef Matrix MatrixX3S; + typedef Matrix MatrixX2S; + typedef Matrix MatrixX4I; + typedef Matrix MatrixX3I; + typedef Matrix MatrixX2I; + typedef Matrix VectorXI; + typedef Array ArrayXb; + + MatrixX4S IT(m,4); + for(size_t t = 0;t & T, + const MatrixX4S & IT, + const ArrayXb & I, + MatrixX4I & TI, + MatrixX4S & ITI, + VectorXI & JI) + { + const Index num_I = std::count(I.data(),I.data()+I.size(),true); + TI.resize(num_I,4); + ITI.resize(num_I,4); + JI.resize(num_I,1); + { + size_t k = 0; + for(size_t t = 0;t<(size_t)T.rows();t++) + { + if(I(t)) + { + TI.row(k) = T.row(t); + ITI.row(k) = IT.row(t); + JI(k) = t; + k++; + } + } + assert(k == num_I); + } + }; + + ArrayXb I13 = (IT.array()<0).rowwise().count()==1; + ArrayXb I31 = (IT.array()>0).rowwise().count()==1; + ArrayXb I22 = (IT.array()<0).rowwise().count()==2; + MatrixX4I T13,T31,T22; + MatrixX4S IT13,IT31,IT22; + VectorXI J13,J31,J22; + extract_rows(T,IT,I13,T13,IT13,J13); + extract_rows(T,IT,I31,T31,IT31,J31); + extract_rows(T,IT,I22,T22,IT22,J22); + + const auto & apply_sort4 = [] ( + const MatrixX4I & T, + const MatrixX4I & sJ, + MatrixX4I & sT) + { + sT.resize(T.rows(),4); + for(size_t t = 0;t<(size_t)T.rows();t++) + { + for(size_t c = 0;c<4;c++) + { + sT(t,c) = T(t,sJ(t,c)); + } + } + }; + + const auto & apply_sort2 = [] ( + const MatrixX2I & E, + const MatrixX2I & sJ, + Eigen::PlainObjectBase& sE) + { + sE.resize(E.rows(),2); + for(size_t t = 0;t<(size_t)E.rows();t++) + { + for(size_t c = 0;c<2;c++) + { + sE(t,c) = E(t,sJ(t,c)); + } + } + }; + + const auto & one_below = [&apply_sort4]( + const MatrixX4I & T, + const MatrixX4S & IT, + MatrixX2I & U, + MatrixX3I & SF) + { + // Number of tets + const size_t m = T.rows(); + if(m == 0) + { + U.resize(0,2); + SF.resize(0,3); + return; + } + MatrixX4S sIT; + MatrixX4I sJ; + sort(IT,2,true,sIT,sJ); + MatrixX4I sT; + apply_sort4(T,sJ,sT); + U.resize(3*m,2); + U<< + sT.col(0),sT.col(1), + sT.col(0),sT.col(2), + sT.col(0),sT.col(3); + SF.resize(m,3); + for(size_t c = 0;c<3;c++) + { + SF.col(c) = + igl::LinSpaced< + Eigen::Matrix > + (m,0+c*m,(m-1)+c*m); + } + ArrayXb flip; + { + VectorXi _; + ismember_rows(sJ,flipped_order,flip,_); + } + for(int i = 0;i(m,0+0*m,(m-1)+0*m); + SF.block(0,1,m,1) = igl::LinSpaced(m,0+1*m,(m-1)+1*m); + SF.block(0,2,m,1) = igl::LinSpaced(m,0+3*m,(m-1)+3*m); + SF.block(m,0,m,1) = igl::LinSpaced(m,0+0*m,(m-1)+0*m); + SF.block(m,1,m,1) = igl::LinSpaced(m,0+3*m,(m-1)+3*m); + SF.block(m,2,m,1) = igl::LinSpaced(m,0+2*m,(m-1)+2*m); + ArrayXb flip; + { + VectorXi _; + ismember_rows(sJ,flipped_order,flip,_); + } + for(int i = 0;i()*lambda(e) + + V.row(sE(e,1)).template cast()*(1.0-lambda(e)); + } + SF.resize( SF13.rows()+SF31.rows()+SF22.rows(),3); + SF<< + SF13, + U13.rows()+ SF31.rowwise().reverse().array(), + U13.rows()+U31.rows()+SF22.array(); + + std::for_each( + SF.data(), + SF.data()+SF.size(), + [&uJ](typename DerivedSF::Scalar & i){i=uJ(i);}); + + J.resize(SF.rows()); + J<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/slice_tets.h b/src/igl/slice_tets.h new file mode 100644 index 000000000..349c5d790 --- /dev/null +++ b/src/igl/slice_tets.h @@ -0,0 +1,96 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SLICE_TETS_H +#define IGL_SLICE_TETS_H +#include "igl_inline.h" + +#include +#include + +#include + +namespace igl +{ + // SLICE_TETS Slice through a tet mesh (V,T) along a given plane (via its + // implicit equation). + // + // Inputs: + // V #V by 3 list of tet mesh vertices + // T #T by 4 list of tet indices into V + //// plane list of 4 coefficients in the plane equation: [x y z 1]'*plane = 0 + // S #V list of values so that S = 0 is the desired isosurface + // Outputs: + // SV #SV by 3 list of triangle mesh vertices along slice + // SF #SF by 3 list of triangles indices into SV + // J #SF list of indices into T revealing from which tet each faces comes + // BC #SU by #V list of barycentric coordinates (or more generally: linear + // interpolation coordinates) so that SV = BC*V + // + template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ, + typename BCType> + IGL_INLINE void slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J, + Eigen::SparseMatrix & BC); + template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ> + IGL_INLINE void slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J); + // Outputs: + // sE #SV by 2 list of sorted edge indices into V + // lambda #SV by 1 list of parameters along each edge in sE so that: + // SV(i,:) = V(sE(i,1),:)*lambda(i) + V(sE(i,2),:)*(1-lambda(i)); + template < + typename DerivedV, + typename DerivedT, + typename DerivedS, + typename DerivedSV, + typename DerivedSF, + typename DerivedJ, + typename DerivedsE, + typename Derivedlambda + > + IGL_INLINE void slice_tets( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + const Eigen::MatrixBase & S, + Eigen::PlainObjectBase& SV, + Eigen::PlainObjectBase& SF, + Eigen::PlainObjectBase& J, + Eigen::PlainObjectBase& sE, + Eigen::PlainObjectBase& lambda); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "slice_tets.cpp" +#endif + +#endif + + diff --git a/src/igl/slim.cpp b/src/igl/slim.cpp new file mode 100644 index 000000000..a88674274 --- /dev/null +++ b/src/igl/slim.cpp @@ -0,0 +1,961 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "slim.h" + +#include "boundary_loop.h" +#include "cotmatrix.h" +#include "edge_lengths.h" +#include "grad.h" +#include "local_basis.h" +#include "repdiag.h" +#include "vector_area_matrix.h" +#include "arap.h" +#include "cat.h" +#include "doublearea.h" +#include "grad.h" +#include "local_basis.h" +#include "per_face_normals.h" +#include "slice_into.h" +#include "volume.h" +#include "polar_svd.h" +#include "flip_avoiding_line_search.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "Timer.h" +#include "sparse_cached.h" +#include "AtA_cached.h" + +#ifdef CHOLMOD +#include +#endif + +namespace igl +{ + namespace slim + { + // Definitions of internal functions + IGL_INLINE void compute_surface_gradient_matrix(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, + const Eigen::MatrixXd &F1, const Eigen::MatrixXd &F2, + Eigen::SparseMatrix &D1, Eigen::SparseMatrix &D2); + IGL_INLINE void buildA(igl::SLIMData& s, std::vector > & IJV); + IGL_INLINE void buildRhs(igl::SLIMData& s, const Eigen::SparseMatrix &A); + IGL_INLINE void add_soft_constraints(igl::SLIMData& s, Eigen::SparseMatrix &L); + IGL_INLINE double compute_energy(igl::SLIMData& s, Eigen::MatrixXd &V_new); + IGL_INLINE double compute_soft_const_energy(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &V_o); + IGL_INLINE double compute_energy_with_jacobians(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, const Eigen::MatrixXd &Ji, + Eigen::MatrixXd &uv, Eigen::VectorXd &areas); + IGL_INLINE void solve_weighted_arap(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &uv, + Eigen::VectorXi &soft_b_p, + Eigen::MatrixXd &soft_bc_p); + IGL_INLINE void update_weights_and_closest_rotations( igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &uv); + IGL_INLINE void compute_jacobians(igl::SLIMData& s, const Eigen::MatrixXd &uv); + IGL_INLINE void build_linear_system(igl::SLIMData& s, Eigen::SparseMatrix &L); + IGL_INLINE void pre_calc(igl::SLIMData& s); + + // Implementation + IGL_INLINE void compute_surface_gradient_matrix(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, + const Eigen::MatrixXd &F1, const Eigen::MatrixXd &F2, + Eigen::SparseMatrix &D1, Eigen::SparseMatrix &D2) + { + + Eigen::SparseMatrix G; + igl::grad(V, F, G); + Eigen::SparseMatrix Dx = G.block(0, 0, F.rows(), V.rows()); + Eigen::SparseMatrix Dy = G.block(F.rows(), 0, F.rows(), V.rows()); + Eigen::SparseMatrix Dz = G.block(2 * F.rows(), 0, F.rows(), V.rows()); + + D1 = F1.col(0).asDiagonal() * Dx + F1.col(1).asDiagonal() * Dy + F1.col(2).asDiagonal() * Dz; + D2 = F2.col(0).asDiagonal() * Dx + F2.col(1).asDiagonal() * Dy + F2.col(2).asDiagonal() * Dz; + } + + IGL_INLINE void compute_jacobians(igl::SLIMData& s, const Eigen::MatrixXd &uv) + { + if (s.F.cols() == 3) + { + // Ji=[D1*u,D2*u,D1*v,D2*v]; + s.Ji.col(0) = s.Dx * uv.col(0); + s.Ji.col(1) = s.Dy * uv.col(0); + s.Ji.col(2) = s.Dx * uv.col(1); + s.Ji.col(3) = s.Dy * uv.col(1); + } + else /*tet mesh*/{ + // Ji=[D1*u,D2*u,D3*u, D1*v,D2*v, D3*v, D1*w,D2*w,D3*w]; + s.Ji.col(0) = s.Dx * uv.col(0); + s.Ji.col(1) = s.Dy * uv.col(0); + s.Ji.col(2) = s.Dz * uv.col(0); + s.Ji.col(3) = s.Dx * uv.col(1); + s.Ji.col(4) = s.Dy * uv.col(1); + s.Ji.col(5) = s.Dz * uv.col(1); + s.Ji.col(6) = s.Dx * uv.col(2); + s.Ji.col(7) = s.Dy * uv.col(2); + s.Ji.col(8) = s.Dz * uv.col(2); + } + } + + IGL_INLINE void update_weights_and_closest_rotations(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &uv) + { + compute_jacobians(s, uv); + + const double eps = 1e-8; + double exp_f = s.exp_factor; + + if (s.dim == 2) + { + for (int i = 0; i < s.Ji.rows(); ++i) + { + typedef Eigen::Matrix Mat2; + typedef Eigen::Matrix Vec2; + Mat2 ji, ri, ti, ui, vi; + Vec2 sing; + Vec2 closest_sing_vec; + Mat2 mat_W; + Vec2 m_sing_new; + double s1, s2; + + ji(0, 0) = s.Ji(i, 0); + ji(0, 1) = s.Ji(i, 1); + ji(1, 0) = s.Ji(i, 2); + ji(1, 1) = s.Ji(i, 3); + + igl::polar_svd(ji, ri, ti, ui, sing, vi); + + s1 = sing(0); + s2 = sing(1); + + // Update Weights according to energy + switch (s.slim_energy) + { + case igl::SLIMData::ARAP: + { + m_sing_new << 1, 1; + break; + } + case igl::SLIMData::SYMMETRIC_DIRICHLET: + { + double s1_g = 2 * (s1 - pow(s1, -3)); + double s2_g = 2 * (s2 - pow(s2, -3)); + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))); + break; + } + case igl::SLIMData::LOG_ARAP: + { + double s1_g = 2 * (log(s1) / s1); + double s2_g = 2 * (log(s2) / s2); + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))); + break; + } + case igl::SLIMData::CONFORMAL: + { + double s1_g = 1 / (2 * s2) - s2 / (2 * pow(s1, 2)); + double s2_g = 1 / (2 * s1) - s1 / (2 * pow(s2, 2)); + + double geo_avg = sqrt(s1 * s2); + double s1_min = geo_avg; + double s2_min = geo_avg; + + m_sing_new << sqrt(s1_g / (2 * (s1 - s1_min))), sqrt(s2_g / (2 * (s2 - s2_min))); + + // change local step + closest_sing_vec << s1_min, s2_min; + ri = ui * closest_sing_vec.asDiagonal() * vi.transpose(); + break; + } + case igl::SLIMData::EXP_CONFORMAL: + { + double s1_g = 2 * (s1 - pow(s1, -3)); + double s2_g = 2 * (s2 - pow(s2, -3)); + + double geo_avg = sqrt(s1 * s2); + double s1_min = geo_avg; + double s2_min = geo_avg; + + double in_exp = exp_f * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2)); + double exp_thing = exp(in_exp); + + s1_g *= exp_thing * exp_f; + s2_g *= exp_thing * exp_f; + + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))); + break; + } + case igl::SLIMData::EXP_SYMMETRIC_DIRICHLET: + { + double s1_g = 2 * (s1 - pow(s1, -3)); + double s2_g = 2 * (s2 - pow(s2, -3)); + + double in_exp = exp_f * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2)); + double exp_thing = exp(in_exp); + + s1_g *= exp_thing * exp_f; + s2_g *= exp_thing * exp_f; + + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))); + break; + } + } + + if (std::abs(s1 - 1) < eps) m_sing_new(0) = 1; + if (std::abs(s2 - 1) < eps) m_sing_new(1) = 1; + mat_W = ui * m_sing_new.asDiagonal() * ui.transpose(); + + s.W_11(i) = mat_W(0, 0); + s.W_12(i) = mat_W(0, 1); + s.W_21(i) = mat_W(1, 0); + s.W_22(i) = mat_W(1, 1); + + // 2) Update local step (doesn't have to be a rotation, for instance in case of conformal energy) + s.Ri(i, 0) = ri(0, 0); + s.Ri(i, 1) = ri(1, 0); + s.Ri(i, 2) = ri(0, 1); + s.Ri(i, 3) = ri(1, 1); + } + } + else + { + typedef Eigen::Matrix Vec3; + typedef Eigen::Matrix Mat3; + Mat3 ji; + Vec3 m_sing_new; + Vec3 closest_sing_vec; + const double sqrt_2 = sqrt(2); + for (int i = 0; i < s.Ji.rows(); ++i) + { + ji(0, 0) = s.Ji(i, 0); + ji(0, 1) = s.Ji(i, 1); + ji(0, 2) = s.Ji(i, 2); + ji(1, 0) = s.Ji(i, 3); + ji(1, 1) = s.Ji(i, 4); + ji(1, 2) = s.Ji(i, 5); + ji(2, 0) = s.Ji(i, 6); + ji(2, 1) = s.Ji(i, 7); + ji(2, 2) = s.Ji(i, 8); + + Mat3 ri, ti, ui, vi; + Vec3 sing; + igl::polar_svd(ji, ri, ti, ui, sing, vi); + + double s1 = sing(0); + double s2 = sing(1); + double s3 = sing(2); + + // 1) Update Weights + switch (s.slim_energy) + { + case igl::SLIMData::ARAP: + { + m_sing_new << 1, 1, 1; + break; + } + case igl::SLIMData::LOG_ARAP: + { + double s1_g = 2 * (log(s1) / s1); + double s2_g = 2 * (log(s2) / s2); + double s3_g = 2 * (log(s3) / s3); + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))), sqrt(s3_g / (2 * (s3 - 1))); + break; + } + case igl::SLIMData::SYMMETRIC_DIRICHLET: + { + double s1_g = 2 * (s1 - pow(s1, -3)); + double s2_g = 2 * (s2 - pow(s2, -3)); + double s3_g = 2 * (s3 - pow(s3, -3)); + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))), sqrt(s3_g / (2 * (s3 - 1))); + break; + } + case igl::SLIMData::EXP_SYMMETRIC_DIRICHLET: + { + double s1_g = 2 * (s1 - pow(s1, -3)); + double s2_g = 2 * (s2 - pow(s2, -3)); + double s3_g = 2 * (s3 - pow(s3, -3)); + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))), sqrt(s3_g / (2 * (s3 - 1))); + + double in_exp = exp_f * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2) + pow(s3, 2) + pow(s3, -2)); + double exp_thing = exp(in_exp); + + s1_g *= exp_thing * exp_f; + s2_g *= exp_thing * exp_f; + s3_g *= exp_thing * exp_f; + + m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1))), sqrt(s3_g / (2 * (s3 - 1))); + + break; + } + case igl::SLIMData::CONFORMAL: + { + double common_div = 9 * (pow(s1 * s2 * s3, 5. / 3.)); + + double s1_g = (-2 * s2 * s3 * (pow(s2, 2) + pow(s3, 2) - 2 * pow(s1, 2))) / common_div; + double s2_g = (-2 * s1 * s3 * (pow(s1, 2) + pow(s3, 2) - 2 * pow(s2, 2))) / common_div; + double s3_g = (-2 * s1 * s2 * (pow(s1, 2) + pow(s2, 2) - 2 * pow(s3, 2))) / common_div; + + double closest_s = sqrt(pow(s1, 2) + pow(s3, 2)) / sqrt_2; + double s1_min = closest_s; + double s2_min = closest_s; + double s3_min = closest_s; + + m_sing_new << sqrt(s1_g / (2 * (s1 - s1_min))), sqrt(s2_g / (2 * (s2 - s2_min))), sqrt( + s3_g / (2 * (s3 - s3_min))); + + // change local step + closest_sing_vec << s1_min, s2_min, s3_min; + ri = ui * closest_sing_vec.asDiagonal() * vi.transpose(); + break; + } + case igl::SLIMData::EXP_CONFORMAL: + { + // E_conf = (s1^2 + s2^2 + s3^2)/(3*(s1*s2*s3)^(2/3) ) + // dE_conf/ds1 = (-2*(s2*s3)*(s2^2+s3^2 -2*s1^2) ) / (9*(s1*s2*s3)^(5/3)) + // Argmin E_conf(s1): s1 = sqrt(s1^2+s2^2)/sqrt(2) + double common_div = 9 * (pow(s1 * s2 * s3, 5. / 3.)); + + double s1_g = (-2 * s2 * s3 * (pow(s2, 2) + pow(s3, 2) - 2 * pow(s1, 2))) / common_div; + double s2_g = (-2 * s1 * s3 * (pow(s1, 2) + pow(s3, 2) - 2 * pow(s2, 2))) / common_div; + double s3_g = (-2 * s1 * s2 * (pow(s1, 2) + pow(s2, 2) - 2 * pow(s3, 2))) / common_div; + + double in_exp = exp_f * ((pow(s1, 2) + pow(s2, 2) + pow(s3, 2)) / (3 * pow((s1 * s2 * s3), 2. / 3)));; + double exp_thing = exp(in_exp); + + double closest_s = sqrt(pow(s1, 2) + pow(s3, 2)) / sqrt_2; + double s1_min = closest_s; + double s2_min = closest_s; + double s3_min = closest_s; + + s1_g *= exp_thing * exp_f; + s2_g *= exp_thing * exp_f; + s3_g *= exp_thing * exp_f; + + m_sing_new << sqrt(s1_g / (2 * (s1 - s1_min))), sqrt(s2_g / (2 * (s2 - s2_min))), sqrt( + s3_g / (2 * (s3 - s3_min))); + + // change local step + closest_sing_vec << s1_min, s2_min, s3_min; + ri = ui * closest_sing_vec.asDiagonal() * vi.transpose(); + } + } + if (std::abs(s1 - 1) < eps) m_sing_new(0) = 1; + if (std::abs(s2 - 1) < eps) m_sing_new(1) = 1; + if (std::abs(s3 - 1) < eps) m_sing_new(2) = 1; + Mat3 mat_W; + mat_W = ui * m_sing_new.asDiagonal() * ui.transpose(); + + s.W_11(i) = mat_W(0, 0); + s.W_12(i) = mat_W(0, 1); + s.W_13(i) = mat_W(0, 2); + s.W_21(i) = mat_W(1, 0); + s.W_22(i) = mat_W(1, 1); + s.W_23(i) = mat_W(1, 2); + s.W_31(i) = mat_W(2, 0); + s.W_32(i) = mat_W(2, 1); + s.W_33(i) = mat_W(2, 2); + + // 2) Update closest rotations (not rotations in case of conformal energy) + s.Ri(i, 0) = ri(0, 0); + s.Ri(i, 1) = ri(1, 0); + s.Ri(i, 2) = ri(2, 0); + s.Ri(i, 3) = ri(0, 1); + s.Ri(i, 4) = ri(1, 1); + s.Ri(i, 5) = ri(2, 1); + s.Ri(i, 6) = ri(0, 2); + s.Ri(i, 7) = ri(1, 2); + s.Ri(i, 8) = ri(2, 2); + } // for loop end + + } // if dim end + + } + + IGL_INLINE void solve_weighted_arap(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &uv, + Eigen::VectorXi &soft_b_p, + Eigen::MatrixXd &soft_bc_p) + { + using namespace Eigen; + + Eigen::SparseMatrix L; + build_linear_system(s,L); + + igl::Timer t; + + //t.start(); + // solve + Eigen::VectorXd Uc; +#ifndef CHOLMOD + if (s.dim == 2) + { + SimplicialLDLT > solver; + Uc = solver.compute(L).solve(s.rhs); + } + else + { // seems like CG performs much worse for 2D and way better for 3D + Eigen::VectorXd guess(uv.rows() * s.dim); + for (int i = 0; i < s.v_num; i++) for (int j = 0; j < s.dim; j++) guess(uv.rows() * j + i) = uv(i, j); // flatten vector + ConjugateGradient, Lower | Upper> cg; + cg.setTolerance(1e-8); + cg.compute(L); + Uc = cg.solveWithGuess(s.rhs, guess); + } +#else + CholmodSimplicialLDLT > solver; + Uc = solver.compute(L).solve(s.rhs); +#endif + for (int i = 0; i < s.dim; i++) + uv.col(i) = Uc.block(i * s.v_n, 0, s.v_n, 1); + + // t.stop(); + // std::cerr << "solve: " << t.getElapsedTime() << std::endl; + + } + + + IGL_INLINE void pre_calc(igl::SLIMData& s) + { + if (!s.has_pre_calc) + { + s.v_n = s.v_num; + s.f_n = s.f_num; + + if (s.F.cols() == 3) + { + s.dim = 2; + Eigen::MatrixXd F1, F2, F3; + igl::local_basis(s.V, s.F, F1, F2, F3); + compute_surface_gradient_matrix(s.V, s.F, F1, F2, s.Dx, s.Dy); + + s.W_11.resize(s.f_n); + s.W_12.resize(s.f_n); + s.W_21.resize(s.f_n); + s.W_22.resize(s.f_n); + } + else + { + s.dim = 3; + Eigen::SparseMatrix G; + igl::grad(s.V, s.F, G, + s.mesh_improvement_3d /*use normal gradient, or one from a "regular" tet*/); + s.Dx = G.block(0, 0, s.F.rows(), s.V.rows()); + s.Dy = G.block(s.F.rows(), 0, s.F.rows(), s.V.rows()); + s.Dz = G.block(2 * s.F.rows(), 0, s.F.rows(), s.V.rows()); + + + s.W_11.resize(s.f_n); + s.W_12.resize(s.f_n); + s.W_13.resize(s.f_n); + s.W_21.resize(s.f_n); + s.W_22.resize(s.f_n); + s.W_23.resize(s.f_n); + s.W_31.resize(s.f_n); + s.W_32.resize(s.f_n); + s.W_33.resize(s.f_n); + } + + s.Dx.makeCompressed(); + s.Dy.makeCompressed(); + s.Dz.makeCompressed(); + s.Ri.resize(s.f_n, s.dim * s.dim); + s.Ji.resize(s.f_n, s.dim * s.dim); + s.rhs.resize(s.dim * s.v_num); + + // flattened weight matrix + s.WGL_M.resize(s.dim * s.dim * s.f_n); + for (int i = 0; i < s.dim * s.dim; i++) + for (int j = 0; j < s.f_n; j++) + s.WGL_M(i * s.f_n + j) = s.M(j); + + s.first_solve = true; + s.has_pre_calc = true; + } + } + + IGL_INLINE void build_linear_system(igl::SLIMData& s, Eigen::SparseMatrix &L) + { + // formula (35) in paper + std::vector > IJV; + + #ifdef SLIM_CACHED + buildA(s,IJV); + if (s.A.rows() == 0) + { + s.A = Eigen::SparseMatrix(s.dim * s.dim * s.f_n, s.dim * s.v_n); + igl::sparse_cached_precompute(IJV,s.A_data,s.A); + } + else + igl::sparse_cached(IJV,s.A_data,s.A); + #else + Eigen::SparseMatrix A(s.dim * s.dim * s.f_n, s.dim * s.v_n); + buildA(s,IJV); + A.setFromTriplets(IJV.begin(),IJV.end()); + A.makeCompressed(); + #endif + + #ifdef SLIM_CACHED + #else + Eigen::SparseMatrix At = A.transpose(); + At.makeCompressed(); + #endif + + #ifdef SLIM_CACHED + Eigen::SparseMatrix id_m(s.A.cols(), s.A.cols()); + #else + Eigen::SparseMatrix id_m(A.cols(), A.cols()); + #endif + + id_m.setIdentity(); + + // add proximal penalty + #ifdef SLIM_CACHED + s.AtA_data.W = s.WGL_M; + if (s.AtA.rows() == 0) + igl::AtA_cached_precompute(s.A,s.AtA_data,s.AtA); + else + igl::AtA_cached(s.A,s.AtA_data,s.AtA); + + L = s.AtA + s.proximal_p * id_m; //add also a proximal + L.makeCompressed(); + + #else + L = At * s.WGL_M.asDiagonal() * A + s.proximal_p * id_m; //add also a proximal term + L.makeCompressed(); + #endif + + #ifdef SLIM_CACHED + buildRhs(s, s.A); + #else + buildRhs(s, A); + #endif + + Eigen::SparseMatrix OldL = L; + add_soft_constraints(s,L); + L.makeCompressed(); + } + + IGL_INLINE void add_soft_constraints(igl::SLIMData& s, Eigen::SparseMatrix &L) + { + int v_n = s.v_num; + for (int d = 0; d < s.dim; d++) + { + for (int i = 0; i < s.b.rows(); i++) + { + int v_idx = s.b(i); + s.rhs(d * v_n + v_idx) += s.soft_const_p * s.bc(i, d); // rhs + L.coeffRef(d * v_n + v_idx, d * v_n + v_idx) += s.soft_const_p; // diagonal of matrix + } + } + } + + IGL_INLINE double compute_energy(igl::SLIMData& s, Eigen::MatrixXd &V_new) + { + compute_jacobians(s,V_new); + return compute_energy_with_jacobians(s, s.V, s.F, s.Ji, V_new, s.M) + + compute_soft_const_energy(s, s.V, s.F, V_new); + } + + IGL_INLINE double compute_soft_const_energy(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + Eigen::MatrixXd &V_o) + { + double e = 0; + for (int i = 0; i < s.b.rows(); i++) + { + e += s.soft_const_p * (s.bc.row(i) - V_o.row(s.b(i))).squaredNorm(); + } + return e; + } + + IGL_INLINE double compute_energy_with_jacobians(igl::SLIMData& s, + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, const Eigen::MatrixXd &Ji, + Eigen::MatrixXd &uv, Eigen::VectorXd &areas) + { + + double energy = 0; + if (s.dim == 2) + { + Eigen::Matrix ji; + for (int i = 0; i < s.f_n; i++) + { + ji(0, 0) = Ji(i, 0); + ji(0, 1) = Ji(i, 1); + ji(1, 0) = Ji(i, 2); + ji(1, 1) = Ji(i, 3); + + typedef Eigen::Matrix Mat2; + typedef Eigen::Matrix Vec2; + Mat2 ri, ti, ui, vi; + Vec2 sing; + igl::polar_svd(ji, ri, ti, ui, sing, vi); + double s1 = sing(0); + double s2 = sing(1); + + switch (s.slim_energy) + { + case igl::SLIMData::ARAP: + { + energy += areas(i) * (pow(s1 - 1, 2) + pow(s2 - 1, 2)); + break; + } + case igl::SLIMData::SYMMETRIC_DIRICHLET: + { + energy += areas(i) * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2)); + break; + } + case igl::SLIMData::EXP_SYMMETRIC_DIRICHLET: + { + energy += areas(i) * exp(s.exp_factor * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2))); + break; + } + case igl::SLIMData::LOG_ARAP: + { + energy += areas(i) * (pow(log(s1), 2) + pow(log(s2), 2)); + break; + } + case igl::SLIMData::CONFORMAL: + { + energy += areas(i) * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2)); + break; + } + case igl::SLIMData::EXP_CONFORMAL: + { + energy += areas(i) * exp(s.exp_factor * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2))); + break; + } + + } + + } + } + else + { + Eigen::Matrix ji; + for (int i = 0; i < s.f_n; i++) + { + ji(0, 0) = Ji(i, 0); + ji(0, 1) = Ji(i, 1); + ji(0, 2) = Ji(i, 2); + ji(1, 0) = Ji(i, 3); + ji(1, 1) = Ji(i, 4); + ji(1, 2) = Ji(i, 5); + ji(2, 0) = Ji(i, 6); + ji(2, 1) = Ji(i, 7); + ji(2, 2) = Ji(i, 8); + + typedef Eigen::Matrix Mat3; + typedef Eigen::Matrix Vec3; + Mat3 ri, ti, ui, vi; + Vec3 sing; + igl::polar_svd(ji, ri, ti, ui, sing, vi); + double s1 = sing(0); + double s2 = sing(1); + double s3 = sing(2); + + switch (s.slim_energy) + { + case igl::SLIMData::ARAP: + { + energy += areas(i) * (pow(s1 - 1, 2) + pow(s2 - 1, 2) + pow(s3 - 1, 2)); + break; + } + case igl::SLIMData::SYMMETRIC_DIRICHLET: + { + energy += areas(i) * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2) + pow(s3, 2) + pow(s3, -2)); + break; + } + case igl::SLIMData::EXP_SYMMETRIC_DIRICHLET: + { + energy += areas(i) * exp(s.exp_factor * + (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2) + pow(s3, 2) + pow(s3, -2))); + break; + } + case igl::SLIMData::LOG_ARAP: + { + energy += areas(i) * (pow(log(s1), 2) + pow(log(std::abs(s2)), 2) + pow(log(std::abs(s3)), 2)); + break; + } + case igl::SLIMData::CONFORMAL: + { + energy += areas(i) * ((pow(s1, 2) + pow(s2, 2) + pow(s3, 2)) / (3 * pow(s1 * s2 * s3, 2. / 3.))); + break; + } + case igl::SLIMData::EXP_CONFORMAL: + { + energy += areas(i) * exp((pow(s1, 2) + pow(s2, 2) + pow(s3, 2)) / (3 * pow(s1 * s2 * s3, 2. / 3.))); + break; + } + } + } + } + + return energy; + } + + IGL_INLINE void buildA(igl::SLIMData& s, std::vector > & IJV) + { + // formula (35) in paper + if (s.dim == 2) + { + IJV.reserve(4 * (s.Dx.outerSize() + s.Dy.outerSize())); + + /*A = [W11*Dx, W12*Dx; + W11*Dy, W12*Dy; + W21*Dx, W22*Dx; + W21*Dy, W22*Dy];*/ + for (int k = 0; k < s.Dx.outerSize(); ++k) + { + for (Eigen::SparseMatrix::InnerIterator it(s.Dx, k); it; ++it) + { + int dx_r = it.row(); + int dx_c = it.col(); + double val = it.value(); + + IJV.push_back(Eigen::Triplet(dx_r, dx_c, val * s.W_11(dx_r))); + IJV.push_back(Eigen::Triplet(dx_r, s.v_n + dx_c, val * s.W_12(dx_r))); + + IJV.push_back(Eigen::Triplet(2 * s.f_n + dx_r, dx_c, val * s.W_21(dx_r))); + IJV.push_back(Eigen::Triplet(2 * s.f_n + dx_r, s.v_n + dx_c, val * s.W_22(dx_r))); + } + } + + for (int k = 0; k < s.Dy.outerSize(); ++k) + { + for (Eigen::SparseMatrix::InnerIterator it(s.Dy, k); it; ++it) + { + int dy_r = it.row(); + int dy_c = it.col(); + double val = it.value(); + + IJV.push_back(Eigen::Triplet(s.f_n + dy_r, dy_c, val * s.W_11(dy_r))); + IJV.push_back(Eigen::Triplet(s.f_n + dy_r, s.v_n + dy_c, val * s.W_12(dy_r))); + + IJV.push_back(Eigen::Triplet(3 * s.f_n + dy_r, dy_c, val * s.W_21(dy_r))); + IJV.push_back(Eigen::Triplet(3 * s.f_n + dy_r, s.v_n + dy_c, val * s.W_22(dy_r))); + } + } + } + else + { + + /*A = [W11*Dx, W12*Dx, W13*Dx; + W11*Dy, W12*Dy, W13*Dy; + W11*Dz, W12*Dz, W13*Dz; + W21*Dx, W22*Dx, W23*Dx; + W21*Dy, W22*Dy, W23*Dy; + W21*Dz, W22*Dz, W23*Dz; + W31*Dx, W32*Dx, W33*Dx; + W31*Dy, W32*Dy, W33*Dy; + W31*Dz, W32*Dz, W33*Dz;];*/ + IJV.reserve(9 * (s.Dx.outerSize() + s.Dy.outerSize() + s.Dz.outerSize())); + for (int k = 0; k < s.Dx.outerSize(); k++) + { + for (Eigen::SparseMatrix::InnerIterator it(s.Dx, k); it; ++it) + { + int dx_r = it.row(); + int dx_c = it.col(); + double val = it.value(); + + IJV.push_back(Eigen::Triplet(dx_r, dx_c, val * s.W_11(dx_r))); + IJV.push_back(Eigen::Triplet(dx_r, s.v_n + dx_c, val * s.W_12(dx_r))); + IJV.push_back(Eigen::Triplet(dx_r, 2 * s.v_n + dx_c, val * s.W_13(dx_r))); + + IJV.push_back(Eigen::Triplet(3 * s.f_n + dx_r, dx_c, val * s.W_21(dx_r))); + IJV.push_back(Eigen::Triplet(3 * s.f_n + dx_r, s.v_n + dx_c, val * s.W_22(dx_r))); + IJV.push_back(Eigen::Triplet(3 * s.f_n + dx_r, 2 * s.v_n + dx_c, val * s.W_23(dx_r))); + + IJV.push_back(Eigen::Triplet(6 * s.f_n + dx_r, dx_c, val * s.W_31(dx_r))); + IJV.push_back(Eigen::Triplet(6 * s.f_n + dx_r, s.v_n + dx_c, val * s.W_32(dx_r))); + IJV.push_back(Eigen::Triplet(6 * s.f_n + dx_r, 2 * s.v_n + dx_c, val * s.W_33(dx_r))); + } + } + + for (int k = 0; k < s.Dy.outerSize(); k++) + { + for (Eigen::SparseMatrix::InnerIterator it(s.Dy, k); it; ++it) + { + int dy_r = it.row(); + int dy_c = it.col(); + double val = it.value(); + + IJV.push_back(Eigen::Triplet(s.f_n + dy_r, dy_c, val * s.W_11(dy_r))); + IJV.push_back(Eigen::Triplet(s.f_n + dy_r, s.v_n + dy_c, val * s.W_12(dy_r))); + IJV.push_back(Eigen::Triplet(s.f_n + dy_r, 2 * s.v_n + dy_c, val * s.W_13(dy_r))); + + IJV.push_back(Eigen::Triplet(4 * s.f_n + dy_r, dy_c, val * s.W_21(dy_r))); + IJV.push_back(Eigen::Triplet(4 * s.f_n + dy_r, s.v_n + dy_c, val * s.W_22(dy_r))); + IJV.push_back(Eigen::Triplet(4 * s.f_n + dy_r, 2 * s.v_n + dy_c, val * s.W_23(dy_r))); + + IJV.push_back(Eigen::Triplet(7 * s.f_n + dy_r, dy_c, val * s.W_31(dy_r))); + IJV.push_back(Eigen::Triplet(7 * s.f_n + dy_r, s.v_n + dy_c, val * s.W_32(dy_r))); + IJV.push_back(Eigen::Triplet(7 * s.f_n + dy_r, 2 * s.v_n + dy_c, val * s.W_33(dy_r))); + } + } + + for (int k = 0; k < s.Dz.outerSize(); k++) + { + for (Eigen::SparseMatrix::InnerIterator it(s.Dz, k); it; ++it) + { + int dz_r = it.row(); + int dz_c = it.col(); + double val = it.value(); + + IJV.push_back(Eigen::Triplet(2 * s.f_n + dz_r, dz_c, val * s.W_11(dz_r))); + IJV.push_back(Eigen::Triplet(2 * s.f_n + dz_r, s.v_n + dz_c, val * s.W_12(dz_r))); + IJV.push_back(Eigen::Triplet(2 * s.f_n + dz_r, 2 * s.v_n + dz_c, val * s.W_13(dz_r))); + + IJV.push_back(Eigen::Triplet(5 * s.f_n + dz_r, dz_c, val * s.W_21(dz_r))); + IJV.push_back(Eigen::Triplet(5 * s.f_n + dz_r, s.v_n + dz_c, val * s.W_22(dz_r))); + IJV.push_back(Eigen::Triplet(5 * s.f_n + dz_r, 2 * s.v_n + dz_c, val * s.W_23(dz_r))); + + IJV.push_back(Eigen::Triplet(8 * s.f_n + dz_r, dz_c, val * s.W_31(dz_r))); + IJV.push_back(Eigen::Triplet(8 * s.f_n + dz_r, s.v_n + dz_c, val * s.W_32(dz_r))); + IJV.push_back(Eigen::Triplet(8 * s.f_n + dz_r, 2 * s.v_n + dz_c, val * s.W_33(dz_r))); + } + } + } + } + + IGL_INLINE void buildRhs(igl::SLIMData& s, const Eigen::SparseMatrix &A) + { + Eigen::VectorXd f_rhs(s.dim * s.dim * s.f_n); + f_rhs.setZero(); + if (s.dim == 2) + { + /*b = [W11*R11 + W12*R21; (formula (36)) + W11*R12 + W12*R22; + W21*R11 + W22*R21; + W21*R12 + W22*R22];*/ + for (int i = 0; i < s.f_n; i++) + { + f_rhs(i + 0 * s.f_n) = s.W_11(i) * s.Ri(i, 0) + s.W_12(i) * s.Ri(i, 1); + f_rhs(i + 1 * s.f_n) = s.W_11(i) * s.Ri(i, 2) + s.W_12(i) * s.Ri(i, 3); + f_rhs(i + 2 * s.f_n) = s.W_21(i) * s.Ri(i, 0) + s.W_22(i) * s.Ri(i, 1); + f_rhs(i + 3 * s.f_n) = s.W_21(i) * s.Ri(i, 2) + s.W_22(i) * s.Ri(i, 3); + } + } + else + { + /*b = [W11*R11 + W12*R21 + W13*R31; + W11*R12 + W12*R22 + W13*R32; + W11*R13 + W12*R23 + W13*R33; + W21*R11 + W22*R21 + W23*R31; + W21*R12 + W22*R22 + W23*R32; + W21*R13 + W22*R23 + W23*R33; + W31*R11 + W32*R21 + W33*R31; + W31*R12 + W32*R22 + W33*R32; + W31*R13 + W32*R23 + W33*R33;];*/ + for (int i = 0; i < s.f_n; i++) + { + f_rhs(i + 0 * s.f_n) = s.W_11(i) * s.Ri(i, 0) + s.W_12(i) * s.Ri(i, 1) + s.W_13(i) * s.Ri(i, 2); + f_rhs(i + 1 * s.f_n) = s.W_11(i) * s.Ri(i, 3) + s.W_12(i) * s.Ri(i, 4) + s.W_13(i) * s.Ri(i, 5); + f_rhs(i + 2 * s.f_n) = s.W_11(i) * s.Ri(i, 6) + s.W_12(i) * s.Ri(i, 7) + s.W_13(i) * s.Ri(i, 8); + f_rhs(i + 3 * s.f_n) = s.W_21(i) * s.Ri(i, 0) + s.W_22(i) * s.Ri(i, 1) + s.W_23(i) * s.Ri(i, 2); + f_rhs(i + 4 * s.f_n) = s.W_21(i) * s.Ri(i, 3) + s.W_22(i) * s.Ri(i, 4) + s.W_23(i) * s.Ri(i, 5); + f_rhs(i + 5 * s.f_n) = s.W_21(i) * s.Ri(i, 6) + s.W_22(i) * s.Ri(i, 7) + s.W_23(i) * s.Ri(i, 8); + f_rhs(i + 6 * s.f_n) = s.W_31(i) * s.Ri(i, 0) + s.W_32(i) * s.Ri(i, 1) + s.W_33(i) * s.Ri(i, 2); + f_rhs(i + 7 * s.f_n) = s.W_31(i) * s.Ri(i, 3) + s.W_32(i) * s.Ri(i, 4) + s.W_33(i) * s.Ri(i, 5); + f_rhs(i + 8 * s.f_n) = s.W_31(i) * s.Ri(i, 6) + s.W_32(i) * s.Ri(i, 7) + s.W_33(i) * s.Ri(i, 8); + } + } + Eigen::VectorXd uv_flat(s.dim *s.v_n); + for (int i = 0; i < s.dim; i++) + for (int j = 0; j < s.v_n; j++) + uv_flat(s.v_n * i + j) = s.V_o(j, i); + + s.rhs = (f_rhs.transpose() * s.WGL_M.asDiagonal() * A).transpose() + s.proximal_p * uv_flat; + } + + } +} + +/// Slim Implementation + +IGL_INLINE void igl::slim_precompute( + const Eigen::MatrixXd &V, + const Eigen::MatrixXi &F, + const Eigen::MatrixXd &V_init, + SLIMData &data, + SLIMData::SLIM_ENERGY slim_energy, + Eigen::VectorXi &b, + Eigen::MatrixXd &bc, + double soft_p) +{ + + data.V = V; + data.F = F; + data.V_o = V_init; + + data.v_num = V.rows(); + data.f_num = F.rows(); + + data.slim_energy = slim_energy; + + data.b = b; + data.bc = bc; + data.soft_const_p = soft_p; + + data.proximal_p = 0.0001; + + igl::doublearea(V, F, data.M); + data.M /= 2.; + data.mesh_area = data.M.sum(); + data.mesh_improvement_3d = false; // whether to use a jacobian derived from a real mesh or an abstract regular mesh (used for mesh improvement) + data.exp_factor = 1.0; // param used only for exponential energies (e.g exponential symmetric dirichlet) + + assert (F.cols() == 3 || F.cols() == 4); + + igl::slim::pre_calc(data); + data.energy = igl::slim::compute_energy(data,data.V_o) / data.mesh_area; +} + +IGL_INLINE Eigen::MatrixXd igl::slim_solve(SLIMData &data, int iter_num) +{ + for (int i = 0; i < iter_num; i++) + { + Eigen::MatrixXd dest_res; + dest_res = data.V_o; + + // Solve Weighted Proxy + igl::slim::update_weights_and_closest_rotations(data,data.V, data.F, dest_res); + igl::slim::solve_weighted_arap(data,data.V, data.F, dest_res, data.b, data.bc); + + double old_energy = data.energy; + + std::function compute_energy = [&]( + Eigen::MatrixXd &aaa) { return igl::slim::compute_energy(data,aaa); }; + + data.energy = igl::flip_avoiding_line_search(data.F, data.V_o, dest_res, compute_energy, + data.energy * data.mesh_area) / data.mesh_area; + } + return data.V_o; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/slim.h b/src/igl/slim.h new file mode 100644 index 000000000..1965e2bba --- /dev/null +++ b/src/igl/slim.h @@ -0,0 +1,113 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Michael Rabinovich +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef SLIM_H +#define SLIM_H + +#include "igl_inline.h" +#include +#include + +// This option makes the iterations faster (all except the first) by caching the +// sparsity pattern of the matrix involved in the assembly. It should be on if you plan to do many iterations, off if you have to change the matrix structure at every iteration. +#define SLIM_CACHED + +#ifdef SLIM_CACHED +#include +#endif + +namespace igl +{ + +// Compute a SLIM map as derived in "Scalable Locally Injective Maps" [Rabinovich et al. 2016]. +struct SLIMData +{ + // Input + Eigen::MatrixXd V; // #V by 3 list of mesh vertex positions + Eigen::MatrixXi F; // #F by 3/3 list of mesh faces (triangles/tets) + enum SLIM_ENERGY + { + ARAP, + LOG_ARAP, + SYMMETRIC_DIRICHLET, + CONFORMAL, + EXP_CONFORMAL, + EXP_SYMMETRIC_DIRICHLET + }; + SLIM_ENERGY slim_energy; + + // Optional Input + // soft constraints + Eigen::VectorXi b; + Eigen::MatrixXd bc; + double soft_const_p; + + double exp_factor; // used for exponential energies, ignored otherwise + bool mesh_improvement_3d; // only supported for 3d + + // Output + Eigen::MatrixXd V_o; // #V by dim list of mesh vertex positions (dim = 2 for parametrization, 3 otherwise) + double energy; // objective value + + // INTERNAL + Eigen::VectorXd M; + double mesh_area; + double avg_edge_length; + int v_num; + int f_num; + double proximal_p; + + Eigen::VectorXd WGL_M; + Eigen::VectorXd rhs; + Eigen::MatrixXd Ri,Ji; + Eigen::VectorXd W_11; Eigen::VectorXd W_12; Eigen::VectorXd W_13; + Eigen::VectorXd W_21; Eigen::VectorXd W_22; Eigen::VectorXd W_23; + Eigen::VectorXd W_31; Eigen::VectorXd W_32; Eigen::VectorXd W_33; + Eigen::SparseMatrix Dx,Dy,Dz; + int f_n,v_n; + bool first_solve; + bool has_pre_calc = false; + int dim; + + #ifdef SLIM_CACHED + Eigen::SparseMatrix A; + Eigen::VectorXi A_data; + Eigen::SparseMatrix AtA; + igl::AtA_cached_data AtA_data; + #endif +}; + +// Compute necessary information to start using SLIM +// Inputs: +// V #V by 3 list of mesh vertex positions +// F #F by 3/3 list of mesh faces (triangles/tets) +// b list of boundary indices into V +// bc #b by dim list of boundary conditions +// soft_p Soft penalty factor (can be zero) +// slim_energy Energy to minimize +IGL_INLINE void slim_precompute( + const Eigen::MatrixXd& V, + const Eigen::MatrixXi& F, + const Eigen::MatrixXd& V_init, + SLIMData& data, + SLIMData::SLIM_ENERGY slim_energy, + Eigen::VectorXi& b, + Eigen::MatrixXd& bc, + double soft_p); + +// Run iter_num iterations of SLIM +// Outputs: +// V_o (in SLIMData): #V by dim list of mesh vertex positions +IGL_INLINE Eigen::MatrixXd slim_solve(SLIMData& data, int iter_num); + +} // END NAMESPACE + +#ifndef IGL_STATIC_LIBRARY +# include "slim.cpp" +#endif + +#endif // SLIM_H diff --git a/src/igl/snap_points.cpp b/src/igl/snap_points.cpp new file mode 100644 index 000000000..1fc136f4c --- /dev/null +++ b/src/igl/snap_points.cpp @@ -0,0 +1,89 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "snap_points.h" +#include +#include + +template < + typename DerivedC, + typename DerivedV, + typename DerivedI, + typename DerivedminD, + typename DerivedVI> +IGL_INLINE void igl::snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & minD, + Eigen::PlainObjectBase & VI) +{ + snap_points(C,V,I,minD); + const int m = C.rows(); + VI.resize(m,V.cols()); + for(int c = 0;c +IGL_INLINE void igl::snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & minD) +{ + using namespace std; + const int n = V.rows(); + const int m = C.rows(); + assert(V.cols() == C.cols() && "Dimensions should match"); + // O(m*n) + // + // I believe there should be a way to do this in O(m*log(n) + n) assuming + // reasonably distubed points. + I.resize(m,1); + typedef typename DerivedV::Scalar Scalar; + minD.setConstant(m,1,numeric_limits::max()); + for(int v = 0;v +IGL_INLINE void igl::snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I) +{ + Eigen::Matrix minD; + return igl::snap_points(C,V,I,minD); +} + + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::snap_points, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::snap_points, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/snap_points.h b/src/igl/snap_points.h new file mode 100644 index 000000000..dbb850a79 --- /dev/null +++ b/src/igl/snap_points.h @@ -0,0 +1,67 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SNAP_POINTS_H +#define IGL_SNAP_POINTS_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // SNAP_POINTS snap list of points C to closest of another list of points V + // + // [I,minD,VI] = snap_points(C,V) + // + // Inputs: + // C #C by dim list of query point positions + // V #V by dim list of data point positions + // Outputs: + // I #C list of indices into V of closest points to C + // minD #C list of squared (^p) distances to closest points + // VI #C by dim list of new point positions, VI = V(I,:) + template < + typename DerivedC, + typename DerivedV, + typename DerivedI, + typename DerivedminD, + typename DerivedVI> + IGL_INLINE void snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & minD, + Eigen::PlainObjectBase & VI); + template < + typename DerivedC, + typename DerivedV, + typename DerivedI, + typename DerivedminD> + IGL_INLINE void snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I, + Eigen::PlainObjectBase & minD); + template < + typename DerivedC, + typename DerivedV, + typename DerivedI > + IGL_INLINE void snap_points( + const Eigen::PlainObjectBase & C, + const Eigen::PlainObjectBase & V, + Eigen::PlainObjectBase & I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "snap_points.cpp" +#endif + +#endif + + + + diff --git a/src/igl/snap_to_canonical_view_quat.cpp b/src/igl/snap_to_canonical_view_quat.cpp new file mode 100644 index 000000000..9fb01724c --- /dev/null +++ b/src/igl/snap_to_canonical_view_quat.cpp @@ -0,0 +1,117 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "snap_to_canonical_view_quat.h" + +#include "canonical_quaternions.h" +#include "normalize_quat.h" + +#include +#include + +// Note: For the canonical view quaternions it should be completely possible to +// determine this anaylitcally. That is the max_distance should be a +// theoretical known value +// Also: I'm not sure it matters in this case, but. We are dealing with +// quaternions on the 4d unit sphere, but measuring distance in general 4d +// space (i.e. not geodesics on the sphere). Probably something with angles +// would be better. +template +IGL_INLINE bool igl::snap_to_canonical_view_quat( + const Q_type* q, + const Q_type threshold, + Q_type* s) +{ + // Copy input into output + // CANNOT use std::copy here according to: + // http://www.cplusplus.com/reference/algorithm/copy/ + s[0] = q[0]; + s[1] = q[1]; + s[2] = q[2]; + s[3] = q[3]; + + // Normalize input quaternion + Q_type qn[4]; + bool valid_len = + igl::normalize_quat(q,qn); + // If normalizing valid then don't bother + if(!valid_len) + { + return false; + } + + // 0.290019 + const Q_type MAX_DISTANCE = 0.4; + Q_type min_distance = 2*MAX_DISTANCE; + int min_index = -1; + double min_sign = 0; + // loop over canonical view quaternions + for(double sign = -1;sign<=1;sign+=2) + { + for(int i = 0; i(i,j))* + (qn[j]-sign*igl::CANONICAL_VIEW_QUAT(i,j)); + } + if(min_distance > distance) + { + min_distance = distance; + min_index = i; + min_sign = sign; + } + } + } + + if(MAX_DISTANCE < min_distance) + { + fprintf( + stderr, + "ERROR: found new max MIN_DISTANCE: %g\n" + "PLEASE update snap_to_canonical_quat()", + min_distance); + } + + assert(min_distance < MAX_DISTANCE); + assert(min_index >= 0); + + if( min_distance/MAX_DISTANCE <= threshold) + { + // loop over coordinates + for(int j = 0;j<4;j++) + { + s[j] = min_sign*igl::CANONICAL_VIEW_QUAT(min_index,j); + } + return true; + } + return false; +} + +template +IGL_INLINE bool igl::snap_to_canonical_view_quat( + const Eigen::Quaternion & q, + const double threshold, + Eigen::Quaternion & s) +{ + return snap_to_canonical_view_quat( + q.coeffs().data(),threshold,s.coeffs().data()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::snap_to_canonical_view_quat(const double*, double, double*); +// generated by autoexplicit.sh +template bool igl::snap_to_canonical_view_quat(const float*, float, float*); +template bool igl::snap_to_canonical_view_quat(Eigen::Quaternion const&, double, Eigen::Quaternion&); +template bool igl::snap_to_canonical_view_quat(Eigen::Quaternion const&, double, Eigen::Quaternion&); +#endif diff --git a/src/igl/snap_to_canonical_view_quat.h b/src/igl/snap_to_canonical_view_quat.h new file mode 100644 index 000000000..79bc3e024 --- /dev/null +++ b/src/igl/snap_to_canonical_view_quat.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SNAP_TO_CANONICAL_VIEW_QUAT_H +#define IGL_SNAP_TO_CANONICAL_VIEW_QUAT_H +#include "igl_inline.h" +#include +// A Quaternion, q, is defined here as an arrays of four scalars (x,y,z,w), +// such that q = x*i + y*j + z*k + w +namespace igl +{ + // Snap the quaternion q to the nearest canonical view quaternion + // Input: + // q quaternion to be snapped (also see Outputs) + // threshold (optional) threshold: + // 1.0 --> snap any input + // 0.5 --> snap inputs somewhat close to canonical views + // 0.0 --> snap no input + // Output: + // q quaternion possibly set to nearest canonical view + // Return: + // true only if q was snapped to the nearest canonical view + template + IGL_INLINE bool snap_to_canonical_view_quat( + const Q_type* q, + const Q_type threshold, + Q_type* s); + + template + IGL_INLINE bool snap_to_canonical_view_quat( + const Eigen::Quaternion & q, + const double threshold, + Eigen::Quaternion & s); +} + +#ifndef IGL_STATIC_LIBRARY +# include "snap_to_canonical_view_quat.cpp" +#endif + +#endif diff --git a/src/igl/snap_to_fixed_up.cpp b/src/igl/snap_to_fixed_up.cpp new file mode 100644 index 000000000..f0fdd7de8 --- /dev/null +++ b/src/igl/snap_to_fixed_up.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "snap_to_fixed_up.h" + +template +IGL_INLINE void igl::snap_to_fixed_up( + const Eigen::Quaternion & q, + Eigen::Quaternion & s) +{ + using namespace Eigen; + typedef Eigen::Matrix Vector3Q; + const Vector3Q up = q.matrix() * Vector3Q(0,1,0); + Vector3Q proj_up(0,up(1),up(2)); + if(proj_up.norm() == 0) + { + proj_up = Vector3Q(0,1,0); + } + proj_up.normalize(); + Quaternion dq; + dq = Quaternion::FromTwoVectors(up,proj_up); + s = dq * q; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiations +template void igl::snap_to_fixed_up(Eigen::Quaternion const&, Eigen::Quaternion&); +template void igl::snap_to_fixed_up(Eigen::Quaternion const&, Eigen::Quaternion&); +#endif diff --git a/src/igl/snap_to_fixed_up.h b/src/igl/snap_to_fixed_up.h new file mode 100644 index 000000000..4276fa79c --- /dev/null +++ b/src/igl/snap_to_fixed_up.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SNAP_TO_FIXED_UP_H +#define IGL_SNAP_TO_FIXED_UP_H + +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Snap an arbitrary rotation to a rotation resulting from a rotation about + // the y-axis then the x-axis (maintaining fixed up like + // two_axis_valuator_fixed_up.) + // + // Inputs: + // q General rotation as quaternion + // Outputs: + // s the resulting rotation (as quaternion) + // + // See also: two_axis_valuator_fixed_up + template + IGL_INLINE void snap_to_fixed_up( + const Eigen::Quaternion & q, + Eigen::Quaternion & s); +} + +#ifndef IGL_STATIC_LIBRARY +# include "snap_to_fixed_up.cpp" +#endif + +#endif + + diff --git a/src/igl/solid_angle.cpp b/src/igl/solid_angle.cpp new file mode 100644 index 000000000..bbb478af3 --- /dev/null +++ b/src/igl/solid_angle.cpp @@ -0,0 +1,81 @@ +#include "solid_angle.h" +#include "PI.h" +#include + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedP> +IGL_INLINE typename DerivedA::Scalar igl::solid_angle( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & P) +{ + typedef typename DerivedA::Scalar SType; + // Gather vectors to corners + Eigen::Matrix v; + // Don't use this since it will freak out for templates with != 3 size + //v<< (A-P),(B-P),(C-P); + for(int d = 0;d<3;d++) + { + v(0,d) = A(d)-P(d); + v(1,d) = B(d)-P(d); + v(2,d) = C(d)-P(d); + } + Eigen::Matrix vl = v.rowwise().norm(); + //printf("\n"); + // Compute determinant + SType detf = + v(0,0)*v(1,1)*v(2,2)+ + v(1,0)*v(2,1)*v(0,2)+ + v(2,0)*v(0,1)*v(1,2)- + v(2,0)*v(1,1)*v(0,2)- + v(1,0)*v(0,1)*v(2,2)- + v(0,0)*v(2,1)*v(1,2); + // Compute pairwise dotproducts + Eigen::Matrix dp; + dp(0) = v(1,0)*v(2,0); + dp(0) += v(1,1)*v(2,1); + dp(0) += v(1,2)*v(2,2); + dp(1) = v(2,0)*v(0,0); + dp(1) += v(2,1)*v(0,1); + dp(1) += v(2,2)*v(0,2); + dp(2) = v(0,0)*v(1,0); + dp(2) += v(0,1)*v(1,1); + dp(2) += v(0,2)*v(1,2); + // Compute winding number + // Only divide by TWO_PI instead of 4*pi because there was a 2 out front + return atan2(detf, + vl(0)*vl(1)*vl(2) + + dp(0)*vl(0) + + dp(1)*vl(1) + + dp(2)*vl(2)) / (2.*igl::PI); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::solid_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::solid_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::solid_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::solid_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, true>::Scalar igl::solid_angle const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Block const, 1, 3, true>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase const, 1, 3, true> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, false>::Scalar igl::solid_angle const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, 3, false>::Scalar igl::solid_angle const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Block const, 1, 3, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase const, 1, 3, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::solid_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::solid_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::solid_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Block const, 1, -1, false>::Scalar igl::solid_angle const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Block const, 1, -1, false>, Eigen::Matrix >(Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase const, 1, -1, false> > const&, Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/solid_angle.h b/src/igl/solid_angle.h new file mode 100644 index 000000000..2ee98a247 --- /dev/null +++ b/src/igl/solid_angle.h @@ -0,0 +1,29 @@ +#ifndef IGL_SOLID_ANGLE_H +#define IGL_SOLID_ANGLE_H +#include "igl_inline.h" +#include +namespace igl +{ + // Compute the signed solid angle subtended by the oriented 3d triangle (A,B,C) at some point P + // + // Inputs: + // A 3D position of corner + // B 3D position of corner + // C 3D position of corner + // P 3D position of query point + // Returns signed solid angle + template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedP> + IGL_INLINE typename DerivedA::Scalar solid_angle( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & P); +} +#ifndef IGL_STATIC_LIBRARY +# include "solid_angle.cpp" +#endif +#endif diff --git a/src/igl/sort.cpp b/src/igl/sort.cpp new file mode 100644 index 000000000..251b3063e --- /dev/null +++ b/src/igl/sort.cpp @@ -0,0 +1,351 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sort.h" + +#include "SortableRow.h" +#include "reorder.h" +#include "IndexComparison.h" +#include "colon.h" +#include "parallel_for.h" + +#include +#include +#include + +template +IGL_INLINE void igl::sort( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX) +{ + typedef typename DerivedX::Scalar Scalar; + // get number of rows (or columns) + int num_inner = (dim == 1 ? X.rows() : X.cols() ); + // Special case for swapping + switch(num_inner) + { + default: + break; + case 2: + return igl::sort2(X,dim,ascending,Y,IX); + case 3: + return igl::sort3(X,dim,ascending,Y,IX); + } + using namespace Eigen; + // get number of columns (or rows) + int num_outer = (dim == 1 ? X.cols() : X.rows() ); + // dim must be 2 or 1 + assert(dim == 1 || dim == 2); + // Resize output + Y.resizeLike(X); + IX.resizeLike(X); + // idea is to process each column (or row) as a std vector + // loop over columns (or rows) + for(int i = 0; i index_map(num_inner); + std::vector data(num_inner); + for(int j = 0;j +IGL_INLINE void igl::sort_new( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX) +{ + // get number of rows (or columns) + int num_inner = (dim == 1 ? X.rows() : X.cols() ); + // Special case for swapping + switch(num_inner) + { + default: + break; + case 2: + return igl::sort2(X,dim,ascending,Y,IX); + case 3: + return igl::sort3(X,dim,ascending,Y,IX); + } + using namespace Eigen; + // get number of columns (or rows) + int num_outer = (dim == 1 ? X.cols() : X.rows() ); + // dim must be 2 or 1 + assert(dim == 1 || dim == 2); + // Resize output + Y.resizeLike(X); + IX.resizeLike(X); + // idea is to process each column (or row) as a std vector + // loop over columns (or rows) + for(int i = 0; i(X.col(i))); + }else + { + std::sort( + ix.data(), + ix.data()+ix.size(), + igl::IndexVectorLessThan(X.row(i))); + } + // if not ascending then reverse + if(!ascending) + { + std::reverse(ix.data(),ix.data()+ix.size()); + } + for(int j = 0;j +IGL_INLINE void igl::sort2( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedY::Scalar YScalar; + Y = X.derived().template cast(); + + + // get number of columns (or rows) + int num_outer = (dim == 1 ? X.cols() : X.rows() ); + // get number of rows (or columns) + int num_inner = (dim == 1 ? X.rows() : X.cols() ); + assert(num_inner == 2);(void)num_inner; + typedef typename DerivedIX::Scalar Index; + IX.resizeLike(X); + if(dim==1) + { + IX.row(0).setConstant(0);// = DerivedIX::Zero(1,IX.cols()); + IX.row(1).setConstant(1);// = DerivedIX::Ones (1,IX.cols()); + }else + { + IX.col(0).setConstant(0);// = DerivedIX::Zero(IX.rows(),1); + IX.col(1).setConstant(1);// = DerivedIX::Ones (IX.rows(),1); + } + // loop over columns (or rows) + for(int i = 0;ib) || (!ascending && a +IGL_INLINE void igl::sort3( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX) +{ + using namespace Eigen; + using namespace std; + typedef typename DerivedY::Scalar YScalar; + Y = X.derived().template cast(); + Y.resizeLike(X); + for(int j=0;j b) + { + std::swap(a,b); + std::swap(ai,bi); + } + // 123 132 123 231 132 231 + if(b > c) + { + std::swap(b,c); + std::swap(bi,ci); + // 123 123 123 213 123 213 + if(a > b) + { + std::swap(a,b); + std::swap(ai,bi); + } + // 123 123 123 123 123 123 + } + }else + { + // 123 132 213 231 312 321 + if(a < b) + { + std::swap(a,b); + std::swap(ai,bi); + } + // 213 312 213 321 312 321 + if(b < c) + { + std::swap(b,c); + std::swap(bi,ci); + // 231 321 231 321 321 321 + if(a < b) + { + std::swap(a,b); + std::swap(ai,bi); + } + // 321 321 321 321 321 321 + } + } + }; + parallel_for(num_outer,inner,16000); +} + +template +IGL_INLINE void igl::sort( +const std::vector & unsorted, +const bool ascending, +std::vector & sorted, +std::vector & index_map) +{ +// Original unsorted index map +index_map.resize(unsorted.size()); +for(size_t i=0;i& >(unsorted)); + +// if not ascending then reverse +if(!ascending) +{ + std::reverse(index_map.begin(),index_map.end()); +} + // make space for output without clobbering + sorted.resize(unsorted.size()); + // reorder unsorted into sorted using index map + igl::reorder(unsorted,index_map,sorted); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort(std::vector > const&, bool, std::vector >&, std::vector > &); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort_new, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort(std::vector > const&, bool, std::vector >&, std::vector >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, int, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::sort,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,int,bool,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/sort.h b/src/igl/sort.h new file mode 100644 index 000000000..f89f54b13 --- /dev/null +++ b/src/igl/sort.h @@ -0,0 +1,88 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SORT_H +#define IGL_SORT_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + + // Sort the elements of a matrix X along a given dimension like matlabs sort + // function + // + // Templates: + // DerivedX derived scalar type, e.g. MatrixXi or MatrixXd + // DerivedIX derived integer type, e.g. MatrixXi + // Inputs: + // X m by n matrix whose entries are to be sorted + // dim dimensional along which to sort: + // 1 sort each column (matlab default) + // 2 sort each row + // ascending sort ascending (true, matlab default) or descending (false) + // Outputs: + // Y m by n matrix whose entries are sorted + // IX m by n matrix of indices so that if dim = 1, then in matlab notation + // for j = 1:n, Y(:,j) = X(I(:,j),j); end + template + IGL_INLINE void sort( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX); + template + // Only better if size(X,dim) is small + IGL_INLINE void sort_new( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX); + // Special case if size(X,dim) == 2 + template + IGL_INLINE void sort2( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX); + // Special case if size(X,dim) == 3 + template + IGL_INLINE void sort3( + const Eigen::DenseBase& X, + const int dim, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX); + + + // Act like matlab's [Y,I] = SORT(X) for std library vectors + // Templates: + // T should be a class that implements the '<' comparator operator + // Input: + // unsorted unsorted vector + // ascending sort ascending (true, matlab default) or descending (false) + // Output: + // sorted sorted vector, allowed to be same as unsorted + // index_map an index map such that sorted[i] = unsorted[index_map[i]] + template + IGL_INLINE void sort( + const std::vector &unsorted, + const bool ascending, + std::vector &sorted, + std::vector &index_map); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "sort.cpp" +#endif + +#endif diff --git a/src/igl/sort_angles.cpp b/src/igl/sort_angles.cpp new file mode 100644 index 000000000..486f621ea --- /dev/null +++ b/src/igl/sort_angles.cpp @@ -0,0 +1,114 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sort_angles.h" +#include "LinSpaced.h" +#include + +template +IGL_INLINE void igl::sort_angles( + const Eigen::PlainObjectBase& M, + Eigen::PlainObjectBase& R) { + const size_t num_rows = M.rows(); + const size_t num_cols = M.cols(); + assert(num_cols >= 2); + + R.resize(num_rows); + // Have to use << instead of = because Eigen's PlainObjectBase is annoying + R << igl::LinSpaced< + Eigen::Matrix > + (num_rows, 0, num_rows-1); + + // | + // (pi/2, pi) | (0, pi/2) + // | + // -------------+-------------- + // | + // (-pi, -pi/2) | (-pi/2, 0) + // | + auto comp = [&](size_t i, size_t j) { + auto yi = M(i, 0); + auto xi = M(i, 1); + auto yj = M(j, 0); + auto xj = M(j, 1); + + if (xi == xj && yi == yj) { + for (size_t idx=2; idx= 0 && yi >= 0) { + if (xj >=0 && yj >= 0) { + if (xi != xj) { + return xi > xj; + } else { + return yi < yj; + } + } else if (xj < 0 && yj >= 0) { + return true; + } else if (xj < 0 && yj < 0) { + return false; + } else { + return false; + } + } else if (xi < 0 && yi >= 0) { + if (xj >= 0 && yj >= 0) { + return false; + } else if (xj < 0 && yj >= 0) { + if (xi != xj) { + return xi > xj; + } else { + return yi > yj; + } + } else if (xj < 0 && yj < 0) { + return false; + } else { + return false; + } + } else if (xi < 0 && yi < 0) { + if (xj >= 0 && yj >= 0) { + return true; + } else if (xj < 0 && yj >= 0) { + return true; + } else if (xj < 0 && yj < 0) { + if (xi != xj) { + return xi < xj; + } else { + return yi > yj; + } + } else { + return true; + } + } else { + if (xj >= 0 && yj >= 0) { + return true; + } else if (xj < 0 && yj >= 0) { + return true; + } else if (xj < 0 && yj < 0) { + return false; + } else { + if (xi != xj) { + return xi < xj; + } else { + return yi < yj; + } + } + } + }; + std::sort(R.data(), R.data() + num_rows, comp); +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::sort_angles, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/sort_angles.h b/src/igl/sort_angles.h new file mode 100644 index 000000000..4763c981e --- /dev/null +++ b/src/igl/sort_angles.h @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef SORT_ANGLES_H +#define SORT_ANGLES_H + +#include "igl_inline.h" +#include + +namespace igl { + // Sort angles in ascending order in a numerically robust way. + // + // Instead of computing angles using atan2(y, x), sort directly on (y, x). + // + // Inputs: + // M: m by n matrix of scalars. (n >= 2). Assuming the first column of M + // contains values for y, and the second column is x. Using the rest + // of the columns as tie-breaker. + // R: an array of m indices. M.row(R[i]) contains the i-th smallest + // angle. + template + IGL_INLINE void sort_angles( + const Eigen::PlainObjectBase& M, + Eigen::PlainObjectBase& R); +} + +#ifndef IGL_STATIC_LIBRARY +#include "sort_angles.cpp" +#endif + +#endif diff --git a/src/igl/sort_triangles.cpp b/src/igl/sort_triangles.cpp new file mode 100644 index 000000000..98fd8ab1a --- /dev/null +++ b/src/igl/sort_triangles.cpp @@ -0,0 +1,498 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sort_triangles.h" +#include "barycenter.h" +#include "sort.h" +#include "sortrows.h" +#include "slice.h" +#include "round.h" +#include "colon.h" + +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedMV, + typename DerivedP, + typename DerivedFF, + typename DerivedI> +IGL_INLINE void igl::sort_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & MV, + const Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I) +{ + using namespace Eigen; + using namespace std; + + + // Barycenter, centroid + Eigen::Matrix D,sD; + Eigen::Matrix BC,PBC; + barycenter(V,F,BC); + D = BC*(MV.transpose()*P.transpose().eval().col(2)); + sort(D,1,false,sD,I); + + //// Closest corner + //Eigen::Matrix D,sD; + //D.setConstant(F.rows(),1,-1e26); + //for(int c = 0;c<3;c++) + //{ + // Eigen::Matrix C; + // Eigen::Matrix DC; + // C.resize(F.rows(),4); + // for(int f = 0;fD.array()).select(DC,D).eval(); + //} + //sort(D,1,false,sD,I); + + //// Closest corner with tie breaks + //Eigen::Matrix D,sD,ssD; + //D.resize(F.rows(),3); + //for(int c = 0;c<3;c++) + //{ + // Eigen::Matrix C; + // C.resize(F.rows(),4); + // for(int f = 0;f +//#include +// +//static int tough_count = 0; +//template +//class Triangle +//{ +// public: +// static inline bool z_comp(const Vec3 & A, const Vec3 & B) +// { +// return A(2) > B(2); +// } +// static typename Vec3::Scalar ZERO() +// { +// return igl::EPS(); +// return 0; +// } +// public: +// int id; +// // Sorted projected coners: c[0] has smallest z value +// Vec3 c[3]; +// Vec3 n; +// public: +// Triangle():id(-1) { }; +// Triangle(int id, const Vec3 c0, const Vec3 c1, const Vec3 c2): +// id(id) +// { +// using namespace std; +// c[0] = c0; +// c[1] = c1; +// c[2] = c2; +// sort(c,c+3,Triangle::z_comp); +// // normal pointed toward viewpoint +// n = (c0-c1).cross(c2-c0); +// if(n(2) < 0) +// { +// n *= -1.0; +// } +// // Avoid NaNs +// typename Vec3::Scalar len = n.norm(); +// if(len == 0) +// { +// cout<<"avoid NaN"<=0); +// return n.dot(r-c[closest]); +// } +// +// // Z-values of this are < z-values of that +// bool is_completely_behind(const Triangle & that) const +// { +// const typename Vec3::Scalar ac0 = that.c[0](2); +// const typename Vec3::Scalar ac1 = that.c[1](2); +// const typename Vec3::Scalar ac2 = that.c[2](2); +// const typename Vec3::Scalar ic0 = this->c[0](2); +// const typename Vec3::Scalar ic1 = this->c[1](2); +// const typename Vec3::Scalar ic2 = this->c[2](2); +// return +// (ic0 < ac2 && ic1 <= ac2 && ic2 <= ac2) || +// (ic0 <= ac2 && ic1 < ac2 && ic2 <= ac2) || +// (ic0 <= ac2 && ic1 <= ac2 && ic2 < ac2); +// } +// +// bool is_behind_plane(const Triangle &that) const +// { +// using namespace std; +// const typename Vec3::Scalar apc0 = that.project(this->c[0]); +// const typename Vec3::Scalar apc1 = that.project(this->c[1]); +// const typename Vec3::Scalar apc2 = that.project(this->c[2]); +// cout<<" "<< +// apc0<<", "<< +// apc1<<", "<< +// apc2<<", "<c[0]); +// const typename Vec3::Scalar apc1 = that.project(this->c[1]); +// const typename Vec3::Scalar apc2 = that.project(this->c[2]); +// cout<<" "<< +// apc0<<", "<< +// apc1<<", "<< +// apc2<<", "< ZERO() && apc1 > ZERO() && apc2 > ZERO()); +// } +// +// bool is_coplanar(const Triangle &that) const +// { +// using namespace std; +// const typename Vec3::Scalar apc0 = that.project(this->c[0]); +// const typename Vec3::Scalar apc1 = that.project(this->c[1]); +// const typename Vec3::Scalar apc2 = that.project(this->c[2]); +// return (fabs(apc0)<=ZERO() && fabs(apc1)<=ZERO() && fabs(apc2)<=ZERO()); +// } +// +// // http://stackoverflow.com/a/14561664/148668 +// // a1 is line1 start, a2 is line1 end, b1 is line2 start, b2 is line2 end +// static bool seg_seg_intersect(const Vec3 & a1, const Vec3 & a2, const Vec3 & b1, const Vec3 & b2) +// { +// Vec3 b = a2-a1; +// Vec3 d = b2-b1; +// typename Vec3::Scalar bDotDPerp = b(0) * d(1) - b(1) * d(0); +// +// // if b dot d == 0, it means the lines are parallel so have infinite intersection points +// if (bDotDPerp == 0) +// return false; +// +// Vec3 c = b1-a1; +// typename Vec3::Scalar t = (c(0) * d(1) - c(1) * d(0)) / bDotDPerp; +// if (t < 0 || t > 1) +// return false; +// +// typename Vec3::Scalar u = (c(0) * b(1) - c(1) * b(0)) / bDotDPerp; +// if (u < 0 || u > 1) +// return false; +// +// return true; +// } +// bool has_corner_inside(const Triangle & that) const +// { +// // http://www.blackpawn.com/texts/pointinpoly/ +// // Compute vectors +// Vec3 A = that.c[0]; +// Vec3 B = that.c[1]; +// Vec3 C = that.c[2]; +// A(2) = B(2) = C(2) = 0; +// for(int ci = 0;ci<3;ci++) +// { +// Vec3 P = this->c[ci]; +// P(2) = 0; +// +// Vec3 v0 = C - A; +// Vec3 v1 = B - A; +// Vec3 v2 = P - A; +// +// // Compute dot products +// typename Vec3::Scalar dot00 = v0.dot(v0); +// typename Vec3::Scalar dot01 = v0.dot(v1); +// typename Vec3::Scalar dot02 = v0.dot(v2); +// typename Vec3::Scalar dot11 = v1.dot(v1); +// typename Vec3::Scalar dot12 = v1.dot(v2); +// +// // Compute barycentric coordinates +// typename Vec3::Scalar invDenom = 1 / (dot00 * dot11 - dot01 * dot01); +// typename Vec3::Scalar u = (dot11 * dot02 - dot01 * dot12) * invDenom; +// typename Vec3::Scalar v = (dot00 * dot12 - dot01 * dot02) * invDenom; +// +// // Check if point is in triangle +// if((u >= 0) && (v >= 0) && (u + v < 1)) +// { +// return true; +// } +// } +// return false; +// } +// +// bool overlaps(const Triangle &that) const +// { +// // Edges cross +// for(int e = 0;e<3;e++) +// { +// for(int f = 0;f<3;f++) +// { +// if(seg_seg_intersect( +// this->c[e],this->c[(e+1)%3], +// that.c[e],that.c[(e+1)%3])) +// { +// return true; +// } +// } +// } +// // This could be entirely inside that +// if(this->has_corner_inside(that)) +// { +// return true; +// } +// // vice versa +// if(that.has_corner_inside(*this)) +// { +// return true; +// } +// return false; +// } +// +// +// bool operator< (const Triangle &that) const +// { +// // THIS < THAT if "depth" of THIS < "depth" of THAT +// // " if THIS should be draw before THAT +// using namespace std; +// bool ret = false; +// // Self compare +// if(that.id == this->id) +// { +// ret = false; +// } +// if(this->is_completely_behind(that)) +// { +// cout<<" "<id<<" completely behind "<id<overlaps(that)) +// { +// assert(!that.overlaps(*this)); +// cout<<" THIS does not overlap THAT"<c[0](2) + this->c[1](2) + this->c[2](2)) > +// 1./3.*(that.c[0](2) + that.c[1](2) + that.c[2](2)); +// }else +// { +// if(this->is_coplanar(that) || that.is_coplanar(*this)) +// { +// cout<<" coplanar"<c[0](2) + this->c[1](2) + this->c[2](2)) > +// 1./3.*(that.c[0](2) + that.c[1](2) + that.c[2](2)); +// }else if(this->is_behind_plane(that)) +// { +// cout<<" THIS behind plane of THAT"<is_in_front_of_plane(that)) +// { +// cout<<" THIS in front plane of THAT"<c[0](2) + this->c[1](2) + this->c[2](2)) > +// 1./3.*(that.c[0](2) + that.c[1](2) + that.c[2](2)); +// } +// } +// } +// if(ret) +// { +// // THIS < THAT so better not be THAT < THIS +// cout<id<<" < "<= THAT so could be THAT < THIS or THAT == THIS +// } +// return ret; +// } +//}; +//#include +// +//template < +// typename DerivedV, +// typename DerivedF, +// typename DerivedMV, +// typename DerivedP, +// typename DerivedFF, +// typename DerivedI> +//IGL_INLINE void igl::sort_triangles_robust( +// const Eigen::PlainObjectBase & V, +// const Eigen::PlainObjectBase & F, +// const Eigen::PlainObjectBase & MV, +// const Eigen::PlainObjectBase & P, +// Eigen::PlainObjectBase & FF, +// Eigen::PlainObjectBase & I) +//{ +// assert(false && +// "THIS WILL NEVER WORK because depth sorting is not a numerical sort where" +// "pairwise comparisons of triangles are transitive. Rather it is a" +// "topological sort on a dependency graph. Dependency encodes 'This triangle" +// "must be drawn before that one'"); +// using namespace std; +// using namespace Eigen; +// typedef Matrix Vec3; +// assert(V.cols() == 4); +// Matrix VMVP = +// V*(MV.transpose()*P.transpose().eval().block(0,0,4,3)); +// +// MatrixXd projV(V.rows(),3); +// for(int v = 0;v > vF(F.rows()); +// MatrixXd N(F.rows(),3); +// MatrixXd C(F.rows()*3,3); +// for(int f = 0;f(f,VMVP.row(F(f,0)),VMVP.row(F(f,1)),VMVP.row(F(f,2))); +// Triangle(f,projV.row(F(f,0)),projV.row(F(f,1)),projV.row(F(f,2))); +// N.row(f) = vF[f].n; +// for(int c = 0;c<3;c++) +// for(int d = 0;d<3;d++) +// C(f*3+c,d) = vF[f].c[c](d); +// } +// MatlabWorkspace mw; +// mw.save_index(F,"F"); +// mw.save(V,"V"); +// mw.save(MV,"MV"); +// mw.save(P,"P"); +// Vector4i VP; +// glGetIntegerv(GL_VIEWPORT, VP.data()); +// mw.save(projV,"projV"); +// mw.save(VP,"VP"); +// mw.save(VMVP,"VMVP"); +// mw.save(N,"N"); +// mw.save(C,"C"); +// mw.write("ao.mat"); +// sort(vF.begin(),vF.end()); +// +// // check +// for(int f = 0;f +//IGL_INLINE void igl::sort_triangles_robust( +// const Eigen::PlainObjectBase & V, +// const Eigen::PlainObjectBase & F, +// Eigen::PlainObjectBase & FF, +// Eigen::PlainObjectBase & I) +//{ +// using namespace Eigen; +// using namespace std; +// // Put model, projection, and viewport matrices into double arrays +// Matrix4d MV; +// Matrix4d P; +// glGetDoublev(GL_MODELVIEW_MATRIX, MV.data()); +// glGetDoublev(GL_PROJECTION_MATRIX, P.data()); +// if(V.cols() == 3) +// { +// Matrix hV; +// hV.resize(V.rows(),4); +// hV.block(0,0,V.rows(),V.cols()) = V; +// hV.col(3).setConstant(1); +// return sort_triangles_robust(hV,F,MV,P,FF,I); +// }else +// { +// return sort_triangles_robust(V,F,MV,P,FF,I); +// } +//} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::sort_triangles, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sort_triangles, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/sort_triangles.h b/src/igl/sort_triangles.h new file mode 100644 index 000000000..b334e6530 --- /dev/null +++ b/src/igl/sort_triangles.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SORT_TRIANGLES_H +#define IGL_SORT_TRIANGLES_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // Inputs: + // V #V by **4** list of homogeneous vertex positions + // F #F by 3 list of triangle indices + // MV 4 by 4 model view matrix + // P 4 by 4 projection matrix + // Outputs: + // FF #F by 3 list of sorted triangles indices + // I #F list of sorted indices + template < + typename DerivedV, + typename DerivedF, + typename DerivedMV, + typename DerivedP, + typename DerivedFF, + typename DerivedI> + IGL_INLINE void sort_triangles( + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & MV, + const Eigen::PlainObjectBase & P, + Eigen::PlainObjectBase & FF, + Eigen::PlainObjectBase & I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "sort_triangles.cpp" +#endif + +#endif diff --git a/src/igl/sort_vectors_ccw.cpp b/src/igl/sort_vectors_ccw.cpp new file mode 100644 index 000000000..ea613c51b --- /dev/null +++ b/src/igl/sort_vectors_ccw.cpp @@ -0,0 +1,103 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include +#include +#include + +template +IGL_INLINE void igl::sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order) +{ + int half_degree = P.cols()/3; + //local frame + Eigen::Matrix e1 = P.head(3).normalized(); + Eigen::Matrix e3 = N.normalized(); + Eigen::Matrix e2 = e3.cross(e1); + + Eigen::Matrix F; F< angles(half_degree,1); + for (int i=0; i Pl = F.colPivHouseholderQr().solve(P.segment(i*3,3).transpose()).transpose(); +// assert(fabs(Pl(2))/Pl.cwiseAbs().maxCoeff() <1e-5); + angles[i] = atan2(Pl(1),Pl(0)); + } + + igl::sort( angles, 1, true, angles, order); + //make sure that the first element is always at the top + while (order[0] != 0) + { + //do a circshift + int temp = order[0]; + for (int i =0; i< half_degree-1; ++i) + order[i] = order[i+1]; + order(half_degree-1) = temp; + } +} + +template +IGL_INLINE void igl::sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &sorted) + { + int half_degree = P.cols()/3; + igl::sort_vectors_ccw(P,N,order); + sorted.resize(1,half_degree*3); + for (int i=0; i +IGL_INLINE void igl::sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &inv_order) + { + int half_degree = P.cols()/3; + igl::sort_vectors_ccw(P,N,order); + inv_order.resize(half_degree,1); + for (int i=0; i +IGL_INLINE void igl::sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &sorted, + Eigen::PlainObjectBase &inv_order) +{ + int half_degree = P.cols()/3; + + igl::sort_vectors_ccw(P,N,order,inv_order); + + sorted.resize(1,half_degree*3); + for (int i=0; i, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +template void igl::sort_vectors_ccw, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/sort_vectors_ccw.h b/src/igl/sort_vectors_ccw.h new file mode 100644 index 000000000..73c2f6fba --- /dev/null +++ b/src/igl/sort_vectors_ccw.h @@ -0,0 +1,68 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Olga Diamanti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IGL_SORT_VECTORS_CCW +#define IGL_SORT_VECTORS_CCW +#include "igl_inline.h" + +#include + +namespace igl { + // Sorts a set of N coplanar vectors in a ccw order, and returns their order. + // Optionally it also returns a copy of the ordered vector set, or the indices, + // in the original unordered set, of the vectors in the ordered set (called here + // the "inverse" set of indices). + + // Inputs: + // P 1 by 3N row vector of the vectors to be sorted, stacked horizontally + // N #1 by 3 normal of the plane where the vectors lie + // Output: + // order N by 1 order of the vectors (indices of the unordered vectors into + // the ordered vector set) + // sorted 1 by 3N row vector of the ordered vectors, stacked horizontally + // inv_order N by 1 "inverse" order of the vectors (the indices of the ordered + // vectors into the unordered vector set) + // + template + IGL_INLINE void sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &sorted, + Eigen::PlainObjectBase &inv_order); + + template + IGL_INLINE void sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &sorted); + + template + IGL_INLINE void sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order, + Eigen::PlainObjectBase &inv_order); + + + template + IGL_INLINE void sort_vectors_ccw( + const Eigen::PlainObjectBase& P, + const Eigen::PlainObjectBase& N, + Eigen::PlainObjectBase &order); + +}; + + +#ifndef IGL_STATIC_LIBRARY +#include "sort_vectors_ccw.cpp" +#endif + + +#endif /* defined(IGL_FIELD_LOCAL_GLOBAL_CONVERSIONS) */ diff --git a/src/igl/sortrows.cpp b/src/igl/sortrows.cpp new file mode 100644 index 000000000..a5e084fd8 --- /dev/null +++ b/src/igl/sortrows.cpp @@ -0,0 +1,142 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sortrows.h" +#include "get_seconds.h" + +#include "SortableRow.h" +#include "sort.h" +#include "colon.h" +#include "IndexComparison.h" + +#include + +// Obsolete slower version converst to vector +//template +//IGL_INLINE void igl::sortrows( +// const Eigen::DenseBase& X, +// const bool ascending, +// Eigen::PlainObjectBase& Y, +// Eigen::PlainObjectBase& IX) +//{ +// using namespace std; +// using namespace Eigen; +// typedef Eigen::Matrix RowVector; +// vector > rows; +// rows.resize(X.rows()); +// // Loop over rows +// for(int i = 0;i(ri); +// } +// vector > sorted; +// std::vector index_map; +// // Perform sort on rows +// igl::sort(rows,ascending,sorted,index_map); +// // Resize output +// Y.resizeLike(X); +// IX.resize(X.rows(),1); +// // Convert to eigen +// for(int i = 0;i +IGL_INLINE void igl::sortrows( + const Eigen::DenseBase& X, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& IX) +{ + // This is already 2x faster than matlab's builtin `sortrows`. I have tried + // implementing a "multiple-pass" sort on each column, but see no performance + // improvement. + using namespace std; + using namespace Eigen; + // Resize output + const size_t num_rows = X.rows(); + const size_t num_cols = X.cols(); + Y.resize(num_rows,num_cols); + IX.resize(num_rows,1); + for(int i = 0;i X.coeff(j, c)) return true; + else if (X.coeff(j,c) > X.coeff(i,c)) return false; + } + return false; + }; + std::sort( + IX.data(), + IX.data()+IX.size(), + index_greater_than + ); + } + for (size_t j=0; j, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::sortrows, Eigen::Matrix >(Eigen::DenseBase > const&, bool, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::sortrows,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,bool,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::sortrows,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,bool,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::sortrows,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,bool,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::sortrows,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,bool,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/sortrows.h b/src/igl/sortrows.h new file mode 100644 index 000000000..c0b15f29e --- /dev/null +++ b/src/igl/sortrows.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SORTROWS_H +#define IGL_SORTROWS_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + // Act like matlab's [Y,I] = sortrows(X) + // + // Templates: + // DerivedX derived scalar type, e.g. MatrixXi or MatrixXd + // DerivedI derived integer type, e.g. MatrixXi + // Inputs: + // X m by n matrix whose entries are to be sorted + // ascending sort ascending (true, matlab default) or descending (false) + // Outputs: + // Y m by n matrix whose entries are sorted (**should not** be same + // reference as X) + // I m list of indices so that + // Y = X(I,:); + template + IGL_INLINE void sortrows( + const Eigen::DenseBase& X, + const bool ascending, + Eigen::PlainObjectBase& Y, + Eigen::PlainObjectBase& I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "sortrows.cpp" +#endif + +#endif + diff --git a/src/igl/sparse.cpp b/src/igl/sparse.cpp new file mode 100644 index 000000000..ed9cd4a49 --- /dev/null +++ b/src/igl/sparse.cpp @@ -0,0 +1,123 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sparse.h" + +#include +#include + +template +IGL_INLINE void igl::sparse( + const IndexVector & I, + const IndexVector & J, + const ValueVector & V, + Eigen::SparseMatrix& X) +{ + size_t m = (size_t)I.maxCoeff()+1; + size_t n = (size_t)J.maxCoeff()+1; + return igl::sparse(I,J,V,m,n,X); +} + +#include "verbose.h" +template < + class IndexVectorI, + class IndexVectorJ, + class ValueVector, + typename T> +IGL_INLINE void igl::sparse( + const IndexVectorI & I, + const IndexVectorJ & J, + const ValueVector & V, + const size_t m, + const size_t n, + Eigen::SparseMatrix& X) +{ + using namespace std; + using namespace Eigen; + assert((int)I.maxCoeff() < (int)m); + assert((int)I.minCoeff() >= 0); + assert((int)J.maxCoeff() < (int)n); + assert((int)J.minCoeff() >= 0); + assert(I.size() == J.size()); + assert(J.size() == V.size()); + // Really we just need .size() to be the same, but this is safer + assert(I.rows() == J.rows()); + assert(J.rows() == V.rows()); + assert(I.cols() == J.cols()); + assert(J.cols() == V.cols()); + //// number of values + //int nv = V.size(); + + //Eigen::DynamicSparseMatrix dyn_X(m,n); + //// over estimate the number of entries + //dyn_X.reserve(I.size()); + //for(int i = 0;i < nv;i++) + //{ + // dyn_X.coeffRef((int)I(i),(int)J(i)) += (T)V(i); + //} + //X = Eigen::SparseMatrix(dyn_X); + vector > IJV; + IJV.reserve(I.size()); + for(int x = 0;x(I(x),J(x),V(x))); + } + X.resize(m,n); + X.setFromTriplets(IJV.begin(),IJV.end()); +} + +template +IGL_INLINE void igl::sparse( + const Eigen::PlainObjectBase& D, + Eigen::SparseMatrix& X) +{ + assert(false && "Obsolete. Just call D.sparseView() directly"); + using namespace std; + using namespace Eigen; + vector > DIJV; + const int m = D.rows(); + const int n = D.cols(); + for(int i = 0;i(i,j,D(i,j))); + } + } + } + X.resize(m,n); + X.setFromTriplets(DIJV.begin(),DIJV.end()); +} + +template +IGL_INLINE Eigen::SparseMatrix igl::sparse( + const Eigen::PlainObjectBase& D) +{ + assert(false && "Obsolete. Just call D.sparseView() directly"); + Eigen::SparseMatrix X; + igl::sparse(D,X); + return X; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +#ifndef WIN32 +//template void igl::sparse >, Eigen::Matrix, Eigen::CwiseNullaryOp, Eigen::Array >, bool>(Eigen::PlainObjectBase > const&, Eigen::Matrix const&, Eigen::CwiseNullaryOp, Eigen::Array > const&, unsigned long, unsigned long, Eigen::SparseMatrix&); +//template void igl::sparse >, Eigen::MatrixBase >, Eigen::CwiseNullaryOp, Eigen::Array >, bool>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::CwiseNullaryOp, Eigen::Array > const&, unsigned long, unsigned long, Eigen::SparseMatrix&); +#if EIGEN_VERSION_AT_LEAST(3,3,0) +#else +//template void igl::sparse, Eigen::Matrix >, Eigen::Matrix, Eigen::CwiseNullaryOp, Eigen::Array >, bool>(Eigen::CwiseNullaryOp, Eigen::Matrix > const&, Eigen::Matrix const&, Eigen::CwiseNullaryOp, Eigen::Array > const&, unsigned long, unsigned long, Eigen::SparseMatrix&); +#endif +#endif + +template void igl::sparse, Eigen::Matrix, Eigen::Matrix, std::complex >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, size_t, size_t, Eigen::SparseMatrix, 0, int>&); +template void igl::sparse, Eigen::Matrix, double>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::SparseMatrix&); +template void igl::sparse, Eigen::Matrix, Eigen::Matrix, double>(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, size_t, size_t, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/sparse.h b/src/igl/sparse.h new file mode 100644 index 000000000..a947978ab --- /dev/null +++ b/src/igl/sparse.h @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SPARSE_H +#define IGL_SPARSE_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Build a sparse matrix from list of indices and values (I,J,V), functions + // like the sparse function in matlab + // + // Templates: + // IndexVector list of indices, value should be non-negative and should + // expect to be cast to an index. Must implement operator(i) to retrieve + // ith element + // ValueVector list of values, value should be expect to be cast to type + // T. Must implement operator(i) to retrieve ith element + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // I nnz vector of row indices of non zeros entries in X + // J nnz vector of column indices of non zeros entries in X + // V nnz vector of non-zeros entries in X + // Optional: + // m number of rows + // n number of cols + // Outputs: + // X m by n matrix of type T whose entries are to be found + // + template + IGL_INLINE void sparse( + const IndexVector & I, + const IndexVector & J, + const ValueVector & V, + Eigen::SparseMatrix& X); + template < + class IndexVectorI, + class IndexVectorJ, + class ValueVector, + typename T> + IGL_INLINE void sparse( + const IndexVectorI & I, + const IndexVectorJ & J, + const ValueVector & V, + const size_t m, + const size_t n, + Eigen::SparseMatrix& X); + // THIS MAY BE SUPERSEDED BY EIGEN'S .sparseView Indeed it is. + // Convert a full, dense matrix to a sparse one + // + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // D m by n full, dense matrix + // Output: + // X m by n sparse matrix + template + IGL_INLINE void sparse( + const Eigen::PlainObjectBase& D, + Eigen::SparseMatrix& X); + // Wrapper with return + template + IGL_INLINE Eigen::SparseMatrix sparse( + const Eigen::PlainObjectBase& D); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "sparse.cpp" +#endif + +#endif diff --git a/src/igl/sparse_cached.cpp b/src/igl/sparse_cached.cpp new file mode 100644 index 000000000..ddd65aadf --- /dev/null +++ b/src/igl/sparse_cached.cpp @@ -0,0 +1,127 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sparse_cached.h" + +#include +#include +#include +#include +#include +#include + +template +IGL_INLINE void igl::sparse_cached_precompute( + const Eigen::MatrixBase & I, + const Eigen::MatrixBase & J, + Eigen::VectorXi& data, + Eigen::SparseMatrix& X) +{ + // Generates the triplets + std::vector > t(I.size()); + for (unsigned i = 0; i(I[i],J[i],1); + + // Call the triplets version + sparse_cached_precompute(t,X,data); +} + +template +IGL_INLINE void igl::sparse_cached_precompute( + const std::vector >& triplets, + Eigen::VectorXi& data, + Eigen::SparseMatrix& X) +{ + // Construct an empty sparse matrix + X.setFromTriplets(triplets.begin(),triplets.end()); + X.makeCompressed(); + + std::vector > T(triplets.size()); + for (unsigned i=0; i= 0); + assert(row < X.rows()); + assert(row >= 0); + assert(value_index >= 0); + assert(value_index < X.nonZeros()); + + std::pair p_m = std::make_pair(row,col); + + while (t +IGL_INLINE void igl::sparse_cached( + const std::vector >& triplets, + const Eigen::VectorXi& data, + Eigen::SparseMatrix& X) +{ + assert(triplets.size() == data.size()); + + // Clear it first + for (unsigned i = 0; i +IGL_INLINE void igl::sparse_cached( + const Eigen::MatrixBase& V, + const Eigen::VectorXi& data, + Eigen::SparseMatrix& X) +{ + assert(V.size() == data.size()); + + // Clear it first + for (unsigned i = 0; i(std::vector::StorageIndex>, std::allocator::StorageIndex> > > const&, Eigen::Matrix const&, Eigen::SparseMatrix&); + template void igl::sparse_cached_precompute(std::vector::StorageIndex>, std::allocator::StorageIndex> > > const&, Eigen::Matrix&, Eigen::SparseMatrix&); +#else + template void igl::sparse_cached(std::vector::Index>, std::allocator::Index> > > const&, Eigen::Matrix const&, Eigen::SparseMatrix&); + template void igl::sparse_cached_precompute(std::vector::Index>, std::allocator::Index> > > const&, Eigen::Matrix&, Eigen::SparseMatrix&); +#endif +#endif diff --git a/src/igl/sparse_cached.h b/src/igl/sparse_cached.h new file mode 100644 index 000000000..40d288962 --- /dev/null +++ b/src/igl/sparse_cached.h @@ -0,0 +1,85 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SPARSE_CACHED_H +#define IGL_SPARSE_CACHED_H +#include "igl_inline.h" +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include +#include +namespace igl +{ + // Build a sparse matrix from list of indices and values (I,J,V), similarly to + // the sparse function in matlab. Divides the construction in two phases, one + // for fixing the sparsity pattern, and one to populate it with values. Compared to + // igl::sparse, this version is slower for the first time (since it requires a + // precomputation), but faster to the subsequent evaluations. + // + // Templates: + // IndexVector list of indices, value should be non-negative and should + // expect to be cast to an index. Must implement operator(i) to retrieve + // ith element + // ValueVector list of values, value should be expect to be cast to type + // T. Must implement operator(i) to retrieve ith element + // T should be a eigen sparse matrix primitive type like int or double + // Input: + // I nnz vector of row indices of non zeros entries in X + // J nnz vector of column indices of non zeros entries in X + // V nnz vector of non-zeros entries in X + // Optional: + // m number of rows + // n number of cols + // Outputs: + // X m by n matrix of type T whose entries are to be found + // + // Example: + // Eigen::SparseMatrix A; + // std::vector > IJV; + // buildA(IJV); + // if (A.rows() == 0) + // { + // A = Eigen::SparseMatrix(rows,cols); + // igl::sparse_cached_precompute(IJV,A,A_data); + // } + // else + // igl::sparse_cached(IJV,s.A,s.A_data); + + template + IGL_INLINE void sparse_cached_precompute( + const Eigen::MatrixBase & I, + const Eigen::MatrixBase & J, + Eigen::VectorXi& data, + Eigen::SparseMatrix& X + ); + + template + IGL_INLINE void sparse_cached_precompute( + const std::vector >& triplets, + Eigen::VectorXi& data, + Eigen::SparseMatrix& X + ); + + template + IGL_INLINE void sparse_cached( + const std::vector >& triplets, + const Eigen::VectorXi& data, + Eigen::SparseMatrix& X); + + template + IGL_INLINE void sparse_cached( + const Eigen::MatrixBase& V, + const Eigen::VectorXi& data, + Eigen::SparseMatrix& X + ); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "sparse_cached.cpp" +#endif + +#endif diff --git a/src/igl/speye.cpp b/src/igl/speye.cpp new file mode 100644 index 000000000..54af00c71 --- /dev/null +++ b/src/igl/speye.cpp @@ -0,0 +1,35 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "speye.h" + +template +IGL_INLINE void igl::speye(const int m, const int n, Eigen::SparseMatrix & I) +{ + // size of diagonal + int d = (m(m,n); + I.reserve(d); + for(int i = 0;i +IGL_INLINE void igl::speye(const int n, Eigen::SparseMatrix & I) +{ + return igl::speye(n,n,I); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::speye(int, Eigen::SparseMatrix&); +template void igl::speye >(int, int, Eigen::SparseMatrix, 0, int>&); +#endif diff --git a/src/igl/speye.h b/src/igl/speye.h new file mode 100644 index 000000000..4c2425571 --- /dev/null +++ b/src/igl/speye.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SPEYE_H +#define IGL_SPEYE_H +#include "igl_inline.h" + +#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET +#include + +namespace igl +{ + // Builds an m by n sparse identity matrix like matlab's speye function + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // m number of rows + // n number of cols + // Outputs: + // I m by n sparse matrix with 1's on the main diagonal + template + IGL_INLINE void speye(const int n,const int m, Eigen::SparseMatrix & I); + // Builds an n by n sparse identity matrix like matlab's speye function + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // n number of rows and cols + // Outputs: + // I n by n sparse matrix with 1's on the main diagonal + template + IGL_INLINE void speye(const int n, Eigen::SparseMatrix & I); +} + +#ifndef IGL_STATIC_LIBRARY +# include "speye.cpp" +#endif + +#endif diff --git a/src/igl/squared_edge_lengths.cpp b/src/igl/squared_edge_lengths.cpp new file mode 100644 index 000000000..d15e6c13e --- /dev/null +++ b/src/igl/squared_edge_lengths.cpp @@ -0,0 +1,106 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "squared_edge_lengths.h" +#include "parallel_for.h" +#include + +template +IGL_INLINE void igl::squared_edge_lengths( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& L) +{ + using namespace std; + const int m = F.rows(); + switch(F.cols()) + { + case 2: + { + L.resize(F.rows(),1); + for(int i = 0;i, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::squared_edge_lengths, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/squared_edge_lengths.h b/src/igl/squared_edge_lengths.h new file mode 100644 index 000000000..2f374d6c3 --- /dev/null +++ b/src/igl/squared_edge_lengths.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SQUARED_EDGE_LENGTHS_H +#define IGL_SQUARED_EDGE_LENGTHS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Constructs a list of squared lengths of edges opposite each index in a face + // (triangle/tet) list + // + // Templates: + // DerivedV derived from vertex positions matrix type: i.e. MatrixXd + // DerivedF derived from face indices matrix type: i.e. MatrixXi + // DerivedL derived from edge lengths matrix type: i.e. MatrixXd + // Inputs: + // V eigen matrix #V by 3 + // F #F by 2 list of mesh edges + // or + // F #F by 3 list of mesh faces (must be triangles) + // or + // T #T by 4 list of mesh elements (must be tets) + // Outputs: + // L #F by {1|3|6} list of edge lengths squared + // for edges, column of lengths + // for triangles, columns correspond to edges [1,2],[2,0],[0,1] + // for tets, columns correspond to edges + // [3 0],[3 1],[3 2],[1 2],[2 0],[0 1] + // + template + IGL_INLINE void squared_edge_lengths( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& L); +} + +#ifndef IGL_STATIC_LIBRARY +# include "squared_edge_lengths.cpp" +#endif + +#endif + diff --git a/src/igl/stdin_to_temp.cpp b/src/igl/stdin_to_temp.cpp new file mode 100644 index 000000000..a7a7fbd70 --- /dev/null +++ b/src/igl/stdin_to_temp.cpp @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "stdin_to_temp.h" + +#include + +IGL_INLINE bool igl::stdin_to_temp(FILE ** temp_file) +{ + // get a temporary file + *temp_file = tmpfile(); + if(*temp_file == NULL) + { + fprintf(stderr,"IOError: temp file could not be created.\n"); + return false; + } + char c; + // c++'s cin handles the stdind input in a reasonable way + while (std::cin.good()) + { + c = std::cin.get(); + if(std::cin.good()) + { + if(1 != fwrite(&c,sizeof(char),1,*temp_file)) + { + fprintf(stderr,"IOError: error writing to tempfile.\n"); + return false; + } + } + } + // rewind file getting it ready to read from + rewind(*temp_file); + return true; +} diff --git a/src/igl/stdin_to_temp.h b/src/igl/stdin_to_temp.h new file mode 100644 index 000000000..ae35219ea --- /dev/null +++ b/src/igl/stdin_to_temp.h @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_STDIN_TO_TEMP_H +#define IGL_STDIN_TO_TEMP_H +#include "igl_inline.h" +#include +namespace igl +{ + // Write stdin/piped input to a temporary file which can than be preprocessed as it + // is (a normal file). This is often useful if you want to process stdin/piped + // with library functions that expect to be able to fseek(), rewind() etc.. + // + // If your application is not using fseek(), rewind(), etc. but just reading + // from stdin then this will likely cause a bottle neck as it defeats the whole + // purpose of piping. + // + // Outputs: + // temp_file pointer to temp file pointer, rewound to beginning of file so + // its ready to be read + // Return true only if no errors were found + // + // Note: Caller is responsible for closing the file (tmpfile() automatically + // unlinks the file so there is no need to remove/delete/unlink the file) + IGL_INLINE bool stdin_to_temp(FILE ** temp_file); +} + +#ifndef IGL_STATIC_LIBRARY +# include "stdin_to_temp.cpp" +#endif + +#endif diff --git a/src/igl/straighten_seams.cpp b/src/igl/straighten_seams.cpp new file mode 100644 index 000000000..0aef2b316 --- /dev/null +++ b/src/igl/straighten_seams.cpp @@ -0,0 +1,370 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "straighten_seams.h" +#include "LinSpaced.h" +#include "on_boundary.h" +#include "sparse.h" +#include "max.h" +#include "count.h" +#include "any.h" +#include "slice_mask.h" +#include "slice_into.h" +#include "unique_simplices.h" +#include "adjacency_matrix.h" +#include "setxor.h" +#include "edges_to_path.h" +#include "ramer_douglas_peucker.h" +#include "components.h" +#include "list_to_matrix.h" +#include "ears.h" +#include "slice.h" +#include "sum.h" +#include "find.h" +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedVT, + typename DerivedFT, + typename Scalar, + typename DerivedUE, + typename DerivedUT, + typename DerivedOT> +IGL_INLINE void igl::straighten_seams( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & VT, + const Eigen::MatrixBase & FT, + const Scalar tol, + Eigen::PlainObjectBase & UE, + Eigen::PlainObjectBase & UT, + Eigen::PlainObjectBase & OT) +{ + using namespace Eigen; + // number of faces + assert(FT.rows() == F.rows() && "#FT must == #F"); + assert(F.cols() == 3 && "F should contain triangles"); + assert(FT.cols() == 3 && "FT should contain triangles"); + const int m = F.rows(); + // Boundary edges of the texture map and 3d meshes + Array _; + Array BT,BF; + on_boundary(FT,_,BT); + on_boundary(F,_,BF); + assert((!((BF && (BT!=true)).any())) && + "Not dealing with boundaries of mesh that get 'stitched' in texture mesh"); + typedef Matrix MatrixX2I; + const MatrixX2I ET = (MatrixX2I(FT.rows()*3,2) + <vBT = Map >(BT.data(),BT.size(),1); + ArrayvBF = Map >(BF.data(),BF.size(),1); + MatrixX2I OF; + slice_mask(ET,vBT,1,OT); + slice_mask(EF,vBT,1,OF); + VectorXi OFMAP; + slice_mask(EFMAP,vBT,1,OFMAP); + // Two boundary edges on the texture-mapping are "equivalent" to each other on + // the 3D-mesh if their 3D-mesh vertex indices match + SparseMatrix OEQ; + { + SparseMatrix OEQR; + sparse( + igl::LinSpaced(OT.rows(),0,OT.rows()-1), + OFMAP, + Array::Ones(OT.rows(),1), + OT.rows(), + m*3, + OEQR); + OEQ = OEQR * OEQR.transpose(); + // Remove diagonal + OEQ.prune([](const int r, const int c, const bool)->bool{return r!=c;}); + } + // For each edge in OT, for each endpoint, how many _other_ texture-vertices + // are images of all the 3d-mesh vertices in F who map from "corners" in F/FT + // mapping to this endpoint. + // + // Adjacency matrix between 3d-vertices and texture-vertices + SparseMatrix V2VT; + sparse( + F, + FT, + Array::Ones(F.rows(),F.cols()), + V.rows(), + VT.rows(), + V2VT); + // For each 3d-vertex count how many different texture-coordinates its getting + // from different incident corners + VectorXi DV; + count(V2VT,2,DV); + VectorXi M,I; + max(V2VT,1,M,I); + assert( (M.array() == 1).all() ); + VectorXi DT; + // Map counts onto texture-vertices + slice(DV,I,1,DT); + // Boundary in 3D && UV + Array BTF; + slice_mask(vBF, vBT, 1, BTF); + // Texture-vertex is "sharp" if incident on "half-"edge that is not a + // boundary in the 3D mesh but is a boundary in the texture-mesh AND is not + // "cut cleanly" (the vertex is mapped to exactly 2 locations) + Array SV = Array::Zero(VT.rows(),1); + //std::cout<<"#SV: "< CL = DT.array()==2; + SparseMatrix VTOT; + { + Eigen::MatrixXi I = + igl::LinSpaced(OT.rows(),0,OT.rows()-1).replicate(1,2); + sparse( + OT, + I, + Array::Ones(OT.rows(),OT.cols()), + VT.rows(), + OT.rows(), + VTOT); + Array cuts; + count( (VTOT*OEQ).eval(), 2, cuts); + CL = (CL && (cuts.array() == 2)).eval(); + } + //std::cout<<"#CL: "< earT = Array::Zero(VT.rows(),1); + for(int e = 0;e A; + adjacency_matrix(FT,A); + earT = (earT || (A*earT.matrix()).array()).eval(); + //std::cout<<"#earT: "< V2VTSV,V2VTC; + slice_mask(V2VT,SV,2,V2VTSV); + Array Cb; + any(V2VTSV,2,Cb); + slice_mask(V2VT,Cb,1,V2VTC); + any(V2VTC,1,SV); + } + //std::cout<<"#SV: "< OTVT = VTOT.transpose(); + int nc; + ArrayXi C; + { + // Doesn't Compile on older Eigen: + //SparseMatrix A = OTVT * (!SV).matrix().asDiagonal() * VTOT; + SparseMatrix A = OTVT * (SV!=true).matrix().asDiagonal() * VTOT; + components(A,C); + nc = C.maxCoeff()+1; + } + //std::cout<<"nc: "< > vUE; + // loop over each component + std::vector done(nc,false); + for(int c = 0;c OEQIc; + slice(OEQ,Ic,1,OEQIc); + Eigen::VectorXi N; + sum(OEQIc,2,N); + const int ncopies = N(0)+1; + assert((N.array() == ncopies-1).all()); + assert((ncopies == 1 || ncopies == 2) && + "Not dealing with non-manifold meshes"); + Eigen::VectorXi vpath,epath,eend; + typedef Eigen::Matrix MatrixX2S; + switch(ncopies) + { + case 1: + { + MatrixX2I OTIc; + slice(OT,Ic,1,OTIc); + edges_to_path(OTIc,vpath,epath,eend); + Array SVvpath; + slice(SV,vpath,1,SVvpath); + assert( + (vpath(0) != vpath(vpath.size()-1) || !SVvpath.any()) && + "Not dealing with 1-loops touching 'sharp' corners"); + // simple open boundary + MatrixX2S PI; + slice(VT,vpath,1,PI); + const Scalar bbd = + (PI.colwise().maxCoeff() - PI.colwise().minCoeff()).norm(); + // Do not collapse boundaries to fewer than 3 vertices + const bool allow_boundary_collapse = false; + assert(PI.size() >= 2); + const bool is_closed = PI(0) == PI(PI.size()-1); + assert(!is_closed || vpath.size() >= 4); + Scalar eff_tol = std::min(tol,2.); + VectorXi UIc; + while(true) + { + MatrixX2S UPI,UTvpath; + ramer_douglas_peucker(PI,eff_tol*bbd,UPI,UIc,UTvpath); + slice_into(UTvpath,vpath,1,UT); + if(!is_closed || allow_boundary_collapse) + { + break; + } + if(UPI.rows()>=4) + { + break; + } + eff_tol = eff_tol*0.5; + } + for(int i = 0;i IV; + SparseMatrix OEQIcT = OEQIc.transpose().eval(); + find(OEQIcT,Icc,II,IV); + assert(II.size() == Ic.size() && + (II.array() == + igl::LinSpaced(Ic.size(),0,Ic.size()-1).array()).all()); + assert(Icc.size() == Ic.size()); + const int cc = C(Icc(0)); + Eigen::VectorXi CIcc; + slice(C,Icc,1,CIcc); + assert((CIcc.array() == cc).all()); + assert(!done[cc]); + done[cc] = true; + } + Array flipped; + { + MatrixX2I OFIc,OFIcc; + slice(OF,Ic,1,OFIc); + slice(OF,Icc,1,OFIcc); + Eigen::VectorXi XOR,IA,IB; + setxor(OFIc,OFIcc,XOR,IA,IB); + assert(XOR.size() == 0); + flipped = OFIc.array().col(0) != OFIcc.array().col(0); + } + if(Ic.size() == 1) + { + // No change to UT + vUE.push_back({OT(Ic(0),0),OT(Ic(0),1)}); + assert(Icc.size() == 1); + vUE.push_back({OT(Icc(0),flipped(0)?1:0),OT(Icc(0),flipped(0)?0:1)}); + }else + { + MatrixX2I OTIc; + slice(OT,Ic,1,OTIc); + edges_to_path(OTIc,vpath,epath,eend); + // Flip endpoints if needed + for(int e = 0;e PI(vpath.size(),VT.cols()*2); + for(int p = 0;p UPI,SI; + VectorXi UIc; + ramer_douglas_peucker(PI,tol*bbd,UPI,UIc,SI); + slice_into(SI.leftCols (VT.cols()), vpath,1,UT); + slice_into(SI.rightCols(VT.cols()),vpathc,1,UT); + for(int i = 0;i, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, double, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/straighten_seams.h b/src/igl/straighten_seams.h new file mode 100644 index 000000000..2eb0e9d46 --- /dev/null +++ b/src/igl/straighten_seams.h @@ -0,0 +1,57 @@ +#ifndef IGL_STRAIGHTEN_SEAMS_H +#define IGL_STRAIGHTEN_SEAMS_H + +#include "igl_inline.h" +#include + +namespace igl +{ + // STRAIGHTEN_SEAMS Given a obj-style mesh with (V,F) defining the geometric + // surface of the mesh and (VT,FT) defining the + // parameterization/texture-mapping of the mesh in the uv-domain, find all + // seams and boundaries in the texture-mapping and "straighten" them, + // remapping vertices along the boundary and in the interior. This will be + // careful to consistently straighten multiple seams in the texture-mesh + // corresponding to the same edge chains in the surface-mesh. + // + // [UT] = straighten_seams(V,F,VT,FT) + // + // Inputs: + // V #V by 3 list of vertices + // F #F by 3 list of triangle indices + // VT #VT by 2 list of texture coordinates + // FT #F by 3 list of triangle texture coordinates + // Optional: + // 'Tol' followed by Ramer-Douglas-Peucker tolerance as a fraction of the + // curves bounding box diagonal (see dpsimplify) + // Outputs: + // UE #UE by 2 list of indices into VT of coarse output polygon edges + // UT #VT by 3 list of new texture coordinates + // OT #OT by 2 list of indices into VT of boundary edges + // + // See also: simplify_curve, dpsimplify + template < + typename DerivedV, + typename DerivedF, + typename DerivedVT, + typename DerivedFT, + typename Scalar, + typename DerivedUE, + typename DerivedUT, + typename DerivedOT> + IGL_INLINE void straighten_seams( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & VT, + const Eigen::MatrixBase & FT, + const Scalar tol, + Eigen::PlainObjectBase & UE, + Eigen::PlainObjectBase & UT, + Eigen::PlainObjectBase & OT); +} + +#ifndef IGL_STATIC_LIBRARY +# include "straighten_seams.cpp" +#endif + +#endif diff --git a/src/igl/sum.cpp b/src/igl/sum.cpp new file mode 100644 index 000000000..b994ce405 --- /dev/null +++ b/src/igl/sum.cpp @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "sum.h" +#include "redux.h" + +template +IGL_INLINE void igl::sum( + const Eigen::SparseMatrix& X, + const int dim, + Eigen::SparseVector& S) +{ + assert((dim == 1 || dim == 2) && "dim must be 2 or 1"); + // Get size of input + int m = X.rows(); + int n = X.cols(); + // resize output + if(dim==1) + { + S = Eigen::SparseVector(n); + }else + { + S = Eigen::SparseVector(m); + } + + // Iterate over outside + for(int k=0; k::InnerIterator it (X,k); it; ++it) + { + if(dim == 1) + { + S.coeffRef(it.col()) += it.value(); + }else + { + S.coeffRef(it.row()) += it.value(); + } + } + } +} + +template +IGL_INLINE void igl::sum( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B) +{ + typedef typename DerivedB::Scalar Scalar; + igl::redux(A,dim,[](Scalar a, Scalar b){ return a+b;},B); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::sum >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::sum >(Eigen::SparseMatrix const&, int, Eigen::PlainObjectBase >&); +template void igl::sum(Eigen::SparseMatrix const&, int, Eigen::SparseVector&); +#endif diff --git a/src/igl/sum.h b/src/igl/sum.h new file mode 100644 index 000000000..6caeac344 --- /dev/null +++ b/src/igl/sum.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SUM_H +#define IGL_SUM_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Note: If your looking for dense matrix matlab like sum for eigen matrics + // just use: + // M.colwise().sum() or M.rowwise().sum() + // + + // Sum the columns or rows of a sparse matrix + // Templates: + // T should be a eigen sparse matrix primitive type like int or double + // Inputs: + // X m by n sparse matrix + // dim dimension along which to sum (1 or 2) + // Output: + // S n-long sparse vector (if dim == 1) + // or + // S m-long sparse vector (if dim == 2) + template + IGL_INLINE void sum( + const Eigen::SparseMatrix& X, + const int dim, + Eigen::SparseVector& S); + // Sum is "conducted" in the type of DerivedB::Scalar + template + IGL_INLINE void sum( + const Eigen::SparseMatrix & A, + const int dim, + Eigen::PlainObjectBase& B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "sum.cpp" +#endif + +#endif diff --git a/src/igl/svd3x3.cpp b/src/igl/svd3x3.cpp new file mode 100644 index 000000000..dd65e0cf3 --- /dev/null +++ b/src/igl/svd3x3.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "svd3x3.h" + +#include +#include + +#define USE_SCALAR_IMPLEMENTATION +#undef USE_SSE_IMPLEMENTATION +#undef USE_AVX_IMPLEMENTATION +#define COMPUTE_U_AS_MATRIX +#define COMPUTE_V_AS_MATRIX +#include "Singular_Value_Decomposition_Preamble.hpp" + +#pragma runtime_checks( "u", off ) // disable runtime asserts on xor eax,eax type of stuff (doesn't always work, disable explicitly in compiler settings) +template +IGL_INLINE void igl::svd3x3(const Eigen::Matrix& A, Eigen::Matrix &U, Eigen::Matrix &S, Eigen::Matrix&V) +{ + // this code only supports the scalar version (otherwise we'd need to pass arrays of matrices) + +#include "Singular_Value_Decomposition_Kernel_Declarations.hpp" + + ENABLE_SCALAR_IMPLEMENTATION(Sa11.f=A(0,0);) ENABLE_SSE_IMPLEMENTATION(Va11=_mm_loadu_ps(a11);) ENABLE_AVX_IMPLEMENTATION(Va11=_mm256_loadu_ps(a11);) + ENABLE_SCALAR_IMPLEMENTATION(Sa21.f=A(1,0);) ENABLE_SSE_IMPLEMENTATION(Va21=_mm_loadu_ps(a21);) ENABLE_AVX_IMPLEMENTATION(Va21=_mm256_loadu_ps(a21);) + ENABLE_SCALAR_IMPLEMENTATION(Sa31.f=A(2,0);) ENABLE_SSE_IMPLEMENTATION(Va31=_mm_loadu_ps(a31);) ENABLE_AVX_IMPLEMENTATION(Va31=_mm256_loadu_ps(a31);) + ENABLE_SCALAR_IMPLEMENTATION(Sa12.f=A(0,1);) ENABLE_SSE_IMPLEMENTATION(Va12=_mm_loadu_ps(a12);) ENABLE_AVX_IMPLEMENTATION(Va12=_mm256_loadu_ps(a12);) + ENABLE_SCALAR_IMPLEMENTATION(Sa22.f=A(1,1);) ENABLE_SSE_IMPLEMENTATION(Va22=_mm_loadu_ps(a22);) ENABLE_AVX_IMPLEMENTATION(Va22=_mm256_loadu_ps(a22);) + ENABLE_SCALAR_IMPLEMENTATION(Sa32.f=A(2,1);) ENABLE_SSE_IMPLEMENTATION(Va32=_mm_loadu_ps(a32);) ENABLE_AVX_IMPLEMENTATION(Va32=_mm256_loadu_ps(a32);) + ENABLE_SCALAR_IMPLEMENTATION(Sa13.f=A(0,2);) ENABLE_SSE_IMPLEMENTATION(Va13=_mm_loadu_ps(a13);) ENABLE_AVX_IMPLEMENTATION(Va13=_mm256_loadu_ps(a13);) + ENABLE_SCALAR_IMPLEMENTATION(Sa23.f=A(1,2);) ENABLE_SSE_IMPLEMENTATION(Va23=_mm_loadu_ps(a23);) ENABLE_AVX_IMPLEMENTATION(Va23=_mm256_loadu_ps(a23);) + ENABLE_SCALAR_IMPLEMENTATION(Sa33.f=A(2,2);) ENABLE_SSE_IMPLEMENTATION(Va33=_mm_loadu_ps(a33);) ENABLE_AVX_IMPLEMENTATION(Va33=_mm256_loadu_ps(a33);) + +#include "Singular_Value_Decomposition_Main_Kernel_Body.hpp" + + ENABLE_SCALAR_IMPLEMENTATION(U(0,0)=Su11.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u11,Vu11);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u11,Vu11);) + ENABLE_SCALAR_IMPLEMENTATION(U(1,0)=Su21.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u21,Vu21);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u21,Vu21);) + ENABLE_SCALAR_IMPLEMENTATION(U(2,0)=Su31.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u31,Vu31);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u31,Vu31);) + ENABLE_SCALAR_IMPLEMENTATION(U(0,1)=Su12.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u12,Vu12);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u12,Vu12);) + ENABLE_SCALAR_IMPLEMENTATION(U(1,1)=Su22.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u22,Vu22);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u22,Vu22);) + ENABLE_SCALAR_IMPLEMENTATION(U(2,1)=Su32.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u32,Vu32);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u32,Vu32);) + ENABLE_SCALAR_IMPLEMENTATION(U(0,2)=Su13.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u13,Vu13);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u13,Vu13);) + ENABLE_SCALAR_IMPLEMENTATION(U(1,2)=Su23.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u23,Vu23);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u23,Vu23);) + ENABLE_SCALAR_IMPLEMENTATION(U(2,2)=Su33.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(u33,Vu33);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(u33,Vu33);) + + ENABLE_SCALAR_IMPLEMENTATION(V(0,0)=Sv11.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v11,Vv11);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v11,Vv11);) + ENABLE_SCALAR_IMPLEMENTATION(V(1,0)=Sv21.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v21,Vv21);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v21,Vv21);) + ENABLE_SCALAR_IMPLEMENTATION(V(2,0)=Sv31.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v31,Vv31);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v31,Vv31);) + ENABLE_SCALAR_IMPLEMENTATION(V(0,1)=Sv12.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v12,Vv12);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v12,Vv12);) + ENABLE_SCALAR_IMPLEMENTATION(V(1,1)=Sv22.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v22,Vv22);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v22,Vv22);) + ENABLE_SCALAR_IMPLEMENTATION(V(2,1)=Sv32.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v32,Vv32);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v32,Vv32);) + ENABLE_SCALAR_IMPLEMENTATION(V(0,2)=Sv13.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v13,Vv13);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v13,Vv13);) + ENABLE_SCALAR_IMPLEMENTATION(V(1,2)=Sv23.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v23,Vv23);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v23,Vv23);) + ENABLE_SCALAR_IMPLEMENTATION(V(2,2)=Sv33.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(v33,Vv33);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(v33,Vv33);) + + ENABLE_SCALAR_IMPLEMENTATION(S(0,0)=Sa11.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(sigma1,Va11);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(sigma1,Va11);) + ENABLE_SCALAR_IMPLEMENTATION(S(1,0)=Sa22.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(sigma2,Va22);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(sigma2,Va22);) + ENABLE_SCALAR_IMPLEMENTATION(S(2,0)=Sa33.f;) ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(sigma3,Va33);) ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(sigma3,Va33);) +} +#pragma runtime_checks( "u", restore ) + +// forced instantiation +template void igl::svd3x3(const Eigen::Matrix& A, Eigen::Matrix &U, Eigen::Matrix &S, Eigen::Matrix&V); +template void igl::svd3x3(Eigen::Matrix const&, Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +// doesn't even make sense with double because this SVD code is only single precision anyway... diff --git a/src/igl/svd3x3.h b/src/igl/svd3x3.h new file mode 100644 index 000000000..900d459f4 --- /dev/null +++ b/src/igl/svd3x3.h @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SVD3X3_H +#define IGL_SVD3X3_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Super fast 3x3 SVD according to http://pages.cs.wisc.edu/~sifakis/project_pages/svd.html + // The resulting decomposition is A = U * diag(S[0], S[1], S[2]) * V' + // BEWARE: this SVD algorithm guarantees that det(U) = det(V) = 1, but this + // comes at the cost that 'sigma3' can be negative + // for computing polar decomposition it's great because all we need to do is U*V' + // and the result will automatically have positive determinant + // + // Inputs: + // A 3x3 matrix + // Outputs: + // U 3x3 left singular vectors + // S 3x1 singular values + // V 3x3 right singular vectors + // + // Known bugs: this will not work correctly for double precision. + template + IGL_INLINE void svd3x3( + const Eigen::Matrix& A, + Eigen::Matrix &U, + Eigen::Matrix &S, + Eigen::Matrix&V); +} +#ifndef IGL_STATIC_LIBRARY +# include "svd3x3.cpp" +#endif +#endif diff --git a/src/igl/svd3x3_avx.cpp b/src/igl/svd3x3_avx.cpp new file mode 100644 index 000000000..db56ee30a --- /dev/null +++ b/src/igl/svd3x3_avx.cpp @@ -0,0 +1,108 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifdef __AVX__ +#include "svd3x3_avx.h" + +#include +#include + +#undef USE_SCALAR_IMPLEMENTATION +#undef USE_SSE_IMPLEMENTATION +#define USE_AVX_IMPLEMENTATION +#define COMPUTE_U_AS_MATRIX +#define COMPUTE_V_AS_MATRIX +#include "Singular_Value_Decomposition_Preamble.hpp" + +#pragma runtime_checks( "u", off ) // disable runtime asserts on xor eax,eax type of stuff (doesn't always work, disable explicitly in compiler settings) +template +IGL_INLINE void igl::svd3x3_avx( + const Eigen::Matrix& A, + Eigen::Matrix &U, + Eigen::Matrix &S, + Eigen::Matrix&V) +{ + // this code assumes USE_AVX_IMPLEMENTATION is defined + float Ashuffle[9][8], Ushuffle[9][8], Vshuffle[9][8], Sshuffle[3][8]; + for (int i=0; i<3; i++) + { + for (int j=0; j<3; j++) + { + for (int k=0; k<8; k++) + { + Ashuffle[i + j*3][k] = A(i + 3*k, j); + } + } + } + +#include "Singular_Value_Decomposition_Kernel_Declarations.hpp" + + ENABLE_AVX_IMPLEMENTATION(Va11=_mm256_loadu_ps(Ashuffle[0]);) + ENABLE_AVX_IMPLEMENTATION(Va21=_mm256_loadu_ps(Ashuffle[1]);) + ENABLE_AVX_IMPLEMENTATION(Va31=_mm256_loadu_ps(Ashuffle[2]);) + ENABLE_AVX_IMPLEMENTATION(Va12=_mm256_loadu_ps(Ashuffle[3]);) + ENABLE_AVX_IMPLEMENTATION(Va22=_mm256_loadu_ps(Ashuffle[4]);) + ENABLE_AVX_IMPLEMENTATION(Va32=_mm256_loadu_ps(Ashuffle[5]);) + ENABLE_AVX_IMPLEMENTATION(Va13=_mm256_loadu_ps(Ashuffle[6]);) + ENABLE_AVX_IMPLEMENTATION(Va23=_mm256_loadu_ps(Ashuffle[7]);) + ENABLE_AVX_IMPLEMENTATION(Va33=_mm256_loadu_ps(Ashuffle[8]);) + +#include "Singular_Value_Decomposition_Main_Kernel_Body.hpp" + + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[0],Vu11);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[1],Vu21);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[2],Vu31);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[3],Vu12);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[4],Vu22);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[5],Vu32);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[6],Vu13);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[7],Vu23);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Ushuffle[8],Vu33);) + + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[0],Vv11);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[1],Vv21);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[2],Vv31);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[3],Vv12);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[4],Vv22);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[5],Vv32);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[6],Vv13);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[7],Vv23);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Vshuffle[8],Vv33);) + + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Sshuffle[0],Va11);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Sshuffle[1],Va22);) + ENABLE_AVX_IMPLEMENTATION(_mm256_storeu_ps(Sshuffle[2],Va33);) + + for (int i=0; i<3; i++) + { + for (int j=0; j<3; j++) + { + for (int k=0; k<8; k++) + { + U(i + 3*k, j) = Ushuffle[i + j*3][k]; + V(i + 3*k, j) = Vshuffle[i + j*3][k]; + } + } + } + + for (int i=0; i<3; i++) + { + for (int k=0; k<8; k++) + { + S(i + 3*k, 0) = Sshuffle[i][k]; + } + } +} +#pragma runtime_checks( "u", restore ) + +#ifdef IGL_STATIC_LIBRARY +// forced instantiation +//template void igl::svd3x3_avx(const Eigen::Matrix& A, Eigen::Matrix &U, Eigen::Matrix &S, Eigen::Matrix&V); +// doesn't even make sense with double because the wunder-SVD code is only single precision anyway... +template void igl::svd3x3_avx(Eigen::Matrix const&, Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +#endif +#endif diff --git a/src/igl/svd3x3_avx.h b/src/igl/svd3x3_avx.h new file mode 100644 index 000000000..b814acc95 --- /dev/null +++ b/src/igl/svd3x3_avx.h @@ -0,0 +1,40 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SVD3X3_AVX_H +#define IGL_SVD3X3_AVX_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Super fast 3x3 SVD according to + // http://pages.cs.wisc.edu/~sifakis/project_pages/svd.html This is AVX + // version of svd3x3 (see svd3x3.h) which works on 8 matrices at a time These + // eight matrices are simply stacked in columns, the rest is the same as for + // svd3x3 + // + // Inputs: + // A 12 by 3 stack of 3x3 matrices + // Outputs: + // U 12x3 left singular vectors stacked + // S 12x1 singular values stacked + // V 12x3 right singular vectors stacked + // + // Known bugs: this will not work correctly for double precision. + template + IGL_INLINE void svd3x3_avx( + const Eigen::Matrix& A, + Eigen::Matrix &U, + Eigen::Matrix &S, + Eigen::Matrix&V); +} +#ifndef IGL_STATIC_LIBRARY +# include "svd3x3_avx.cpp" +#endif +#endif + diff --git a/src/igl/svd3x3_sse.cpp b/src/igl/svd3x3_sse.cpp new file mode 100644 index 000000000..2ef4e0fe8 --- /dev/null +++ b/src/igl/svd3x3_sse.cpp @@ -0,0 +1,108 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifdef __SSE__ +#include "svd3x3_sse.h" + +#include +#include + +#undef USE_SCALAR_IMPLEMENTATION +#define USE_SSE_IMPLEMENTATION +#undef USE_AVX_IMPLEMENTATION +#define COMPUTE_U_AS_MATRIX +#define COMPUTE_V_AS_MATRIX +#include "Singular_Value_Decomposition_Preamble.hpp" + +// disable runtime asserts on xor eax,eax type of stuff (doesn't always work, +// disable explicitly in compiler settings) +#pragma runtime_checks( "u", off ) +template +IGL_INLINE void igl::svd3x3_sse( + const Eigen::Matrix& A, + Eigen::Matrix &U, + Eigen::Matrix &S, + Eigen::Matrix&V) +{ + // this code assumes USE_SSE_IMPLEMENTATION is defined + float Ashuffle[9][4], Ushuffle[9][4], Vshuffle[9][4], Sshuffle[3][4]; + for (int i=0; i<3; i++) + { + for (int j=0; j<3; j++) + { + for (int k=0; k<4; k++) + { + Ashuffle[i + j*3][k] = A(i + 3*k, j); + } + } + } + +#include "Singular_Value_Decomposition_Kernel_Declarations.hpp" + + ENABLE_SSE_IMPLEMENTATION(Va11=_mm_loadu_ps(Ashuffle[0]);) + ENABLE_SSE_IMPLEMENTATION(Va21=_mm_loadu_ps(Ashuffle[1]);) + ENABLE_SSE_IMPLEMENTATION(Va31=_mm_loadu_ps(Ashuffle[2]);) + ENABLE_SSE_IMPLEMENTATION(Va12=_mm_loadu_ps(Ashuffle[3]);) + ENABLE_SSE_IMPLEMENTATION(Va22=_mm_loadu_ps(Ashuffle[4]);) + ENABLE_SSE_IMPLEMENTATION(Va32=_mm_loadu_ps(Ashuffle[5]);) + ENABLE_SSE_IMPLEMENTATION(Va13=_mm_loadu_ps(Ashuffle[6]);) + ENABLE_SSE_IMPLEMENTATION(Va23=_mm_loadu_ps(Ashuffle[7]);) + ENABLE_SSE_IMPLEMENTATION(Va33=_mm_loadu_ps(Ashuffle[8]);) + +#include "Singular_Value_Decomposition_Main_Kernel_Body.hpp" + + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[0],Vu11);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[1],Vu21);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[2],Vu31);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[3],Vu12);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[4],Vu22);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[5],Vu32);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[6],Vu13);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[7],Vu23);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Ushuffle[8],Vu33);) + + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[0],Vv11);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[1],Vv21);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[2],Vv31);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[3],Vv12);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[4],Vv22);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[5],Vv32);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[6],Vv13);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[7],Vv23);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Vshuffle[8],Vv33);) + + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Sshuffle[0],Va11);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Sshuffle[1],Va22);) + ENABLE_SSE_IMPLEMENTATION(_mm_storeu_ps(Sshuffle[2],Va33);) + + for (int i=0; i<3; i++) + { + for (int j=0; j<3; j++) + { + for (int k=0; k<4; k++) + { + U(i + 3*k, j) = Ushuffle[i + j*3][k]; + V(i + 3*k, j) = Vshuffle[i + j*3][k]; + } + } + } + + for (int i=0; i<3; i++) + { + for (int k=0; k<4; k++) + { + S(i + 3*k, 0) = Sshuffle[i][k]; + } + } +} +#pragma runtime_checks( "u", restore ) + +// forced instantiation +template void igl::svd3x3_sse(const Eigen::Matrix& A, Eigen::Matrix &U, Eigen::Matrix &S, Eigen::Matrix&V); +//// doesn't even make sense with double because the wunder-SVD code is only single precision anyway... +//template void wunderSVD3x3_SSE(Eigen::Matrix const&, Eigen::Matrix&, Eigen::Matrix&, Eigen::Matrix&); +#endif diff --git a/src/igl/svd3x3_sse.h b/src/igl/svd3x3_sse.h new file mode 100644 index 000000000..1fcff988a --- /dev/null +++ b/src/igl/svd3x3_sse.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SVD3X3_SSE_H +#define IGL_SVD3X3_SSE_H +#include "igl_inline.h" +#include + +namespace igl +{ + // Super fast 3x3 SVD according to http://pages.cs.wisc.edu/~sifakis/project_pages/svd.html + // This is SSE version of svd3x3 (see svd3x3.h) which works on 4 matrices at a time + // These four matrices are simply stacked in columns, the rest is the same as for svd3x3 + // + // Inputs: + // A 12 by 3 stack of 3x3 matrices + // Outputs: + // U 12x3 left singular vectors stacked + // S 12x1 singular values stacked + // V 12x3 right singular vectors stacked + // + // Known bugs: this will not work correctly for double precision. + template + IGL_INLINE void svd3x3_sse( + const Eigen::Matrix& A, + Eigen::Matrix &U, + Eigen::Matrix &S, + Eigen::Matrix&V); +} +#ifndef IGL_STATIC_LIBRARY +# include "svd3x3_sse.cpp" +#endif +#endif diff --git a/src/igl/swept_volume_bounding_box.cpp b/src/igl/swept_volume_bounding_box.cpp new file mode 100644 index 000000000..7fc12e2f1 --- /dev/null +++ b/src/igl/swept_volume_bounding_box.cpp @@ -0,0 +1,28 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "swept_volume_bounding_box.h" +#include "LinSpaced.h" + +IGL_INLINE void igl::swept_volume_bounding_box( + const size_t & n, + const std::function & V, + const size_t & steps, + Eigen::AlignedBox3d & box) +{ + using namespace Eigen; + box.setEmpty(); + const VectorXd t = igl::LinSpaced(steps,0,1); + // Find extent over all time steps + for(int ti = 0;ti +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SWEPT_VOLUME_BOUNDING_BOX_H +#define IGL_SWEPT_VOLUME_BOUNDING_BOX_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Construct an axis-aligned bounding box containing a shape undergoing a + // motion sampled at `steps` discrete momements. + // + // Inputs: + // n number of mesh vertices + // V function handle so that V(i,t) returns the 3d position of vertex + // i at time t, for t∈[0,1] + // steps number of time steps: steps=3 --> t∈{0,0.5,1} + // Outputs: + // box box containing mesh under motion + IGL_INLINE void swept_volume_bounding_box( + const size_t & n, + const std::function< + Eigen::RowVector3d(const size_t vi, const double t)> & V, + const size_t & steps, + Eigen::AlignedBox3d & box); +} + +#ifndef IGL_STATIC_LIBRARY +# include "swept_volume_bounding_box.cpp" +#endif + +#endif diff --git a/src/igl/swept_volume_signed_distance.cpp b/src/igl/swept_volume_signed_distance.cpp new file mode 100644 index 000000000..2571a7d12 --- /dev/null +++ b/src/igl/swept_volume_signed_distance.cpp @@ -0,0 +1,122 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "swept_volume_signed_distance.h" +#include "LinSpaced.h" +#include "flood_fill.h" +#include "signed_distance.h" +#include "AABB.h" +#include "pseudonormal_test.h" +#include "per_face_normals.h" +#include "per_vertex_normals.h" +#include "per_edge_normals.h" +#include +#include +#include + +IGL_INLINE void igl::swept_volume_signed_distance( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & transform, + const size_t & steps, + const Eigen::MatrixXd & GV, + const Eigen::RowVector3i & res, + const double h, + const double isolevel, + const Eigen::VectorXd & S0, + Eigen::VectorXd & S) +{ + using namespace std; + using namespace igl; + using namespace Eigen; + S = S0; + const VectorXd t = igl::LinSpaced(steps,0,1); + const bool finite_iso = isfinite(isolevel); + const double extension = (finite_iso ? isolevel : 0) + sqrt(3.0)*h; + Eigen::AlignedBox3d box( + V.colwise().minCoeff().array()-extension, + V.colwise().maxCoeff().array()+extension); + // Precomputation + Eigen::MatrixXd FN,VN,EN; + Eigen::MatrixXi E; + Eigen::VectorXi EMAP; + per_face_normals(V,F,FN); + per_vertex_normals(V,F,PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE,FN,VN); + per_edge_normals( + V,F,PER_EDGE_NORMALS_WEIGHTING_TYPE_UNIFORM,FN,EN,E,EMAP); + AABB tree; + tree.init(V,F); + for(int ti = 0;ti::infinity(); + sqrd = tree.squared_distance(V,F,gv,min_sqrd,i,c); + if(sqrd & transform, + const size_t & steps, + const Eigen::MatrixXd & GV, + const Eigen::RowVector3i & res, + const double h, + const double isolevel, + Eigen::VectorXd & S) +{ + using namespace std; + using namespace igl; + using namespace Eigen; + S = VectorXd::Constant(GV.rows(),1,numeric_limits::quiet_NaN()); + return + swept_volume_signed_distance(V,F,transform,steps,GV,res,h,isolevel,S,S); +} diff --git a/src/igl/swept_volume_signed_distance.h b/src/igl/swept_volume_signed_distance.h new file mode 100644 index 000000000..86074fb67 --- /dev/null +++ b/src/igl/swept_volume_signed_distance.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_SWEPT_VOLUME_SIGNED_DISTANCE_H +#define IGL_SWEPT_VOLUME_SIGNED_DISTANCE_H +#include "igl_inline.h" +#include +#include +#include +namespace igl +{ + // Compute the signed distance to a sweep surface of a mesh under-going + // an arbitrary motion V(t) discretely sampled at `steps`-many moments in + // time at a grid. + // + // Inputs: + // V #V by 3 list of mesh positions in reference pose + // F #F by 3 list of triangle indices [0,n) + // transform function handle so that transform(t) returns the rigid + // transformation at time t∈[0,1] + // steps number of time steps: steps=3 --> t∈{0,0.5,1} + // GV #GV by 3 list of evaluation point grid positions + // res 3-long resolution of GV grid + // h edge-length of grid + // isolevel isolevel to "focus" on; grid positions far enough away from + // isolevel (based on h) will get approximate values). Set + // isolevel=infinity to get good values everywhere (slow and + // unnecessary if just trying to extract isolevel-level set). + // S0 #GV initial values (will take minimum with these), can be same + // as S) + // Outputs: + // S #GV list of signed distances + IGL_INLINE void swept_volume_signed_distance( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & transform, + const size_t & steps, + const Eigen::MatrixXd & GV, + const Eigen::RowVector3i & res, + const double h, + const double isolevel, + const Eigen::VectorXd & S0, + Eigen::VectorXd & S); + IGL_INLINE void swept_volume_signed_distance( + const Eigen::MatrixXd & V, + const Eigen::MatrixXi & F, + const std::function & transform, + const size_t & steps, + const Eigen::MatrixXd & GV, + const Eigen::RowVector3i & res, + const double h, + const double isolevel, + Eigen::VectorXd & S); + } + +#ifndef IGL_STATIC_LIBRARY +# include "swept_volume_signed_distance.cpp" +#endif + +#endif diff --git a/src/igl/trackball.cpp b/src/igl/trackball.cpp new file mode 100644 index 000000000..3a5fa2e9f --- /dev/null +++ b/src/igl/trackball.cpp @@ -0,0 +1,168 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "trackball.h" + +#include "EPS.h" +#include "dot.h" +#include "cross.h" +#include "axis_angle_to_quat.h" +#include "quat_mult.h" +#include +#include +#include +#include +#include + +// Utility IGL_INLINE functions +template +static IGL_INLINE Q_type _QuatD(double w, double h) +{ + using namespace std; + return (Q_type)(std::abs(w) < std::abs(h) ? std::abs(w) : std::abs(h)) - 4; +} +template +static IGL_INLINE Q_type _QuatIX(double x, double w, double h) +{ + return (2.0f*(Q_type)x - (Q_type)w - 1.0f)/_QuatD(w, h); +} +template +static IGL_INLINE Q_type _QuatIY(double y, double w, double h) +{ + return (-2.0f*(Q_type)y + (Q_type)h - 1.0f)/_QuatD(w, h); +} + +// This is largely the trackball as implemented in AntTweakbar. Much of the +// code is straight from its source in TwMgr.cpp +// http://www.antisphere.com/Wiki/tools:anttweakbar +template +IGL_INLINE void igl::trackball( + const double w, + const double h, + const Q_type speed_factor, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Q_type * quat) +{ + assert(speed_factor > 0); + + double original_x = + _QuatIX(speed_factor*(down_mouse_x-w/2)+w/2, w, h); + double original_y = + _QuatIY(speed_factor*(down_mouse_y-h/2)+h/2, w, h); + + double x = _QuatIX(speed_factor*(mouse_x-w/2)+w/2, w, h); + double y = _QuatIY(speed_factor*(mouse_y-h/2)+h/2, w, h); + + double z = 1; + double n0 = sqrt(original_x*original_x + original_y*original_y + z*z); + double n1 = sqrt(x*x + y*y + z*z); + if(n0>igl::DOUBLE_EPS && n1>igl::DOUBLE_EPS) + { + double v0[] = { original_x/n0, original_y/n0, z/n0 }; + double v1[] = { x/n1, y/n1, z/n1 }; + double axis[3]; + cross(v0,v1,axis); + double sa = sqrt(dot(axis, axis)); + double ca = dot(v0, v1); + double angle = atan2(sa, ca); + if( x*x+y*y>1.0 ) + { + angle *= 1.0 + 0.2f*(sqrt(x*x+y*y)-1.0); + } + double qrot[4]; + axis_angle_to_quat(axis,angle,qrot); + quat[0] = qrot[0]; + quat[1] = qrot[1]; + quat[2] = qrot[2]; + quat[3] = qrot[3]; + } +} + + +template +IGL_INLINE void igl::trackball( + const double w, + const double h, + const Q_type speed_factor, + const Q_type * down_quat, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Q_type * quat) +{ + double qrot[4], qres[4], qorig[4]; + igl::trackball( + w,h, + speed_factor, + down_mouse_x,down_mouse_y, + mouse_x,mouse_y, + qrot); + double nqorig = + sqrt(down_quat[0]*down_quat[0]+ + down_quat[1]*down_quat[1]+ + down_quat[2]*down_quat[2]+ + down_quat[3]*down_quat[3]); + + if( fabs(nqorig)>igl::DOUBLE_EPS_SQ ) + { + qorig[0] = down_quat[0]/nqorig; + qorig[1] = down_quat[1]/nqorig; + qorig[2] = down_quat[2]/nqorig; + qorig[3] = down_quat[3]/nqorig; + igl::quat_mult(qrot,qorig,qres); + quat[0] = qres[0]; + quat[1] = qres[1]; + quat[2] = qres[2]; + quat[3] = qres[3]; + } + else + { + quat[0] = qrot[0]; + quat[1] = qrot[1]; + quat[2] = qrot[2]; + quat[3] = qrot[3]; + } +} + +template +IGL_INLINE void igl::trackball( + const double w, + const double h, + const double speed_factor, + const Eigen::Quaternion & down_quat, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Eigen::Quaternion & quat) +{ + using namespace std; + return trackball( + w, + h, + (Scalarquat)speed_factor, + down_quat.coeffs().data(), + down_mouse_x, + down_mouse_y, + mouse_x, + mouse_y, + quat.coeffs().data()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::trackball(double, double, double, double const*, double, double, double, double, double*); +// generated by autoexplicit.sh +template void igl::trackball(double, double, float, float const*, double, double, double, double, float*); +template void igl::trackball(double, double, double, Eigen::Quaternion const&, double, double, double, double, Eigen::Quaternion&); +template void igl::trackball(double, double, double, Eigen::Quaternion const&, double, double, double, double, Eigen::Quaternion&); +#endif diff --git a/src/igl/trackball.h b/src/igl/trackball.h new file mode 100644 index 000000000..6bf79e019 --- /dev/null +++ b/src/igl/trackball.h @@ -0,0 +1,80 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRACKBALL_H +#define IGL_TRACKBALL_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Applies a trackball drag to identity + // Inputs: + // w width of the trackball context + // h height of the trackball context + // speed_factor controls how fast the trackball feels, 1 is normal + // down_mouse_x x position of mouse down + // down_mouse_y y position of mouse down + // mouse_x current x position of mouse + // mouse_y current y position of mouse + // Outputs: + // quat the resulting rotation (as quaternion) + template + IGL_INLINE void trackball( + const double w, + const double h, + const Q_type speed_factor, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Q_type * quat); + + // Applies a trackball drag to a given rotation + // Inputs: + // w width of the trackball context + // h height of the trackball context + // speed_factor controls how fast the trackball feels, 1 is normal + // down_quat rotation at mouse down, i.e. the rotation we're applying the + // trackball motion to (as quaternion) + // down_mouse_x x position of mouse down + // down_mouse_y y position of mouse down + // mouse_x current x position of mouse + // mouse_y current y position of mouse + // Outputs: + // quat the resulting rotation (as quaternion) + template + IGL_INLINE void trackball( + const double w, + const double h, + const Q_type speed_factor, + const Q_type * down_quat, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Q_type * quat); + // Eigen wrapper. + template + IGL_INLINE void trackball( + const double w, + const double h, + const double speed_factor, + const Eigen::Quaternion & down_quat, + const double down_mouse_x, + const double down_mouse_y, + const double mouse_x, + const double mouse_y, + Eigen::Quaternion & quat); +} + +#ifndef IGL_STATIC_LIBRARY +# include "trackball.cpp" +#endif + +#endif diff --git a/src/igl/transpose_blocks.cpp b/src/igl/transpose_blocks.cpp new file mode 100644 index 000000000..004edd7ac --- /dev/null +++ b/src/igl/transpose_blocks.cpp @@ -0,0 +1,63 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "transpose_blocks.h" + +#include + +template +IGL_INLINE void igl::transpose_blocks( + const Eigen::Matrix & A, + const size_t k, + const size_t dim, + Eigen::Matrix & B) +{ + // Eigen matrices must be 2d so dim must be only 1 or 2 + assert(dim == 1 || dim == 2); + // Output is not allowed to be input + assert(&A != &B); + + + // block height, width, and number of blocks + int m,n; + if(dim == 1) + { + m = A.rows()/k; + n = A.cols(); + }else// dim == 2 + { + m = A.rows(); + n = A.cols()/k; + } + + // resize output + if(dim == 1) + { + B.resize(n*k,m); + }else//dim ==2 + { + B.resize(n,m*k); + } + + // loop over blocks + for(int b = 0;b<(int)k;b++) + { + if(dim == 1) + { + B.block(b*n,0,n,m) = A.block(b*m,0,m,n).transpose(); + }else//dim ==2 + { + B.block(0,b*m,n,m) = A.block(0,b*n,m,n).transpose(); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::transpose_blocks(Eigen::Matrix const&, size_t, size_t, Eigen::Matrix&); +#endif diff --git a/src/igl/transpose_blocks.h b/src/igl/transpose_blocks.h new file mode 100644 index 000000000..65ce89fbd --- /dev/null +++ b/src/igl/transpose_blocks.h @@ -0,0 +1,61 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRANSPOSE_BLOCKS_H +#define IGL_TRANSPOSE_BLOCKS_H +#include "igl_inline.h" + +#include + +namespace igl +{ + // Templates: + // T should be a eigen matrix primitive type like int or double + // Inputs: + // A m*k by n (dim: 1) or m by n*k (dim: 2) eigen Matrix of type T values + // k number of blocks + // dim dimension in which to transpose + // Output + // B n*k by m (dim: 1) or n by m*k (dim: 2) eigen Matrix of type T values, + // NOT allowed to be the same as A + // + // Example: + // A = [ + // 1 2 3 4 + // 5 6 7 8 + // 101 102 103 104 + // 105 106 107 108 + // 201 202 203 204 + // 205 206 207 208]; + // transpose_blocks(A,1,3,B); + // B -> [ + // 1 5 + // 2 6 + // 3 7 + // 4 8 + // 101 105 + // 102 106 + // 103 107 + // 104 108 + // 201 205 + // 202 206 + // 203 207 + // 204 208]; + // + template + IGL_INLINE void transpose_blocks( + const Eigen::Matrix & A, + const size_t k, + const size_t dim, + Eigen::Matrix & B); +} + +#ifndef IGL_STATIC_LIBRARY +# include "transpose_blocks.cpp" +#endif + +#endif diff --git a/src/igl/triangle/cdt.cpp b/src/igl/triangle/cdt.cpp new file mode 100644 index 000000000..cbe1b3e1a --- /dev/null +++ b/src/igl/triangle/cdt.cpp @@ -0,0 +1,58 @@ +#include "cdt.h" +#include "../bounding_box.h" +#include "../triangle/triangulate.h" +#include "../remove_duplicate_vertices.h" +#include "../remove_unreferenced.h" +#include "../slice_mask.h" + +template < + typename DerivedV, + typename DerivedE, + typename DerivedWV, + typename DerivedWF, + typename DerivedWE, + typename DerivedJ> +IGL_INLINE void igl::triangle::cdt( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const std::string & flags, + Eigen::PlainObjectBase & WV, + Eigen::PlainObjectBase & WF, + Eigen::PlainObjectBase & WE, + Eigen::PlainObjectBase & J) +{ + assert(V.cols() == 2); + assert(E.cols() == 2); + typedef typename DerivedV::Scalar Scalar; + typedef Eigen::Matrix MatrixX2S; + //MatrixX2S BV; + //Eigen::MatrixXi BE; + //igl::bounding_box(V,BV,BE); + //WV.resize(V.rows()+BV.rows(),2); + //WV<, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::triangle::cdt, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/triangle/cdt.h b/src/igl/triangle/cdt.h new file mode 100644 index 000000000..9f513e853 --- /dev/null +++ b/src/igl/triangle/cdt.h @@ -0,0 +1,54 @@ +#ifndef IGL_TRIANGLE_CDT_H +#define IGL_TRIANGLE_CDT_H + +#include "../igl_inline.h" +#include + +namespace igl +{ + namespace triangle + { + // CDT Construct the constrained delaunay triangulation of the convex hull + // of a given set of points and segments in 2D. This differs from a direct + // call to triangulate because it will preprocess the input to remove + // duplicates and return an adjusted segment list on the output. + // + // + // BACKGROUND_MESH Construct a background mesh for a (messy) texture mesh with + // cosntraint edges that are about to deform. + // + // Inputs: + // V #V by 2 list of texture mesh vertices + // E #E by 2 list of constraint edge indices into V + // flags string of triangle flags should contain "-c" unless the + // some subset of segments are known to enclose all other + // points/segments. + // Outputs: + // WV #WV by 2 list of background mesh vertices + // WF #WF by 2 list of background mesh triangle indices into WV + // WE #WE by 2 list of constraint edge indices into WV (might be smaller + // than E because degenerate constraints have been removed) + // J #V list of indices into WF/WE for each vertex in V + // + template < + typename DerivedV, + typename DerivedE, + typename DerivedWV, + typename DerivedWF, + typename DerivedWE, + typename DerivedJ> + IGL_INLINE void cdt( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const std::string & flags, + Eigen::PlainObjectBase & WV, + Eigen::PlainObjectBase & WF, + Eigen::PlainObjectBase & WE, + Eigen::PlainObjectBase & J); + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "cdt.cpp" +#endif +#endif diff --git a/src/igl/triangle/triangulate.cpp b/src/igl/triangle/triangulate.cpp new file mode 100644 index 000000000..5ee1d4be8 --- /dev/null +++ b/src/igl/triangle/triangulate.cpp @@ -0,0 +1,181 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "triangulate.h" +#ifdef ANSI_DECLARATORS +# define IGL_PREVIOUSLY_DEFINED_ANSI_DECLARATORS ANSI_DECLARATORS +# undef ANSI_DECLARATORS +#endif +#ifdef REAL +# define IGL_PREVIOUSLY_DEFINED_REAL REAL +# undef REAL +#endif +#ifdef VOID +# define IGL_PREVIOUSLY_DEFINED_VOID VOID +# undef VOID +#endif +#define ANSI_DECLARATORS +#define REAL double +#define VOID int + +extern "C" +{ +#include +} + +#undef ANSI_DECLARATORS +#ifdef IGL_PREVIOUSLY_DEFINED_ANSI_DECLARATORS +# define ANSI_DECLARATORS IGL_PREVIOUSLY_DEFINED_ANSI_DECLARATORS +#endif + +#undef REAL +#ifdef IGL_PREVIOUSLY_DEFINED_REAL +# define REAL IGL_PREVIOUSLY_DEFINED_REAL +#endif + +#undef VOID +#ifdef IGL_PREVIOUSLY_DEFINED_VOID +# define VOID IGL_PREVIOUSLY_DEFINED_VOID +#endif + +template < + typename DerivedV, + typename DerivedE, + typename DerivedH, + typename DerivedV2, + typename DerivedF2> +IGL_INLINE void igl::triangle::triangulate( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & H, + const std::string flags, + Eigen::PlainObjectBase & V2, + Eigen::PlainObjectBase & F2) +{ + Eigen::VectorXi VM,EM,VM2,EM2; + return triangulate(V,E,H,VM,EM,flags,V2,F2,VM2,EM2); +} + +template < + typename DerivedV, + typename DerivedE, + typename DerivedH, + typename DerivedVM, + typename DerivedEM, + typename DerivedV2, + typename DerivedF2, + typename DerivedVM2, + typename DerivedEM2> +IGL_INLINE void igl::triangle::triangulate( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & H, + const Eigen::MatrixBase & VM, + const Eigen::MatrixBase & EM, + const std::string flags, + Eigen::PlainObjectBase & V2, + Eigen::PlainObjectBase & F2, + Eigen::PlainObjectBase & VM2, + Eigen::PlainObjectBase & EM2) +{ + using namespace std; + using namespace Eigen; + + assert( (VM.size() == 0 || V.rows() == VM.size()) && + "Vertex markers must be empty or same size as V"); + assert( (EM.size() == 0 || E.rows() == EM.size()) && + "Segment markers must be empty or same size as E"); + assert(V.cols() == 2); + assert(E.size() == 0 || E.cols() == 2); + assert(H.size() == 0 || H.cols() == 2); + + // Prepare the flags + string full_flags = flags + "pz" + (EM.size() || VM.size() ? "" : "B"); + + typedef Map< Matrix > MapXdr; + typedef Map< Matrix > MapXir; + + // Prepare the input struct + triangulateio in; + in.numberofpoints = V.rows(); + in.pointlist = (double*)calloc(V.size(),sizeof(double)); + { + MapXdr inpl(in.pointlist,V.rows(),V.cols()); + inpl = V.template cast(); + } + + in.numberofpointattributes = 0; + in.pointmarkerlist = (int*)calloc(V.size(),sizeof(int)) ; + for(unsigned i=0;i(); + } + in.segmentmarkerlist = (int*)calloc(E.rows(),sizeof(int)); + for (unsigned i=0;i(); + } + in.numberofregions = 0; + + // Prepare the output struct + triangulateio out; + out.pointlist = NULL; + out.trianglelist = NULL; + out.segmentlist = NULL; + out.segmentmarkerlist = NULL; + out.pointmarkerlist = NULL; + + // Call triangle + ::triangulate(const_cast(full_flags.c_str()), &in, &out, 0); + + // Return the mesh + V2 = MapXdr(out.pointlist,out.numberofpoints,2).cast(); + F2 = MapXir(out.trianglelist,out.numberoftriangles,3).cast(); + if(VM.size()) + { + VM2 = MapXir(out.pointmarkerlist,out.numberofpoints,1).cast(); + } + if(EM.size()) + { + EM2 = MapXir(out.segmentmarkerlist,out.numberofsegments,1).cast(); + } + + // Cleanup in + free(in.pointlist); + free(in.pointmarkerlist); + free(in.segmentlist); + free(in.segmentmarkerlist); + free(in.holelist); + // Cleanup out + free(out.pointlist); + free(out.trianglelist); + free(out.segmentlist); + free(out.segmentmarkerlist); + free(out.pointmarkerlist); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::triangle::triangulate, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::triangle::triangulate, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::basic_string, std::allocator >, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/triangle/triangulate.h b/src/igl/triangle/triangulate.h new file mode 100644 index 000000000..90abdac9d --- /dev/null +++ b/src/igl/triangle/triangulate.h @@ -0,0 +1,87 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRIANGLE_TRIANGULATE_H +#define IGL_TRIANGLE_TRIANGULATE_H +#include "../igl_inline.h" +#include +#include + +namespace igl +{ + namespace triangle + { + // Triangulate the interior of a polygon using the triangle library. + // + // Inputs: + // V #V by 2 list of 2D vertex positions + // E #E by 2 list of vertex ids forming unoriented edges of the boundary of the polygon + // H #H by 2 coordinates of points contained inside holes of the polygon + // flags string of options pass to triangle (see triangle documentation) + // Outputs: + // V2 #V2 by 2 coordinates of the vertives of the generated triangulation + // F2 #F2 by 3 list of indices forming the faces of the generated triangulation + // + template < + typename DerivedV, + typename DerivedE, + typename DerivedH, + typename DerivedV2, + typename DerivedF2> + IGL_INLINE void triangulate( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & H, + const std::string flags, + Eigen::PlainObjectBase & V2, + Eigen::PlainObjectBase & F2); + + // Triangulate the interior of a polygon using the triangle library. + // + // Inputs: + // V #V by 2 list of 2D vertex positions + // E #E by 2 list of vertex ids forming unoriented edges of the boundary of the polygon + // H #H by 2 coordinates of points contained inside holes of the polygon + // M #V list of markers for input vertices + // flags string of options pass to triangle (see triangle documentation) + // Outputs: + // V2 #V2 by 2 coordinates of the vertives of the generated triangulation + // F2 #F2 by 3 list of indices forming the faces of the generated triangulation + // M2 #V2 list of markers for output vertices + // + // TODO: expose the option to prevent Steiner points on the boundary + // + template < + typename DerivedV, + typename DerivedE, + typename DerivedH, + typename DerivedVM, + typename DerivedEM, + typename DerivedV2, + typename DerivedF2, + typename DerivedVM2, + typename DerivedEM2> + IGL_INLINE void triangulate( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & H, + const Eigen::MatrixBase & VM, + const Eigen::MatrixBase & EM, + const std::string flags, + Eigen::PlainObjectBase & V2, + Eigen::PlainObjectBase & F2, + Eigen::PlainObjectBase & VM2, + Eigen::PlainObjectBase & EM2); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "triangulate.cpp" +#endif + +#endif diff --git a/src/igl/triangle_fan.cpp b/src/igl/triangle_fan.cpp new file mode 100644 index 000000000..35944d026 --- /dev/null +++ b/src/igl/triangle_fan.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "triangle_fan.h" +#include "exterior_edges.h" +#include "list_to_matrix.h" + +IGL_INLINE void igl::triangle_fan( + const Eigen::MatrixXi & E, + Eigen::MatrixXi & cap) +{ + using namespace std; + using namespace Eigen; + + // Handle lame base case + if(E.size() == 0) + { + cap.resize(0,E.cols()+1); + return; + } + // "Triangulate" aka "close" the E trivially with facets + // Note: in 2D we need to know if E endpoints are incoming or + // outgoing (left or right). Thus this will not work. + assert(E.cols() == 2); + // Arbitrary starting vertex + //int s = E(int(((double)rand() / RAND_MAX)*E.rows()),0); + int s = E(rand()%E.rows(),0); + vector > lcap; + for(int i = 0;i e(3); + e[0] = s; + e[1] = E(i,0); + e[2] = E(i,1); + lcap.push_back(e); + } + list_to_matrix(lcap,cap); +} + +IGL_INLINE Eigen::MatrixXi igl::triangle_fan( const Eigen::MatrixXi & E) +{ + Eigen::MatrixXi cap; + triangle_fan(E,cap); + return cap; +} diff --git a/src/igl/triangle_fan.h b/src/igl/triangle_fan.h new file mode 100644 index 000000000..b56a05bc7 --- /dev/null +++ b/src/igl/triangle_fan.h @@ -0,0 +1,30 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRIANGLE_FAN_H +#define IGL_TRIANGLE_FAN_H +#include "igl_inline.h" +#include +namespace igl +{ + // Given a list of faces tessellate all of the "exterior" edges forming another + // list of + // + // Inputs: + // E #E by simplex_size-1 list of exterior edges (see exterior_edges.h) + // Outputs: + // cap #cap by simplex_size list of "faces" tessellating the boundary edges + IGL_INLINE void triangle_fan( + const Eigen::MatrixXi & E, + Eigen::MatrixXi & cap); + // In-line version + IGL_INLINE Eigen::MatrixXi triangle_fan( const Eigen::MatrixXi & E); +} +#ifndef IGL_STATIC_LIBRARY +# include "triangle_fan.cpp" +#endif +#endif diff --git a/src/igl/triangle_triangle_adjacency.cpp b/src/igl/triangle_triangle_adjacency.cpp new file mode 100644 index 000000000..62e8fbe1c --- /dev/null +++ b/src/igl/triangle_triangle_adjacency.cpp @@ -0,0 +1,274 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2018 Alec Jacobson, Marc Alexa +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "triangle_triangle_adjacency.h" +#include "vertex_triangle_adjacency.h" +#include "parallel_for.h" +#include "unique_edge_map.h" +#include +#include + +// Extract the face adjacencies +template +IGL_INLINE void igl::triangle_triangle_adjacency_extractTT( + const Eigen::MatrixBase& F, + std::vector >& TTT, + Eigen::PlainObjectBase& TT) +{ + TT.setConstant((int)(F.rows()),F.cols(),-1); + + for(int i=1;i<(int)TTT.size();++i) + { + std::vector& r1 = TTT[i-1]; + std::vector& r2 = TTT[i]; + if ((r1[0] == r2[0]) && (r1[1] == r2[1])) + { + TT(r1[2],r1[3]) = r2[2]; + TT(r2[2],r2[3]) = r1[2]; + } + } +} + +template +IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& TT) +{ + const int n = F.maxCoeff()+1; + typedef Eigen::Matrix VectorXI; + VectorXI VF,NI; + vertex_triangle_adjacency(F,n,VF,NI); + TT = DerivedTT::Constant(F.rows(),3,-1); + // Loop over faces + igl::parallel_for(F.rows(),[&](int f) + { + // Loop over corners + for (int k = 0; k < 3; k++) + { + int vi = F(f,k), vin = F(f,(k+1)%3); + // Loop over face neighbors incident on this corner + for (int j = NI[vi]; j < NI[vi+1]; j++) + { + int fn = VF[j]; + // Not this face + if (fn != f) + { + // Face neighbor also has [vi,vin] edge + if (F(fn,0) == vin || F(fn,1) == vin || F(fn,2) == vin) + { + TT(f,k) = fn; + break; + } + } + } + } + }); +} + +template +IGL_INLINE void igl::triangle_triangle_adjacency_preprocess( + const Eigen::MatrixBase& F, + std::vector >& TTT) +{ + for(int f=0;f v2) std::swap(v1,v2); + std::vector r(4); + r[0] = v1; r[1] = v2; + r[2] = f; r[3] = i; + TTT.push_back(r); + } + std::sort(TTT.begin(),TTT.end()); +} + +// Extract the face adjacencies indices (needed for fast traversal) +template +IGL_INLINE void igl::triangle_triangle_adjacency_extractTTi( + const Eigen::MatrixBase& F, + std::vector >& TTT, + Eigen::PlainObjectBase& TTi) +{ + TTi.setConstant((int)(F.rows()),F.cols(),-1); + + for(int i=1;i<(int)TTT.size();++i) + { + std::vector& r1 = TTT[i-1]; + std::vector& r2 = TTT[i]; + if ((r1[0] == r2[0]) && (r1[1] == r2[1])) + { + TTi(r1[2],r1[3]) = r2[3]; + TTi(r2[2],r2[3]) = r1[3]; + } + } +} + +// Compute triangle-triangle adjacency with indices +template +IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TTi) +{ + triangle_triangle_adjacency(F,TT); + TTi = DerivedTTi::Constant(TT.rows(),TT.cols(),-1); + //for(int f = 0; f= 0) + { + for(int kn = 0;kn<3;kn++) + { + int vin = F(fn,kn), vjn = F(fn,(kn+1)%3); + if(vi == vjn && vin == vj) + { + TTi(f,k) = kn; + break; + } + } + } + } + }); +} + +template < + typename DerivedF, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + std::vector > > & TT, + std::vector > > & TTi) +{ + return triangle_triangle_adjacency(F,true,TT,TTi); +} + +template < + typename DerivedF, + typename TTIndex> + IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + std::vector > > & TT) +{ + std::vector > > not_used; + return triangle_triangle_adjacency(F,false,TT,not_used); +} + +template < + typename DerivedF, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + const bool construct_TTi, + std::vector > > & TT, + std::vector > > & TTi) +{ + using namespace Eigen; + using namespace std; + assert(F.cols() == 3 && "Faces must be triangles"); + // number of faces + typedef typename DerivedF::Index Index; + typedef Matrix MatrixX2I; + typedef Matrix VectorXI; + MatrixX2I E,uE; + VectorXI EMAP; + vector > uE2E; + unique_edge_map(F,E,uE,EMAP,uE2E); + return triangle_triangle_adjacency(E,EMAP,uE2E,construct_TTi,TT,TTi); +} + +template < + typename DerivedE, + typename DerivedEMAP, + typename uE2EType, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void igl::triangle_triangle_adjacency( + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + const std::vector > & uE2E, + const bool construct_TTi, + std::vector > > & TT, + std::vector > > & TTi) +{ + using namespace std; + using namespace Eigen; + typedef typename DerivedE::Index Index; + const size_t m = E.rows()/3; + assert((size_t)E.rows() == m*3 && "E should come from list of triangles."); + // E2E[i] --> {j,k,...} means face edge i corresponds to other faces edges j + // and k + TT.resize (m,vector >(3)); + if(construct_TTi) + { + TTi.resize(m,vector >(3)); + } + + // No race conditions because TT*[f][c]'s are in bijection with e's + // Minimum number of items per thread + //const size_t num_e = E.rows(); + // Slightly better memory access than loop over E + igl::parallel_for( + m, + [&](const Index & f) + { + for(Index c = 0;c<3;c++) + { + const Index e = f + m*c; + //const Index c = e/m; + const vector & N = uE2E[EMAP(e)]; + for(const auto & ne : N) + { + const Index nf = ne%m; + // don't add self + if(nf != f) + { + TT[f][c].push_back(nf); + if(construct_TTi) + { + const Index nc = ne/m; + TTi[f][c].push_back(nc); + } + } + } + } + }, + 1000ul); + + +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::triangle_triangle_adjacency, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::triangle_triangle_adjacency, int>(Eigen::MatrixBase > const&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&); +// generated by autoexplicit.sh +template void igl::triangle_triangle_adjacency, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::triangle_triangle_adjacency, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::triangle_triangle_adjacency, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::triangle_triangle_adjacency, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::triangle_triangle_adjacency, long, long>(Eigen::MatrixBase > const&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&); +template void igl::triangle_triangle_adjacency, int>(Eigen::MatrixBase > const&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&); +#ifdef WIN32 +template void igl::triangle_triangle_adjacency, __int64, __int64>(class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>>, class std::allocator>, class std::allocator>>>>> &, class std::vector>, class std::allocator>>>, class std::allocator>, class std::allocator>>>>> &); +template void igl::triangle_triangle_adjacency, class Eigen::Matrix, unsigned __int64, int, int>(class Eigen::MatrixBase> const &, class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>> const &, bool, class std::vector>, class std::allocator>>>, class std::allocator>, class std::allocator>>>>> &, class std::vector>, class std::allocator>>>, class std::allocator>, class std::allocator>>>>> &); +#endif +template void igl::triangle_triangle_adjacency, Eigen::Matrix, long, long, long>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&, bool, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&); +template void igl::triangle_triangle_adjacency, Eigen::Matrix, unsigned long, int, int>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > > const&, bool, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&, std::vector >, std::allocator > > >, std::allocator >, std::allocator > > > > >&); +#endif diff --git a/src/igl/triangle_triangle_adjacency.h b/src/igl/triangle_triangle_adjacency.h new file mode 100644 index 000000000..632141bd0 --- /dev/null +++ b/src/igl/triangle_triangle_adjacency.h @@ -0,0 +1,115 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRIANGLE_TRIANGLE_ADJACENCY_H +#define IGL_TRIANGLE_TRIANGLE_ADJACENCY_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Constructs the triangle-triangle adjacency matrix for a given + // mesh (V,F). + // + // Inputs: + // F #F by simplex_size list of mesh faces (must be triangles) + // Outputs: + // TT #F by #3 adjacent matrix, the element i,j is the id of the triangle + // adjacent to the j edge of triangle i + // TTi #F by #3 adjacent matrix, the element i,j is the id of edge of the + // triangle TT(i,j) that is adjacent with triangle i + // + // NOTE: the first edge of a triangle is [0,1] the second [1,2] and the third + // [2,3]. this convention is DIFFERENT from cotmatrix_entries.h + template + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& TT, + Eigen::PlainObjectBase& TTi); + template + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& TT); + // Preprocessing + template + IGL_INLINE void triangle_triangle_adjacency_preprocess( + const Eigen::MatrixBase& F, + std::vector >& TTT); + // Extract the face adjacencies + template + IGL_INLINE void triangle_triangle_adjacency_extractTT( + const Eigen::MatrixBase& F, + std::vector >& TTT, + Eigen::PlainObjectBase& TT); + // Extract the face adjacencies indices (needed for fast traversal) + template + IGL_INLINE void triangle_triangle_adjacency_extractTTi( + const Eigen::MatrixBase& F, + std::vector >& TTT, + Eigen::PlainObjectBase& TTi); + // Adjacency list version, which works with non-manifold meshes + // + // Inputs: + // F #F by 3 list of triangle indices + // Outputs: + // TT #F by 3 list of lists so that TT[i][c] --> {j,k,...} means that + // faces j and k etc. are edge-neighbors of face i on face i's edge + // opposite corner c + // TTj #F list of lists so that TTj[i][c] --> {j,k,...} means that face + // TT[i][c][0] is an edge-neighbor of face i incident on the edge of face + // TT[i][c][0] opposite corner j, and TT[i][c][1] " corner k, etc. + template < + typename DerivedF, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + std::vector > > & TT, + std::vector > > & TTi); + template < typename DerivedF, typename TTIndex> + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + std::vector > > & TT); + // Wrapper with bool to choose whether to compute TTi (this prototype should + // be "hidden"). + template < + typename DerivedF, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase & F, + const bool construct_TTi, + std::vector > > & TT, + std::vector > > & TTi); + // Inputs: + // E #F*3 by 2 list of all of directed edges in order (see + // `oriented_facets`) + // EMAP #F*3 list of indices into uE, mapping each directed edge to unique + // undirected edge + // uE2E #uE list of lists of indices into E of coexisting edges + // See also: unique_edge_map, oriented_facets + template < + typename DerivedE, + typename DerivedEMAP, + typename uE2EType, + typename TTIndex, + typename TTiIndex> + IGL_INLINE void triangle_triangle_adjacency( + const Eigen::MatrixBase & E, + const Eigen::MatrixBase & EMAP, + const std::vector > & uE2E, + const bool construct_TTi, + std::vector > > & TT, + std::vector > > & TTi); +} + +#ifndef IGL_STATIC_LIBRARY +# include "triangle_triangle_adjacency.cpp" +#endif + +#endif diff --git a/src/igl/triangles_from_strip.cpp b/src/igl/triangles_from_strip.cpp new file mode 100644 index 000000000..1db854834 --- /dev/null +++ b/src/igl/triangles_from_strip.cpp @@ -0,0 +1,36 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "triangles_from_strip.h" +#include + +template +IGL_INLINE void igl::triangles_from_strip( + const Eigen::MatrixBase& S, + Eigen::PlainObjectBase& F) +{ + using namespace std; + F.resize(S.size()-2,3); + for(int s = 0;s < S.size()-2;s++) + { + if(s%2 == 0) + { + F(s,0) = S(s+2); + F(s,1) = S(s+1); + F(s,2) = S(s+0); + }else + { + F(s,0) = S(s+0); + F(s,1) = S(s+1); + F(s,2) = S(s+2); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +#endif diff --git a/src/igl/triangles_from_strip.h b/src/igl/triangles_from_strip.h new file mode 100644 index 000000000..01ffd6402 --- /dev/null +++ b/src/igl/triangles_from_strip.h @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TRIANGLES_FROM_STRIP_H +#define IGL_TRIANGLES_FROM_STRIP_H +#include "igl_inline.h" +#include +namespace igl +{ + // TRIANGLES_FROM_STRIP Create a list of triangles from a stream of indices + // along a strip. + // + // Inputs: + // S #S list of indices + // Outputs: + // F #S-2 by 3 list of triangle indices + // + template + IGL_INLINE void triangles_from_strip( + const Eigen::MatrixBase& S, + Eigen::PlainObjectBase& F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "triangles_from_strip.cpp" +#endif + +#endif + diff --git a/src/igl/two_axis_valuator_fixed_up.cpp b/src/igl/two_axis_valuator_fixed_up.cpp new file mode 100644 index 000000000..4ffb607f4 --- /dev/null +++ b/src/igl/two_axis_valuator_fixed_up.cpp @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "two_axis_valuator_fixed_up.h" +#include "PI.h" + +template +IGL_INLINE void igl::two_axis_valuator_fixed_up( + const int w, + const int h, + const double speed, + const Eigen::Quaternion & down_quat, + const int down_x, + const int down_y, + const int mouse_x, + const int mouse_y, + Eigen::Quaternion & quat) +{ + using namespace Eigen; + Matrix axis(0,1,0); + quat = down_quat * + Quaternion( + AngleAxis( + PI*((Scalarquat)(mouse_x-down_x))/(Scalarquat)w*speed/2.0, + axis.normalized())); + quat.normalize(); + { + Matrix axis(1,0,0); + if(axis.norm() != 0) + { + quat = + Quaternion( + AngleAxis( + PI*(mouse_y-down_y)/(Scalarquat)h*speed/2.0, + axis.normalized())) * quat; + quat.normalize(); + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::two_axis_valuator_fixed_up(int, int, double, Eigen::Quaternion const&, int, int, int, int, Eigen::Quaternion&); +template void igl::two_axis_valuator_fixed_up(int, int, double, Eigen::Quaternion const&, int, int, int, int, Eigen::Quaternion&); +#endif diff --git a/src/igl/two_axis_valuator_fixed_up.h b/src/igl/two_axis_valuator_fixed_up.h new file mode 100644 index 000000000..7b2e9aabf --- /dev/null +++ b/src/igl/two_axis_valuator_fixed_up.h @@ -0,0 +1,51 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_TWO_AXIS_VALUATOR_FIXED_AXIS_UP_H +#define IGL_TWO_AXIS_VALUATOR_FIXED_AXIS_UP_H + +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Applies a two-axis valuator drag rotation (as seen in Maya/Studio max) to a given rotation. + // Inputs: + // w width of the trackball context + // h height of the trackball context + // speed controls how fast the trackball feels, 1 is normal + // down_quat rotation at mouse down, i.e. the rotation we're applying the + // trackball motion to (as quaternion). **Note:** Up-vector that is fixed + // is with respect to this rotation. + // down_x position of mouse down + // down_y position of mouse down + // mouse_x current x position of mouse + // mouse_y current y position of mouse + // Outputs: + // quat the resulting rotation (as quaternion) + // + // See also: snap_to_fixed_up + template + IGL_INLINE void two_axis_valuator_fixed_up( + const int w, + const int h, + const double speed, + const Eigen::Quaternion & down_quat, + const int down_x, + const int down_y, + const int mouse_x, + const int mouse_y, + Eigen::Quaternion & quat); +} + +#ifndef IGL_STATIC_LIBRARY +# include "two_axis_valuator_fixed_up.cpp" +#endif + +#endif + diff --git a/src/igl/uniformly_sample_two_manifold.cpp b/src/igl/uniformly_sample_two_manifold.cpp new file mode 100644 index 000000000..d35f74113 --- /dev/null +++ b/src/igl/uniformly_sample_two_manifold.cpp @@ -0,0 +1,427 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "uniformly_sample_two_manifold.h" +#include "verbose.h" +#include "slice.h" +#include "colon.h" +#include "all_pairs_distances.h" +#include "mat_max.h" +#include "vertex_triangle_adjacency.h" +#include "get_seconds.h" +#include "cat.h" +//#include "MT19937.h" +#include "partition.h" + +////////////////////////////////////////////////////////////////////////////// +// Helper functions +////////////////////////////////////////////////////////////////////////////// + +IGL_INLINE void igl::uniformly_sample_two_manifold( + const Eigen::MatrixXd & W, + const Eigen::MatrixXi & F, + const int k, + const double push, + Eigen::MatrixXd & WS) +{ + using namespace Eigen; + using namespace std; + + // Euclidean distance between two points on a mesh given as barycentric + // coordinates + // Inputs: + // W #W by dim positions of mesh in weight space + // F #F by 3 indices of triangles + // face_A face index where 1st point lives + // bary_A barycentric coordinates of 1st point on face_A + // face_B face index where 2nd point lives + // bary_B barycentric coordinates of 2nd point on face_B + // Returns distance in euclidean space + const auto & bary_dist = [] ( + const Eigen::MatrixXd & W, + const Eigen::MatrixXi & F, + const int face_A, + const Eigen::Vector3d & bary_A, + const int face_B, + const Eigen::Vector3d & bary_B) -> double + { + return + ((bary_A(0)*W.row(F(face_A,0)) + + bary_A(1)*W.row(F(face_A,1)) + + bary_A(2)*W.row(F(face_A,2))) + - + (bary_B(0)*W.row(F(face_B,0)) + + bary_B(1)*W.row(F(face_B,1)) + + bary_B(2)*W.row(F(face_B,2)))).norm(); + }; + + // Base case if F is a tet list, find all faces and pass as non-manifold + // triangle mesh + if(F.cols() == 4) + { + verbose("uniform_sample.h: sampling tet mesh\n"); + MatrixXi T0 = F.col(0); + MatrixXi T1 = F.col(1); + MatrixXi T2 = F.col(2); + MatrixXi T3 = F.col(3); + // Faces from tets + MatrixXi TF = + cat(1, + cat(1, + cat(2,T0, cat(2,T1,T2)), + cat(2,T0, cat(2,T2,T3))), + cat(1, + cat(2,T0, cat(2,T3,T1)), + cat(2,T1, cat(2,T3,T2))) + ); + assert(TF.rows() == 4*F.rows()); + assert(TF.cols() == 3); + uniformly_sample_two_manifold(W,TF,k,push,WS); + return; + } + + double start = get_seconds(); + + VectorXi S; + // First get sampling as best as possible on mesh + uniformly_sample_two_manifold_at_vertices(W,k,push,S); + verbose("Lap: %g\n",get_seconds()-start); + slice(W,S,colon(0,W.cols()-1),WS); + //cout<<"WSmesh=["< > VF,VFi; + vertex_triangle_adjacency(W,F,VF,VFi); + + // List of list of face indices, for each sample gives index to face it is on + vector > sample_faces; sample_faces.resize(k); + // List of list of barycentric coordinates, for each sample gives b-coords in + // face its on + vector > sample_barys; sample_barys.resize(k); + // List of current maxmins amongst samples + vector cur_maxmin; cur_maxmin.resize(k); + // List of distance matrices, D(i)(s,j) reveals distance from i's sth sample + // to jth seed if j D; D.resize(k); + + // Precompute an W.cols() by W.cols() identity matrix + MatrixXd I(MatrixXd::Identity(W.cols(),W.cols())); + + // Describe each seed as a face index and barycentric coordinates + for(int i = 0;i < k;i++) + { + // Unreferenced vertex? + assert(VF[S(i)].size() > 0); + sample_faces[i].push_back(VF[S(i)][0]); + // We're right on a face vertex so barycentric coordinates are 0, but 1 at + // that vertex + Eigen::Vector3d bary(0,0,0); + bary( VFi[S(i)][0] ) = 1; + sample_barys[i].push_back(bary); + // initialize this to current maxmin + cur_maxmin[i] = 0; + } + + // initialize radius + double radius = 1.0; + // minimum radius (bound on precision) + //double min_radius = 1e-5; + double min_radius = 1e-5; + int max_num_rand_samples_per_triangle = 100; + int max_sample_attempts_per_triangle = 1000; + // Max number of outer iterations for a given radius + int max_iters = 1000; + + // continue iterating until radius is smaller than some threshold + while(radius > min_radius) + { + // initialize each seed + for(int i = 0;i < k;i++) + { + // Keep track of cur_maxmin data + int face_i = sample_faces[i][cur_maxmin[i]]; + Eigen::Vector3d bary(sample_barys[i][cur_maxmin[i]]); + // Find index in face of closest mesh vertex (on this face) + int index_in_face = + (bary(0) > bary(1) ? (bary(0) > bary(2) ? 0 : 2) + : (bary(1) > bary(2) ? 1 : 2)); + // find closest mesh vertex + int vertex_i = F(face_i,index_in_face); + // incident triangles + vector incident_F = VF[vertex_i]; + // We're going to try to place num_rand_samples_per_triangle samples on + // each sample *after* this location + sample_barys[i].clear(); + sample_faces[i].clear(); + cur_maxmin[i] = 0; + sample_barys[i].push_back(bary); + sample_faces[i].push_back(face_i); + // Current seed location in weight space + VectorXd seed = + bary(0)*W.row(F(face_i,0)) + + bary(1)*W.row(F(face_i,1)) + + bary(2)*W.row(F(face_i,2)); +#ifdef EXTREME_VERBOSE + verbose("i: %d\n",i); + verbose("face_i: %d\n",face_i); + //cout<<"bary: "<1) + { + u = 1-rv; + v = 1-ru; + }else + { + u = ru; + v = rv; + } + Eigen::Vector3d sample_bary(u,v,1-u-v); + double d = bary_dist(W,F,face_i,bary,face_f,sample_bary); + // check that sample is close enough + if(d= max_num_rand_samples_per_triangle) + { +#ifdef EXTREME_VERBOSE + verbose("Reached maximum number of samples per face\n"); +#endif + break; + } + if(s==(max_sample_attempts_per_triangle-1)) + { +#ifdef EXTREME_VERBOSE + verbose("Reached maximum sample attempts per triangle\n"); +#endif + } + } +#ifdef EXTREME_VERBOSE + verbose("sample_faces[%d].size(): %d\n",i,sample_faces[i].size()); + verbose("sample_barys[%d].size(): %d\n",i,sample_barys[i].size()); +#endif + } + } + + // Precompute distances from each seed's random samples to each "pushed" + // corner + // Put -1 in entries corresponding distance of a seed's random samples to + // self + // Loop over seeds + for(int i = 0;i < k;i++) + { + // resize distance matrix for new samples + D[i].resize(sample_faces[i].size(),k+W.cols()); + // Loop over i's samples + for(int s = 0;s<(int)sample_faces[i].size();s++) + { + int sample_face = sample_faces[i][s]; + Eigen::Vector3d sample_bary = sample_barys[i][s]; + // Loop over other seeds + for(int j = 0;j < k;j++) + { + // distance from sample(i,s) to seed j + double d; + if(i==j) + { + // phony self distance: Ilya's idea of infinite + d = 10; + }else + { + int seed_j_face = sample_faces[j][cur_maxmin[j]]; + Eigen::Vector3d seed_j_bary(sample_barys[j][cur_maxmin[j]]); + d = bary_dist(W,F,sample_face,sample_bary,seed_j_face,seed_j_bary); + } + D[i](s,j) = d; + } + // Loop over corners + for(int j = 0;j < W.cols();j++) + { + // distance from sample(i,s) to corner j + double d = + ((sample_bary(0)*W.row(F(sample_face,0)) + + sample_bary(1)*W.row(F(sample_face,1)) + + sample_bary(2)*W.row(F(sample_face,2))) + - I.row(j)).norm()/push; + // append after distances to seeds + D[i](s,k+j) = d; + } + } + } + + int iters = 0; + while(true) + { + bool has_changed = false; + // try to move each seed + for(int i = 0;i < k;i++) + { + // for each sample look at distance to closest seed/corner + VectorXd minD = D[i].rowwise().minCoeff(); + assert(minD.size() == (int)sample_faces[i].size()); + // find random sample with maximum minimum distance to other seeds + int old_cur_maxmin = cur_maxmin[i]; + double max_min = -2; + for(int s = 0;s<(int)sample_faces[i].size();s++) + { + if(max_min < minD(s)) + { + max_min = minD(s); + // Set this as the new seed location + cur_maxmin[i] = s; + } + } +#ifdef EXTREME_VERBOSE + verbose("max_min: %g\n",max_min); + verbose("cur_maxmin[%d]: %d->%d\n",i,old_cur_maxmin,cur_maxmin[i]); +#endif + // did location change? + has_changed |= (old_cur_maxmin!=cur_maxmin[i]); + // update distances of random samples of other seeds + } + // if no seed moved, exit + if(!has_changed) + { + break; + } + iters++; + if(iters>=max_iters) + { + verbose("Hit max iters (%d) before converging\n",iters); + } + } + // shrink radius + //radius *= 0.9; + //radius *= 0.99; + radius *= 0.9; + } + // Collect weight space locations + WS.resize(k,W.cols()); + for(int i = 0;i ignore; + partition(W,k+W.cols(),G,S,ignore); + // Remove corners, which better be at top + S = S.segment(W.cols(),k).eval(); + + MatrixXd WS; + slice(W,S,colon(0,W.cols()-1),WS); + //cout<<"WSpartition=["< +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNIFORMLY_SAMPLE_TWO_MANIFOLD_H +#define IGL_UNIFORMLY_SAMPLE_TWO_MANIFOLD_H +#include "igl_inline.h" +#include +namespace igl +{ + // UNIFORMLY_SAMPLE_TWO_MANIFOLD Attempt to sample a mesh uniformly by + // furthest point relaxation as described in "Fast Automatic Skinning + // Transformations" + // + // [Jacobson et al. 12] Section 3.3. + // + // Inputs: + // W #W by dim positions of mesh in weight space + // F #F by 3 indices of triangles + // k number of samplse + // push factor by which corners should be pushed away + // Outputs + // WS k by dim locations in weights space + // + IGL_INLINE void uniformly_sample_two_manifold( + const Eigen::MatrixXd & W, + const Eigen::MatrixXi & F, + const int k, + const double push, + Eigen::MatrixXd & WS); + // Find uniform sampling up to placing samples on mesh vertices + IGL_INLINE void uniformly_sample_two_manifold_at_vertices( + const Eigen::MatrixXd & OW, + const int k, + const double push, + Eigen::VectorXi & S); +} +#ifndef IGL_STATIC_LIBRARY +# include "uniformly_sample_two_manifold.cpp" +#endif +#endif diff --git a/src/igl/unique.cpp b/src/igl/unique.cpp new file mode 100644 index 000000000..9c5c7cb8d --- /dev/null +++ b/src/igl/unique.cpp @@ -0,0 +1,222 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unique.h" +#include "sort.h" +#include "IndexComparison.h" +#include "SortableRow.h" +#include "sortrows.h" +#include "list_to_matrix.h" +#include "matrix_to_list.h" + +#include +#include +#include + +template +IGL_INLINE void igl::unique( + const std::vector & A, + std::vector & C, + std::vector & IA, + std::vector & IC) +{ + using namespace std; + std::vector IM; + std::vector sortA; + igl::sort(A,true,sortA,IM); + // Original unsorted index map + IA.resize(sortA.size()); + for(int i=0;i<(int)sortA.size();i++) + { + IA[i] = i; + } + IA.erase( + std::unique( + IA.begin(), + IA.end(), + igl::IndexEquals& >(sortA)),IA.end()); + + IC.resize(A.size()); + { + int j = 0; + for(int i = 0;i<(int)sortA.size();i++) + { + if(sortA[IA[j]] != sortA[i]) + { + j++; + } + IC[IM[i]] = j; + } + } + C.resize(IA.size()); + // Reindex IA according to IM + for(int i = 0;i<(int)IA.size();i++) + { + IA[i] = IM[IA[i]]; + C[i] = A[IA[i]]; + } + +} + +template +IGL_INLINE void igl::unique( + const std::vector & A, + std::vector & C) +{ + std::vector IA,IC; + return igl::unique(A,C,IA,IC); +} + +template < + typename DerivedA, + typename DerivedC, + typename DerivedIA, + typename DerivedIC> +IGL_INLINE void igl::unique( + const Eigen::DenseBase & A, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IC) +{ + using namespace std; + using namespace Eigen; + vector vA; + vector vC; + vector vIA,vIC; + matrix_to_list(A,vA); + unique(vA,vC,vIA,vIC); + list_to_matrix(vC,C); + list_to_matrix(vIA,IA); + list_to_matrix(vIC,IC); +} + +template < + typename DerivedA, + typename DerivedC + > +IGL_INLINE void igl::unique( + const Eigen::DenseBase & A, + Eigen::PlainObjectBase & C) +{ + using namespace std; + using namespace Eigen; + vector vA; + vector vC; + vector vIA,vIC; + matrix_to_list(A,vA); + unique(vA,vC,vIA,vIC); + list_to_matrix(vC,C); +} + +// Obsolete slow version converting to vectors +// template +// IGL_INLINE void igl::unique_rows( +// const Eigen::PlainObjectBase& A, +// Eigen::PlainObjectBase& C, +// Eigen::PlainObjectBase& IA, +// Eigen::PlainObjectBase& IC) +// { +// using namespace std; +// +// typedef Eigen::Matrix RowVector; +// vector > rows; +// rows.resize(A.rows()); +// // Loop over rows +// for(int i = 0;i(ri); +// } +// vector > vC; +// +// // unique on rows +// vector vIA; +// vector vIC; +// unique(rows,vC,vIA,vIC); +// +// // Convert to eigen +// C.resize(vC.size(),A.cols()); +// IA.resize(vIA.size(),1); +// IC.resize(vIC.size(),1); +// for(int i = 0;i +// IGL_INLINE void igl::unique_rows_many( +// const Eigen::PlainObjectBase& A, +// Eigen::PlainObjectBase& C, +// Eigen::PlainObjectBase& IA, +// Eigen::PlainObjectBase& IC) +// { +// using namespace std; +// // frequency map +// typedef Eigen::Matrix RowVector; +// IC.resize(A.rows(),1); +// map, int> fm; +// const int m = A.rows(); +// for(int i = 0;i(ri)) == 0) +// { +// fm[SortableRow(ri)] = i; +// } +// IC(i) = fm[SortableRow(ri)]; +// } +// IA.resize(fm.size(),1); +// Eigen::VectorXi RIA(m); +// C.resize(fm.size(),A.cols()); +// { +// int i = 0; +// for(typename map , int >::const_iterator fit = fm.begin(); +// fit != fm.end(); +// fit++) +// { +// IA(i) = fit->second; +// RIA(fit->second) = i; +// C.row(i) = fit->first.data; +// i++; +// } +// } +// // IC should index C +// for(int i = 0;i, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique(std::vector > const&, std::vector >&); +template void igl::unique(std::vector > const&, std::vector >&); +template void igl::unique(std::vector > const&, std::vector >&, std::vector >&, std::vector >&); +template void igl::unique(std::vector > const&, std::vector >&, std::vector >&, std::vector >&); +#ifdef WIN32 +template void igl::unique,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/unique.h b/src/igl/unique.h new file mode 100644 index 000000000..8618da15e --- /dev/null +++ b/src/igl/unique.h @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNIQUE_H +#define IGL_UNIQUE_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + // Act like matlab's [C,IA,IC] = unique(X) + // + // Templates: + // T comparable type T + // Inputs: + // A #A vector of type T + // Outputs: + // C #C vector of unique entries in A + // IA #C index vector so that C = A(IA); + // IC #A index vector so that A = C(IC); + template + IGL_INLINE void unique( + const std::vector & A, + std::vector & C, + std::vector & IA, + std::vector & IC); + template + IGL_INLINE void unique( + const std::vector & A, + std::vector & C); + template < + typename DerivedA, + typename DerivedC, + typename DerivedIA, + typename DerivedIC> + IGL_INLINE void unique( + const Eigen::DenseBase & A, + Eigen::PlainObjectBase & C, + Eigen::PlainObjectBase & IA, + Eigen::PlainObjectBase & IC); + template < + typename DerivedA, + typename DerivedC> + IGL_INLINE void unique( + const Eigen::DenseBase & A, + Eigen::PlainObjectBase & C); +} + +#ifndef IGL_STATIC_LIBRARY +# include "unique.cpp" +#endif + +#endif diff --git a/src/igl/unique_edge_map.cpp b/src/igl/unique_edge_map.cpp new file mode 100644 index 000000000..a69be58a0 --- /dev/null +++ b/src/igl/unique_edge_map.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unique_edge_map.h" +#include "oriented_facets.h" +#include "unique_simplices.h" +#include +#include +template < + typename DerivedF, + typename DerivedE, + typename DeriveduE, + typename DerivedEMAP, + typename uE2EType> +IGL_INLINE void igl::unique_edge_map( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & uE, + Eigen::PlainObjectBase & EMAP, + std::vector > & uE2E) +{ + using namespace Eigen; + using namespace std; + // All occurrences of directed edges + oriented_facets(F,E); + const size_t ne = E.rows(); + // This is 2x faster to create than a map from pairs to lists of edges and 5x + // faster to access (actually access is probably assympotically faster O(1) + // vs. O(log m) + Matrix IA; + unique_simplices(E,uE,IA,EMAP); + uE2E.resize(uE.rows()); + // This does help a little + for_each(uE2E.begin(),uE2E.end(),[](vector & v){v.reserve(2);}); + assert((size_t)EMAP.size() == ne); + for(uE2EType e = 0;e<(uE2EType)ne;e++) + { + uE2E[EMAP(e)].push_back(e); + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); +template void igl::unique_edge_map, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, unsigned long>(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, std::vector >, std::allocator > > >&); + +#ifdef WIN32 +template void igl::unique_edge_map, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, __int64>(class Eigen::MatrixBase > const &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &, class std::vector >, class std::allocator > > > &); +template void igl::unique_edge_map,class Eigen::Matrix,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,__int64>(class Eigen::MatrixBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class std::vector >,class std::allocator > > > &); +template void igl::unique_edge_map, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64>(class Eigen::MatrixBase> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class std::vector>, class std::allocator>>> &); +template void igl::unique_edge_map, class Eigen::Matrix, class Eigen::Matrix, class Eigen::Matrix, unsigned __int64>(class Eigen::MatrixBase> const &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class Eigen::PlainObjectBase> &, class std::vector>, class std::allocator>>> &); +#endif + +#endif diff --git a/src/igl/unique_edge_map.h b/src/igl/unique_edge_map.h new file mode 100644 index 000000000..4915db66f --- /dev/null +++ b/src/igl/unique_edge_map.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNIQUE_EDGE_MAP_H +#define IGL_UNIQUE_EDGE_MAP_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Construct relationships between facet "half"-(or rather "viewed")-edges E + // to unique edges of the mesh seen as a graph. + // + // Inputs: + // F #F by 3 list of simplices + // Outputs: + // E #F*3 by 2 list of all of directed edges + // uE #uE by 2 list of unique undirected edges + // EMAP #F*3 list of indices into uE, mapping each directed edge to unique + // undirected edge + // uE2E #uE list of lists of indices into E of coexisting edges + template < + typename DerivedF, + typename DerivedE, + typename DeriveduE, + typename DerivedEMAP, + typename uE2EType> + IGL_INLINE void unique_edge_map( + const Eigen::MatrixBase & F, + Eigen::PlainObjectBase & E, + Eigen::PlainObjectBase & uE, + Eigen::PlainObjectBase & EMAP, + std::vector > & uE2E); + +} +#ifndef IGL_STATIC_LIBRARY +# include "unique_edge_map.cpp" +#endif + +#endif diff --git a/src/igl/unique_rows.cpp b/src/igl/unique_rows.cpp new file mode 100644 index 000000000..1973a80c2 --- /dev/null +++ b/src/igl/unique_rows.cpp @@ -0,0 +1,111 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unique_rows.h" +#include "sortrows.h" + +#include +#include +#include + + +template +IGL_INLINE void igl::unique_rows( + const Eigen::DenseBase& A, + Eigen::PlainObjectBase& C, + Eigen::PlainObjectBase& IA, + Eigen::PlainObjectBase& IC) +{ + using namespace std; + using namespace Eigen; + VectorXi IM; + DerivedA sortA; + sortrows(A,true,sortA,IM); + + + const int num_rows = sortA.rows(); + const int num_cols = sortA.cols(); + vector vIA(num_rows); + for(int i=0;i, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::DenseBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +template void igl::unique_rows,Eigen::Matrix,Eigen::Matrix,Eigen::Matrix >(Eigen::DenseBase > const&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&,Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::unique_rows, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1> >(class Eigen::DenseBase > const &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &); +template void igl::unique_rows,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::unique_rows,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::unique_rows,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +template void igl::unique_rows,class Eigen::Matrix,class Eigen::Matrix<__int64,-1,1,0,-1,1>,class Eigen::Matrix<__int64,-1,1,0,-1,1> >(class Eigen::DenseBase > const &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &,class Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/unique_rows.h b/src/igl/unique_rows.h new file mode 100644 index 000000000..c612c8b1d --- /dev/null +++ b/src/igl/unique_rows.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2017 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNIQUE_ROWS_H +#define IGL_UNIQUE_ROWS_H +#include "igl_inline.h" + +#include +#include +namespace igl +{ + // Act like matlab's [C,IA,IC] = unique(X,'rows') + // + // Templates: + // DerivedA derived scalar type, e.g. MatrixXi or MatrixXd + // DerivedIA derived integer type, e.g. MatrixXi + // DerivedIC derived integer type, e.g. MatrixXi + // Inputs: + // A m by n matrix whose entries are to unique'd according to rows + // Outputs: + // C #C vector of unique rows in A + // IA #C index vector so that C = A(IA,:); + // IC #A index vector so that A = C(IC,:); + template + IGL_INLINE void unique_rows( + const Eigen::DenseBase& A, + Eigen::PlainObjectBase& C, + Eigen::PlainObjectBase& IA, + Eigen::PlainObjectBase& IC); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "unique_rows.cpp" +#endif + +#endif diff --git a/src/igl/unique_simplices.cpp b/src/igl/unique_simplices.cpp new file mode 100644 index 000000000..463459866 --- /dev/null +++ b/src/igl/unique_simplices.cpp @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unique_simplices.h" +#include "sort.h" +#include "unique_rows.h" +#include "parallel_for.h" + +template < + typename DerivedF, + typename DerivedFF, + typename DerivedIA, + typename DerivedIC> +IGL_INLINE void igl::unique_simplices( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& FF, + Eigen::PlainObjectBase& IA, + Eigen::PlainObjectBase& IC) +{ + using namespace Eigen; + using namespace std; + // Sort each face + MatrixXi sortF, unusedI; + igl::sort(F,2,true,sortF,unusedI); + // Find unique faces + MatrixXi C; + igl::unique_rows(sortF,C,IA,IC); + FF.resize(IA.size(),F.cols()); + const size_t mff = FF.rows(); + parallel_for(mff,[&F,&IA,&FF](size_t & i){FF.row(i) = F.row(IA(i));},1000ul); +} + +template < + typename DerivedF, + typename DerivedFF> +IGL_INLINE void igl::unique_simplices( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& FF) +{ + Eigen::VectorXi IA,IC; + return unique_simplices(F,FF,IA,IC); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::unique_simplices, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::unique_simplices, class Eigen::Matrix, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1>, class Eigen::Matrix<__int64, -1, 1, 0, -1, 1> >(class Eigen::MatrixBase > const &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &, class Eigen::PlainObjectBase > &); +#endif +#endif diff --git a/src/igl/unique_simplices.h b/src/igl/unique_simplices.h new file mode 100644 index 000000000..a7c13849e --- /dev/null +++ b/src/igl/unique_simplices.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNIQUE_SIMPLICES_H +#define IGL_UNIQUE_SIMPLICES_H +#include "igl_inline.h" +#include +namespace igl +{ + // Find *combinatorially* unique simplices in F. **Order independent** + // + // Inputs: + // F #F by simplex-size list of simplices + // Outputs: + // FF #FF by simplex-size list of unique simplices in F + // IA #FF index vector so that FF == sort(F(IA,:),2); + // IC #F index vector so that sort(F,2) == FF(IC,:); + template < + typename DerivedF, + typename DerivedFF, + typename DerivedIA, + typename DerivedIC> + IGL_INLINE void unique_simplices( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& FF, + Eigen::PlainObjectBase& IA, + Eigen::PlainObjectBase& IC); + template < + typename DerivedF, + typename DerivedFF> + IGL_INLINE void unique_simplices( + const Eigen::MatrixBase& F, + Eigen::PlainObjectBase& FF); +} + +#ifndef IGL_STATIC_LIBRARY +# include "unique_simplices.cpp" +#endif + +#endif diff --git a/src/igl/unproject.cpp b/src/igl/unproject.cpp new file mode 100644 index 000000000..702d77fee --- /dev/null +++ b/src/igl/unproject.cpp @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject.h" + +#include +#include + +template < + typename Derivedwin, + typename Derivedmodel, + typename Derivedproj, + typename Derivedviewport, + typename Derivedscene> +IGL_INLINE void igl::unproject( + const Eigen::MatrixBase& win, + const Eigen::MatrixBase& model, + const Eigen::MatrixBase& proj, + const Eigen::MatrixBase& viewport, + Eigen::PlainObjectBase & scene) +{ + if(win.cols() != 3) + { + assert(win.rows() == 3); + // needless transposes + Eigen::Matrix sceneT; + unproject(win.transpose().eval(),model,proj,viewport,sceneT); + scene = sceneT.head(3); + return; + } + assert(win.cols() == 3); + const int n = win.rows(); + scene.resize(n,3); + for(int i = 0;i Inverse = + (proj.template cast() * model.template cast()).inverse(); + + Eigen::Matrix tmp; + tmp << win.row(i).head(3).transpose(), 1; + tmp(0) = (tmp(0) - viewport(0)) / viewport(2); + tmp(1) = (tmp(1) - viewport(1)) / viewport(3); + tmp = tmp.array() * 2.0f - 1.0f; + + Eigen::Matrix obj = Inverse * tmp; + obj /= obj(3); + + scene.row(i).head(3) = obj.head(3); + } +} + +template +IGL_INLINE Eigen::Matrix igl::unproject( + const Eigen::Matrix& win, + const Eigen::Matrix& model, + const Eigen::Matrix& proj, + const Eigen::Matrix& viewport) +{ + Eigen::Matrix scene; + unproject(win,model,proj,viewport,scene); + return scene; +} + +#ifdef IGL_STATIC_LIBRARY +template Eigen::Matrix igl::unproject(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +template Eigen::Matrix igl::unproject(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +template void igl::unproject, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template void igl::unproject, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/unproject.h b/src/igl/unproject.h new file mode 100644 index 000000000..f34e87513 --- /dev/null +++ b/src/igl/unproject.h @@ -0,0 +1,47 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNPROJECT_H +#define IGL_UNPROJECT_H +#include "igl_inline.h" +#include +namespace igl +{ + // Eigen reimplementation of gluUnproject + // + // Inputs: + // win #P by 3 or 3-vector (#P=1) of screen space x, y, and z coordinates + // model 4x4 model-view matrix + // proj 4x4 projection matrix + // viewport 4-long viewport vector + // Outputs: + // scene #P by 3 or 3-vector (#P=1) the unprojected x, y, and z coordinates + template < + typename Derivedwin, + typename Derivedmodel, + typename Derivedproj, + typename Derivedviewport, + typename Derivedscene> + IGL_INLINE void unproject( + const Eigen::MatrixBase& win, + const Eigen::MatrixBase& model, + const Eigen::MatrixBase& proj, + const Eigen::MatrixBase& viewport, + Eigen::PlainObjectBase & scene); + template + IGL_INLINE Eigen::Matrix unproject( + const Eigen::Matrix& win, + const Eigen::Matrix& model, + const Eigen::Matrix& proj, + const Eigen::Matrix& viewport); +} + +#ifndef IGL_STATIC_LIBRARY +# include "unproject.cpp" +#endif + +#endif diff --git a/src/igl/unproject_in_mesh.cpp b/src/igl/unproject_in_mesh.cpp new file mode 100644 index 000000000..6238aaf2a --- /dev/null +++ b/src/igl/unproject_in_mesh.cpp @@ -0,0 +1,97 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_in_mesh.h" +#include "unproject_ray.h" +#include "ray_mesh_intersect.h" + +template < typename Derivedobj> + IGL_INLINE int igl::unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const std::function< + void( + const Eigen::Vector3f&, + const Eigen::Vector3f&, + std::vector &) + > & shoot_ray, + Eigen::PlainObjectBase & obj, + std::vector & hits) +{ + using namespace std; + using namespace Eigen; + Vector3f s,dir; + unproject_ray(pos,model,proj,viewport,s,dir); + shoot_ray(s,dir,hits); + switch(hits.size()) + { + case 0: + break; + case 1: + { + obj = (s + dir*hits[0].t).cast(); + break; + } + case 2: + default: + { + obj = 0.5*((s + dir*hits[0].t) + (s + dir*hits[1].t)).cast(); + break; + } + } + return hits.size(); +} + +extern "C" +{ +#include "raytri.c" +} + +template < typename DerivedV, typename DerivedF, typename Derivedobj> + IGL_INLINE int igl::unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & obj, + std::vector & hits) +{ + using namespace std; + using namespace Eigen; + const auto & shoot_ray = [&V,&F]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir, + std::vector & hits) + { + ray_mesh_intersect(s,dir,V,F,hits); + }; + return unproject_in_mesh(pos,model,proj,viewport,shoot_ray,obj,hits); +} + +template < typename DerivedV, typename DerivedF, typename Derivedobj> + IGL_INLINE int igl::unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & obj) +{ + std::vector hits; + return unproject_in_mesh(pos,model,proj,viewport,V,F,obj,hits); +} +#ifdef IGL_STATIC_LIBRARY +template int igl::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, std::function const&, Eigen::Matrix const&, std::vector >&)> const&, Eigen::PlainObjectBase >&, std::vector >&); +template int igl::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, std::function const&, Eigen::Matrix const&, std::vector >&)> const&, Eigen::PlainObjectBase >&, std::vector >&); +template int igl::unproject_in_mesh >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, std::function const&, Eigen::Matrix const&, std::vector >&)> const&, Eigen::PlainObjectBase >&, std::vector >&); +template int igl::unproject_in_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/unproject_in_mesh.h b/src/igl/unproject_in_mesh.h new file mode 100644 index 000000000..edb1a0e65 --- /dev/null +++ b/src/igl/unproject_in_mesh.h @@ -0,0 +1,88 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNPROJECT_IN_MESH +#define IGL_UNPROJECT_IN_MESH +#include "igl_inline.h" +#include + +#include +#include "Hit.h" + +namespace igl +{ + // Unproject a screen location (using current opengl viewport, projection, and + // model view) to a 3D position _inside_ a given mesh. If the ray through the + // given screen location (x,y) _hits_ the mesh more than twice then the 3D + // midpoint between the first two hits is return. If it hits once, then that + // point is return. If it does not hit the mesh then obj is not set. + // + // Inputs: + // pos screen space coordinates + // model model matrix + // proj projection matrix + // viewport vieweport vector + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh triangle indices into V + // Outputs: + // obj 3d unprojected mouse point in mesh + // hits vector of hits + // Returns number of hits + // + template < typename DerivedV, typename DerivedF, typename Derivedobj> + IGL_INLINE int unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & obj, + std::vector & hits); + // + // Inputs: + // pos screen space coordinates + // model model matrix + // proj projection matrix + // viewport vieweport vector + // shoot_ray function handle that outputs first hit of a given ray + // against a mesh (embedded in function handles as captured + // variable/data) + // Outputs: + // obj 3d unprojected mouse point in mesh + // hits vector of hits + // Returns number of hits + // + template < typename Derivedobj> + IGL_INLINE int unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const std::function< + void( + const Eigen::Vector3f&, + const Eigen::Vector3f&, + std::vector &) + > & shoot_ray, + Eigen::PlainObjectBase & obj, + std::vector & hits); + template < typename DerivedV, typename DerivedF, typename Derivedobj> + IGL_INLINE int unproject_in_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + Eigen::PlainObjectBase & obj); +} +#ifndef IGL_STATIC_LIBRARY +# include "unproject_in_mesh.cpp" +#endif +#endif + diff --git a/src/igl/unproject_onto_mesh.cpp b/src/igl/unproject_onto_mesh.cpp new file mode 100644 index 000000000..897d206c1 --- /dev/null +++ b/src/igl/unproject_onto_mesh.cpp @@ -0,0 +1,78 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_onto_mesh.h" +#include "unproject.h" +#include "unproject_ray.h" +#include "ray_mesh_intersect.h" +#include + +template < typename DerivedV, typename DerivedF, typename Derivedbc> +IGL_INLINE bool igl::unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + int & fid, + Eigen::PlainObjectBase & bc) +{ + using namespace std; + using namespace Eigen; + const auto & shoot_ray = [&V,&F]( + const Eigen::Vector3f& s, + const Eigen::Vector3f& dir, + igl::Hit & hit)->bool + { + std::vector hits; + if(!ray_mesh_intersect(s,dir,V,F,hits)) + { + return false; + } + hit = hits[0]; + return true; + }; + return unproject_onto_mesh(pos,model,proj,viewport,shoot_ray,fid,bc); +} + +template +IGL_INLINE bool igl::unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const std::function< + bool( + const Eigen::Vector3f&, + const Eigen::Vector3f&, + igl::Hit &) + > & shoot_ray, + int & fid, + Eigen::PlainObjectBase & bc) +{ + using namespace std; + using namespace Eigen; + Vector3f s,dir; + unproject_ray(pos,model,proj,viewport,s,dir); + Hit hit; + if(!shoot_ray(s,dir,hit)) + { + return false; + } + bc.resize(3); + bc << 1.0-hit.u-hit.v, hit.u, hit.v; + fid = hit.id; + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::unproject_onto_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, Eigen::PlainObjectBase >&); +template bool igl::unproject_onto_mesh, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, int&, Eigen::PlainObjectBase >&); +#endif + diff --git a/src/igl/unproject_onto_mesh.h b/src/igl/unproject_onto_mesh.h new file mode 100644 index 000000000..57252f99b --- /dev/null +++ b/src/igl/unproject_onto_mesh.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNPROJECT_ONTO_MESH +#define IGL_UNPROJECT_ONTO_MESH +#include "igl_inline.h" +#include "Hit.h" +#include +#include + +namespace igl +{ + // Unproject a screen location (using current opengl viewport, projection, and + // model view) to a 3D position _onto_ a given mesh, if the ray through the + // given screen location (x,y) _hits_ the mesh. + // + // Inputs: + // pos screen space coordinates + // model model matrix + // proj projection matrix + // viewport vieweport vector + // V #V by 3 list of mesh vertex positions + // F #F by 3 list of mesh triangle indices into V + // Outputs: + // fid id of the first face hit + // bc barycentric coordinates of hit + // Returns true if there's a hit + template < typename DerivedV, typename DerivedF, typename Derivedbc> + IGL_INLINE bool unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + int & fid, + Eigen::PlainObjectBase & bc); + // + // Inputs: + // pos screen space coordinates + // model model matrix + // proj projection matrix + // viewport vieweport vector + // shoot_ray function handle that outputs hits of a given ray against a + // mesh (embedded in function handles as captured variable/data) + // Outputs: + // fid id of the first face hit + // bc barycentric coordinates of hit + // Returns true if there's a hit + template + IGL_INLINE bool unproject_onto_mesh( + const Eigen::Vector2f& pos, + const Eigen::Matrix4f& model, + const Eigen::Matrix4f& proj, + const Eigen::Vector4f& viewport, + const std::function< + bool( + const Eigen::Vector3f&, + const Eigen::Vector3f&, + igl::Hit &) + > & shoot_ray, + int & fid, + Eigen::PlainObjectBase & bc); +} +#ifndef IGL_STATIC_LIBRARY +# include "unproject_onto_mesh.cpp" +#endif +#endif + + diff --git a/src/igl/unproject_ray.cpp b/src/igl/unproject_ray.cpp new file mode 100644 index 000000000..f6eb7ebdd --- /dev/null +++ b/src/igl/unproject_ray.cpp @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unproject_ray.h" +#include "unproject.h" + +template < + typename Derivedpos, + typename Derivedmodel, + typename Derivedproj, + typename Derivedviewport, + typename Deriveds, + typename Deriveddir> +IGL_INLINE void igl::unproject_ray( + const Eigen::PlainObjectBase & pos, + const Eigen::PlainObjectBase & model, + const Eigen::PlainObjectBase & proj, + const Eigen::PlainObjectBase & viewport, + Eigen::PlainObjectBase & s, + Eigen::PlainObjectBase & dir) +{ + using namespace std; + using namespace Eigen; + // Source and direction on screen + typedef Eigen::Matrix Vec3; + Vec3 win_s(pos(0),pos(1),0); + Vec3 win_d(pos(0),pos(1),1); + // Source, destination and direction in world + Vec3 d; + igl::unproject(win_s,model,proj,viewport,s); + igl::unproject(win_d,model,proj,viewport,d); + dir = d-s; +} + +#ifdef IGL_STATIC_LIBRARY +template void igl::unproject_ray, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/unproject_ray.h b/src/igl/unproject_ray.h new file mode 100644 index 000000000..4ef66f266 --- /dev/null +++ b/src/igl/unproject_ray.h @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNPROJECT_RAY_H +#define IGL_UNPROJECT_RAY_H +#include "igl_inline.h" +#include +namespace igl +{ + // Construct a ray (source point + direction vector) given a screen space + // positions (e.g. mouse) and a model-view projection constellation. + // + // Inputs: + // pos 2d screen-space position (x,y) + // model 4x4 model-view matrix + // proj 4x4 projection matrix + // viewport 4-long viewport vector + // Outputs: + // s source of ray (pos unprojected with z=0) + /// dir direction of ray (d - s) where d is pos unprojected with z=1 + // + template < + typename Derivedpos, + typename Derivedmodel, + typename Derivedproj, + typename Derivedviewport, + typename Deriveds, + typename Deriveddir> + IGL_INLINE void unproject_ray( + const Eigen::PlainObjectBase & pos, + const Eigen::PlainObjectBase & model, + const Eigen::PlainObjectBase & proj, + const Eigen::PlainObjectBase & viewport, + Eigen::PlainObjectBase & s, + Eigen::PlainObjectBase & dir); +} +#ifndef IGL_STATIC_LIBRARY +# include "unproject_ray.cpp" +#endif +#endif diff --git a/src/igl/unzip_corners.cpp b/src/igl/unzip_corners.cpp new file mode 100644 index 000000000..0a2ed004c --- /dev/null +++ b/src/igl/unzip_corners.cpp @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "unzip_corners.h" + +#include "unique_rows.h" +#include "slice.h" + +template < typename DerivedA, typename DerivedU, typename DerivedG, typename DerivedJ > +IGL_INLINE void igl::unzip_corners( + const std::vector > & A, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J) +{ + if(A.size() == 0) + { + U.resize(0,0); + G.resize(0,3); + J.resize(0,0); + return; + } + const size_t num_a = A.size(); + const typename DerivedA::Index m = A[0].get().rows(); + DerivedU C(m*3,num_a); + for(int a = 0;a +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UNZIP_CORNERS_H +#define IGL_UNZIP_CORNERS_H +#include "igl_inline.h" +#include +#include +#include + +namespace igl +{ + // UNZIP_CORNERS Given a triangle mesh where corners of each triangle index + // different matrices of attributes (e.g. read from an OBJ file), unzip the + // corners into unique efficiently: attributes become properly vertex valued + // (usually creating greater than #V but less than #F*3 vertices). + // + // To pass a list of attributes this function takes an std::vector of + // std::reference_wrapper of an Eigen::... type. This allows you to use list + // initializers **without** incurring a copy, but means you'll need to + // provide the derived type of A as an explicit template parameter: + // + // unzip_corners({F,FTC,FN},U,G,J); + // + // Inputs: + // A #A list of #F by 3 attribute indices, typically {F,FTC,FN} + // Outputs: + // U #U by #A list of indices into each attribute for each unique mesh + // vertex: U(v,a) is the attribute index of vertex v in attribute a. + // G #F by 3 list of triangle indices into U + // Example: + // [V,F,TC,FTC] = readOBJ('~/Downloads/kiwis/kiwi.obj'); + // [U,G] = unzip_corners(cat(3,F,FTC)); + // % display mesh + // tsurf(G,V(U(:,1),:)); + // % display texture coordinates + // tsurf(G,TC(U(:,2),:)); + // + template < typename DerivedA, typename DerivedU, typename DerivedG, typename DerivedJ> + IGL_INLINE void unzip_corners( + const std::vector > & A, + Eigen::PlainObjectBase & U, + Eigen::PlainObjectBase & G, + Eigen::PlainObjectBase & J); +} + +#ifndef IGL_STATIC_LIBRARY +# include "unzip_corners.cpp" +#endif +#endif diff --git a/src/igl/upsample.cpp b/src/igl/upsample.cpp new file mode 100644 index 000000000..8b5e3da24 --- /dev/null +++ b/src/igl/upsample.cpp @@ -0,0 +1,145 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "upsample.h" + +#include "triangle_triangle_adjacency.h" + + +template < + typename DerivedF, + typename SType, + typename DerivedNF> +IGL_INLINE void igl::upsample( + const int n_verts, + const Eigen::PlainObjectBase& F, + Eigen::SparseMatrix& S, + Eigen::PlainObjectBase& NF) +{ + using namespace std; + using namespace Eigen; + + typedef Eigen::Triplet Triplet_t; + + Eigen::Matrix< typename DerivedF::Scalar,Eigen::Dynamic,Eigen::Dynamic> + FF,FFi; + triangle_triangle_adjacency(F,FF,FFi); + + // TODO: Cache optimization missing from here, it is a mess + + // Compute the number and positions of the vertices to insert (on edges) + Eigen::MatrixXi NI = Eigen::MatrixXi::Constant(FF.rows(),FF.cols(),-1); + Eigen::MatrixXi NIdoubles = Eigen::MatrixXi::Zero(FF.rows(), FF.cols()); + int counter = 0; + + for(int i=0;i tripletList; + + // Fill the odd vertices position + for (int i=0; i +IGL_INLINE void igl::upsample( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& NV, + Eigen::PlainObjectBase& NF, + const int number_of_subdivs) +{ + NV = V; + NF = F; + for(int i=0; iS; + upsample(NV.rows(), tempF, S, NF); + // This .eval is super important + NV = (S*NV).eval(); + } +} + +template < + typename MatV, + typename MatF> +IGL_INLINE void igl::upsample( + MatV& V, + MatF& F, + const int number_of_subdivs) +{ + const MatV V_copy = V; + const MatF F_copy = F; + return upsample(V_copy,F_copy,V,F,number_of_subdivs); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::upsample, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&, int); +template void igl::upsample, double, Eigen::Matrix >(int, Eigen::PlainObjectBase > const&, Eigen::SparseMatrix&, Eigen::PlainObjectBase >&); +template void igl::upsample, Eigen::Matrix >(Eigen::Matrix&, Eigen::Matrix&, int); +#endif diff --git a/src/igl/upsample.h b/src/igl/upsample.h new file mode 100644 index 000000000..51608aef6 --- /dev/null +++ b/src/igl/upsample.h @@ -0,0 +1,82 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_UPSAMPLE_H +#define IGL_UPSAMPLE_H +#include "igl_inline.h" + +#include +#include + +// History: +// changed templates from generic matrices to PlainObjectBase Alec May 7, 2011 +namespace igl +{ + // Subdivide without moving vertices: Given the triangle mesh [V, F], + // where n_verts = V.rows(), computes newV and a sparse matrix S s.t. + // [newV, newF] is the subdivided mesh where newV = S*V. + // + // Inputs: + // n_verts an integer (number of mesh vertices) + // F an m by 3 matrix of integers of triangle faces + // Outputs: + // S a sparse matrix (will become the subdivision matrix) + // newF a matrix containing the new faces + template < + typename DerivedF, + typename SType, + typename DerivedNF> + IGL_INLINE void upsample( + const int n_verts, + const Eigen::PlainObjectBase& F, + Eigen::SparseMatrix& S, + Eigen::PlainObjectBase& NF); + // Subdivide a mesh without moving vertices: loop subdivision but odd + // vertices stay put and even vertices are just edge midpoints + // + // Templates: + // MatV matrix for vertex positions, e.g. MatrixXd + // MatF matrix for vertex positions, e.g. MatrixXi + // Inputs: + // V #V by dim mesh vertices + // F #F by 3 mesh triangles + // Outputs: + // NV new vertex positions, V is guaranteed to be at top + // NF new list of face indices + // + // NOTE: V should not be the same as NV, + // NOTE: F should not be the same as NF, use other proto + // + // Known issues: + // - assumes (V,F) is edge-manifold. + template < + typename DerivedV, + typename DerivedF, + typename DerivedNV, + typename DerivedNF> + IGL_INLINE void upsample( + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + Eigen::PlainObjectBase& NV, + Eigen::PlainObjectBase& NF, + const int number_of_subdivs = 1); + + // Virtually in place wrapper + template < + typename MatV, + typename MatF> + IGL_INLINE void upsample( + MatV& V, + MatF& F, + const int number_of_subdivs = 1); +} + +#ifndef IGL_STATIC_LIBRARY +# include "upsample.cpp" +#endif + +#endif diff --git a/src/igl/vector_area_matrix.cpp b/src/igl/vector_area_matrix.cpp new file mode 100644 index 000000000..6464c14a7 --- /dev/null +++ b/src/igl/vector_area_matrix.cpp @@ -0,0 +1,54 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "vector_area_matrix.h" +#include + +// Bug in unsupported/Eigen/SparseExtra needs iostream first +#include +#include + +//#include +#include + +template +IGL_INLINE void igl::vector_area_matrix( + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix& A) +{ + using namespace Eigen; + using namespace std; + + // number of vertices + const int n = F.maxCoeff()+1; + + MatrixXi E; + boundary_facets(F,E); + + //Prepare a vector of triplets to set the matrix + vector > tripletList; + tripletList.reserve(4*E.rows()); + + for(int k = 0; k < E.rows(); k++) + { + int i = E(k,0); + int j = E(k,1); + tripletList.push_back(Triplet(i+n, j, -0.25)); + tripletList.push_back(Triplet(j, i+n, -0.25)); + tripletList.push_back(Triplet(i, j+n, 0.25)); + tripletList.push_back(Triplet(j+n, i, 0.25)); + } + + //Set A from triplets (Eigen will sum triplets with same coordinates) + A.resize(n * 2, n * 2); + A.setFromTriplets(tripletList.begin(), tripletList.end()); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::vector_area_matrix, double>(Eigen::PlainObjectBase > const&, Eigen::SparseMatrix&); +#endif diff --git a/src/igl/vector_area_matrix.h b/src/igl/vector_area_matrix.h new file mode 100644 index 000000000..6d385329c --- /dev/null +++ b/src/igl/vector_area_matrix.h @@ -0,0 +1,41 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VECTOR_AREA_MATRIX_H +#define IGL_VECTOR_AREA_MATRIX_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // Constructs the symmetric area matrix A, s.t. [V.col(0)' V.col(1)'] * A * + // [V.col(0); V.col(1)] is the **vector area** of the mesh (V,F). + // + // Templates: + // DerivedV derived type of eigen matrix for V (e.g. derived from + // MatrixXd) + // DerivedF derived type of eigen matrix for F (e.g. derived from + // MatrixXi) + // Scalar scalar type for eigen sparse matrix (e.g. double) + // Inputs: + // F #F by 3 list of mesh faces (must be triangles) + // Outputs: + // A #Vx2 by #Vx2 area matrix + // + template + IGL_INLINE void vector_area_matrix( + const Eigen::PlainObjectBase & F, + Eigen::SparseMatrix& A); +} + +#ifndef IGL_STATIC_LIBRARY +# include "vector_area_matrix.cpp" +#endif + +#endif diff --git a/src/igl/verbose.h b/src/igl/verbose.h new file mode 100644 index 000000000..e4aa86958 --- /dev/null +++ b/src/igl/verbose.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VERBOSE_H +#define IGL_VERBOSE_H + +// This function is only useful as a header-only inlined function + +namespace igl +{ + // Provide a wrapper for printf, called verbose that functions exactly like + // printf if VERBOSE is defined and does exactly nothing if VERBOSE is + // undefined + inline int verbose(const char * msg,...); +} + + + +#include +#ifdef VERBOSE +# include +#endif + +#include +// http://channel9.msdn.com/forums/techoff/254707-wrapping-printf-in-c/ +#ifdef VERBOSE +inline int igl::verbose(const char * msg,...) +{ + va_list argList; + va_start(argList, msg); + int count = vprintf(msg, argList); + va_end(argList); + return count; +} +#else +inline int igl::verbose(const char * /*msg*/,...) +{ + return 0; +} +#endif + +#endif diff --git a/src/igl/vertex_triangle_adjacency.cpp b/src/igl/vertex_triangle_adjacency.cpp new file mode 100644 index 000000000..352d06244 --- /dev/null +++ b/src/igl/vertex_triangle_adjacency.cpp @@ -0,0 +1,102 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "vertex_triangle_adjacency.h" +#include "cumsum.h" + +template +IGL_INLINE void igl::vertex_triangle_adjacency( + const typename DerivedF::Scalar n, + const Eigen::MatrixBase& F, + std::vector >& VF, + std::vector >& VFi) +{ + VF.clear(); + VFi.clear(); + + VF.resize(n); + VFi.resize(n); + + typedef typename DerivedF::Index Index; + for(Index fi=0; fi +IGL_INLINE void igl::vertex_triangle_adjacency( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + std::vector >& VF, + std::vector >& VFi) +{ + return vertex_triangle_adjacency(V.rows(),F,VF,VFi); +} + +template < + typename DerivedF, + typename DerivedVF, + typename DerivedNI> +IGL_INLINE void igl::vertex_triangle_adjacency( + const Eigen::MatrixBase & F, + const int n, + Eigen::PlainObjectBase & VF, + Eigen::PlainObjectBase & NI) +{ + typedef Eigen::Matrix VectorXI; + // vfd #V list so that vfd(i) contains the vertex-face degree (number of + // faces incident on vertex i) + VectorXI vfd = VectorXI::Zero(n); + for (int i = 0; i < F.rows(); i++) + { + for (int j = 0; j < 3; j++) + { + vfd[F(i,j)]++; + } + } + igl::cumsum(vfd,1,NI); + // Prepend a zero + NI = (DerivedNI(n+1)<<0,NI).finished(); + // vfd now acts as a counter + vfd = NI; + + VF.derived()= Eigen::VectorXi(3*F.rows()); + for (int i = 0; i < F.rows(); i++) + { + for (int j = 0; j < 3; j++) + { + VF[vfd[F(i,j)]] = i; + vfd[F(i,j)]++; + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::vertex_triangle_adjacency, unsigned long, unsigned long>(Eigen::Matrix::Scalar, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +// generated by autoexplicit.sh +template void igl::vertex_triangle_adjacency, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, Eigen::Matrix, unsigned int>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, long, long>(Eigen::Matrix::Scalar, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, long, long>(Eigen::Matrix::Scalar, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, unsigned long, unsigned long>(Eigen::Matrix::Scalar, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, Eigen::Matrix, int>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >, std::allocator > > >&, std::vector >, std::allocator > > >&); +template void igl::vertex_triangle_adjacency, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::vertex_triangle_adjacency, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#ifdef WIN32 +template void igl::vertex_triangle_adjacency, unsigned __int64, unsigned __int64>(int, class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>> &, class std::vector>, class std::allocator>>> &); +template void igl::vertex_triangle_adjacency, unsigned __int64, unsigned __int64>(int, class Eigen::MatrixBase> const &, class std::vector>, class std::allocator>>> &, class std::vector>, class std::allocator>>> &); +#endif +#endif diff --git a/src/igl/vertex_triangle_adjacency.h b/src/igl/vertex_triangle_adjacency.h new file mode 100644 index 000000000..ce67b1f3d --- /dev/null +++ b/src/igl/vertex_triangle_adjacency.h @@ -0,0 +1,71 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Daniele Panozzo +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VERTEX_TRIANGLE_ADJACENCY_H +#define IGL_VERTEX_TRIANGLE_ADJACENCY_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) + // + // Inputs: + // //V #V by 3 list of vertex coordinates + // n number of vertices #V (e.g. `F.maxCoeff()+1` or `V.rows()`) + // F #F by dim list of mesh faces (must be triangles) + // Outputs: + // VF #V list of lists of incident faces (adjacency list) + // VI #V list of lists of index of incidence within incident faces listed + // in VF + // + // See also: edges, cotmatrix, diag, vv + // + // Known bugs: this should not take V as an input parameter. + // Known bugs/features: if a facet is combinatorially degenerate then faces + // will appear multiple times in VF and correspondingly in VFI (j appears + // twice in F.row(i) then i will appear twice in VF[j]) + template + IGL_INLINE void vertex_triangle_adjacency( + const typename DerivedF::Scalar n, + const Eigen::MatrixBase& F, + std::vector >& VF, + std::vector >& VFi); + template + IGL_INLINE void vertex_triangle_adjacency( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + std::vector >& VF, + std::vector >& VFi); + // Inputs: + // F #F by 3 list of triangle indices into some vertex list V + // n number of vertices, #V (e.g., F.maxCoeff()+1) + // Outputs: + // VF 3*#F list List of faces indice on each vertex, so that VF(NI(i)+j) = + // f, means that face f is the jth face (in no particular order) incident + // on vertex i. + // NI #V+1 list cumulative sum of vertex-triangle degrees with a + // preceeding zero. "How many faces" have been seen before visiting this + // vertex and its incident faces. + template < + typename DerivedF, + typename DerivedVF, + typename DerivedNI> + IGL_INLINE void vertex_triangle_adjacency( + const Eigen::MatrixBase & F, + const int n, + Eigen::PlainObjectBase & VF, + Eigen::PlainObjectBase & NI); +} + +#ifndef IGL_STATIC_LIBRARY +# include "vertex_triangle_adjacency.cpp" +#endif + +#endif diff --git a/src/igl/volume.cpp b/src/igl/volume.cpp new file mode 100644 index 000000000..68568cf45 --- /dev/null +++ b/src/igl/volume.cpp @@ -0,0 +1,120 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "volume.h" +#include "cross.h" +#include +template < + typename DerivedV, + typename DerivedT, + typename Derivedvol> +IGL_INLINE void igl::volume( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& vol) +{ + using namespace Eigen; + const int m = T.rows(); + vol.resize(m,1); + for(int t = 0;t RowVector3S; + const RowVector3S & a = V.row(T(t,0)); + const RowVector3S & b = V.row(T(t,1)); + const RowVector3S & c = V.row(T(t,2)); + const RowVector3S & d = V.row(T(t,3)); + vol(t) = -(a-d).dot((b-d).cross(c-d))/6.; + } +} + +template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedD, + typename Derivedvol> +IGL_INLINE void igl::volume( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & vol) +{ + const auto & AmD = A-D; + const auto & BmD = B-D; + const auto & CmD = C-D; + DerivedA BmDxCmD; + cross(BmD.eval(),CmD.eval(),BmDxCmD); + const auto & AmDdx = (AmD.array() * BmDxCmD.array()).rowwise().sum(); + vol = -AmDdx/6.; +} + +template < + typename VecA, + typename VecB, + typename VecC, + typename VecD> +IGL_INLINE typename VecA::Scalar igl::volume_single( + const VecA & a, + const VecB & b, + const VecC & c, + const VecD & d) +{ + return -(a-d).dot((b-d).cross(c-d))/6.; +} + + +template < + typename DerivedL, + typename Derivedvol> +IGL_INLINE void igl::volume( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase& vol) +{ + using namespace Eigen; + const int m = L.rows(); + typedef typename Derivedvol::Scalar ScalarS; + vol.resize(m,1); + for(int t = 0;t, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::volume, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::volume, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template void igl::volume, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +template Eigen::Matrix::Scalar igl::volume_single, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&, Eigen::Matrix const&); +template void igl::volume,Eigen::Matrix,Eigen::Matrix >(Eigen::MatrixBase > const &,Eigen::MatrixBase > const &,Eigen::PlainObjectBase > &); +#endif diff --git a/src/igl/volume.h b/src/igl/volume.h new file mode 100644 index 000000000..2dee7cc95 --- /dev/null +++ b/src/igl/volume.h @@ -0,0 +1,74 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VOLUME_H +#define IGL_VOLUME_H +#include "igl_inline.h" +#include +namespace igl +{ + // VOLUME Compute volume for all tets of a given tet mesh + // (V,T) + // + // vol = volume(V,T) + // + // Inputs: + // V #V by dim list of vertex positions + // T #V by 4 list of tet indices + // Outputs: + // vol #T list of dihedral angles (in radians) + // + template < + typename DerivedV, + typename DerivedT, + typename Derivedvol> + IGL_INLINE void volume( + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& T, + Eigen::PlainObjectBase& vol); + template < + typename DerivedA, + typename DerivedB, + typename DerivedC, + typename DerivedD, + typename Derivedvol> + IGL_INLINE void volume( + const Eigen::MatrixBase & A, + const Eigen::MatrixBase & B, + const Eigen::MatrixBase & C, + const Eigen::MatrixBase & D, + Eigen::PlainObjectBase & vol); + // Single tet + template < + typename VecA, + typename VecB, + typename VecC, + typename VecD> + IGL_INLINE typename VecA::Scalar volume_single( + const VecA & a, + const VecB & b, + const VecC & c, + const VecD & d); + // Intrinsic version: + // + // Inputs: + // L #V by 6 list of edge lengths (see edge_lengths) + template < + typename DerivedL, + typename Derivedvol> + IGL_INLINE void volume( + const Eigen::MatrixBase& L, + Eigen::PlainObjectBase& vol); +} + +#ifndef IGL_STATIC_LIBRARY +# include "volume.cpp" +#endif + +#endif + + diff --git a/src/igl/voxel_grid.cpp b/src/igl/voxel_grid.cpp new file mode 100644 index 000000000..388e58af4 --- /dev/null +++ b/src/igl/voxel_grid.cpp @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "voxel_grid.h" +#include "grid.h" + +template < + typename Scalar, + typename DerivedGV, + typename Derivedside> +IGL_INLINE void igl::voxel_grid( + const Eigen::AlignedBox & box, + const int in_s, + const int pad_count, + Eigen::PlainObjectBase & GV, + Eigen::PlainObjectBase & side) +{ + using namespace Eigen; + using namespace std; + typename DerivedGV::Index si = -1; + box.diagonal().maxCoeff(&si); + //DerivedGV::Index si = 0; + //assert(si>=0); + const Scalar s_len = box.diagonal()(si); + assert(in_s>(pad_count*2+1) && "s should be > 2*pad_count+1"); + const Scalar s = in_s - 2*pad_count; + side(si) = s; + for(int i = 0;i<3;i++) + { + if(i!=si) + { + side(i) = std::ceil(s * (box.max()(i)-box.min()(i))/s_len); + } + } + side.array() += 2*pad_count; + grid(side,GV); + // A * p/s + B = min + // A * (1-p/s) + B = max + // B = min - A * p/s + // A * (1-p/s) + min - A * p/s = max + // A * (1-p/s) - A * p/s = max-min + // A * (1-2p/s) = max-min + // A = (max-min)/(1-2p/s) + const Array ps= + (Scalar)(pad_count)/(side.transpose().template cast().array()-1.); + const Array A = box.diagonal().array()/(1.0-2.*ps); + //// This would result in an "anamorphic", but perfectly fit grid: + //const Array B = box.min().array() - A.array()*ps; + //GV.array().rowwise() *= A.transpose(); + //GV.array().rowwise() += B.transpose(); + // Instead scale by largest factor and move to match center + typename Array::Index ai = -1; + Scalar a = A.maxCoeff(&ai); + const Array ratio = + a*(side.template cast().array()-1.0)/(Scalar)(side(ai)-1.0); + GV.array().rowwise() *= ratio; + const Eigen::Matrix offset = (box.center().transpose()-GV.colwise().mean()).eval(); + GV.rowwise() += offset; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template void igl::voxel_grid, Eigen::Matrix >(Eigen::AlignedBox const&, int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::voxel_grid, Eigen::Matrix >(Eigen::AlignedBox const&, int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::voxel_grid, Eigen::Matrix >(Eigen::AlignedBox const&, int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::voxel_grid, Eigen::Matrix >(Eigen::AlignedBox const&, int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +template void igl::voxel_grid, Eigen::Matrix >(Eigen::AlignedBox const&, int, int, Eigen::PlainObjectBase >&, Eigen::PlainObjectBase >&); +#endif diff --git a/src/igl/voxel_grid.h b/src/igl/voxel_grid.h new file mode 100644 index 000000000..4f8ac1bcb --- /dev/null +++ b/src/igl/voxel_grid.h @@ -0,0 +1,39 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_VOXEL_GRID_H +#define IGL_VOXEL_GRID_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Construct the cell center positions of a regular voxel grid (lattice) made + // of perfectly square voxels. + // + // Inputs: + // box bounding box to enclose by grid + // s number of cell centers on largest side (including 2*pad_count) + // pad_count number of cells beyond box + // Outputs: + // GV side(0)*side(1)*side(2) by 3 list of cell center positions + // side 3-long list of dimension of voxel grid + template < + typename Scalar, + typename DerivedGV, + typename Derivedside> + IGL_INLINE void voxel_grid( + const Eigen::AlignedBox & box, + const int s, + const int pad_count, + Eigen::PlainObjectBase & GV, + Eigen::PlainObjectBase & side); +} +#ifndef IGL_STATIC_LIBRARY +# include "voxel_grid.cpp" +#endif +#endif diff --git a/src/igl/winding_number.cpp b/src/igl/winding_number.cpp new file mode 100644 index 000000000..d6af18d98 --- /dev/null +++ b/src/igl/winding_number.cpp @@ -0,0 +1,117 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "winding_number.h" +#include "WindingNumberAABB.h" +#include "signed_angle.h" +#include "parallel_for.h" +#include "solid_angle.h" +#include "PI.h" + +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedO, + typename DerivedW> +IGL_INLINE void igl::winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & O, + Eigen::PlainObjectBase & W) +{ + using namespace Eigen; + // make room for output + W.resize(O.rows(),1); + switch(F.cols()) + { + case 2: + { + igl::parallel_for(O.rows(),[&](const int o) + { + W(o) = winding_number(V,F,O.row(o)); + },10000); + return; + } + case 3: + { + WindingNumberAABB< + Eigen::Matrix, + DerivedV, + DerivedF> + hier(V,F); + hier.grow(); + // loop over origins + igl::parallel_for(O.rows(),[&](const int o) + { + W(o) = hier.winding_number(O.row(o)); + },10000); + break; + } + default: assert(false && "Bad simplex size"); break; + } +} + +template < + typename DerivedV, + typename DerivedF, + typename Derivedp> +IGL_INLINE typename DerivedV::Scalar igl::winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & p) +{ + typedef typename DerivedV::Scalar wType; + const int ss = F.cols(); + const int m = F.rows(); + wType w = 0; + for(int f = 0;f::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template void igl::winding_number, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template Eigen::Matrix::Scalar igl::winding_number, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/winding_number.h b/src/igl/winding_number.h new file mode 100644 index 000000000..4a1c51d81 --- /dev/null +++ b/src/igl/winding_number.h @@ -0,0 +1,64 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WINDING_NUMBER_H +#define IGL_WINDING_NUMBER_H +#include "igl_inline.h" +#include + +// Minimum number of iterms per openmp thread +#ifndef IGL_WINDING_NUMBER_OMP_MIN_VALUE +# define IGL_WINDING_NUMBER_OMP_MIN_VALUE 1000 +#endif +namespace igl +{ + // WINDING_NUMBER Compute the sum of solid angles of a triangle/tetrahedron + // described by points (vectors) V + // + // Templates: + // dim dimension of input + // Inputs: + // V n by 3 list of vertex positions + // F #F by 3 list of triangle indices, minimum index is 0 + // O no by 3 list of origin positions + // Outputs: + // S no by 1 list of winding numbers + // + template < + typename DerivedV, + typename DerivedF, + typename DerivedO, + typename DerivedW> + IGL_INLINE void winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & O, + Eigen::PlainObjectBase & W); + // Compute winding number of a single point + // + // Inputs: + // V n by dim list of vertex positions + // F #F by dim list of triangle indices, minimum index is 0 + // p single origin position + // Outputs: + // w winding number of this point + // + template < + typename DerivedV, + typename DerivedF, + typename Derivedp> + IGL_INLINE typename DerivedV::Scalar winding_number( + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & p); +} + +#ifndef IGL_STATIC_LIBRARY +# include "winding_number.cpp" +#endif + +#endif diff --git a/src/igl/writeBF.cpp b/src/igl/writeBF.cpp new file mode 100644 index 000000000..18dc6a934 --- /dev/null +++ b/src/igl/writeBF.cpp @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeBF.h" +#include +#include +template < + typename DerivedWI, + typename DerivedP, + typename DerivedO> +IGL_INLINE bool igl::writeBF( + const std::string & filename, + const Eigen::PlainObjectBase & WI, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & O) +{ + using namespace Eigen; + using namespace std; + const int n = WI.rows(); + assert(n == WI.rows() && "WI must have n rows"); + assert(n == P.rows() && "P must have n rows"); + assert(n == O.rows() && "O must have n rows"); + MatrixXd WIPO(n,1+1+3); + for(int i = 0;i, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/writeBF.h b/src/igl/writeBF.h new file mode 100644 index 000000000..5ea51f1bc --- /dev/null +++ b/src/igl/writeBF.h @@ -0,0 +1,37 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEBF_H +#define IGL_WRITEBF_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Write a bones forest to a file + // + // Input: + // file_name path to .bf bones tree file + // WI #B list of unique weight indices + // P #B list of parent indices into B, -1 for roots + // O #B list of tip offsets + // Returns true on success, false on errors + template < + typename DerivedWI, + typename DerivedP, + typename DerivedO> + IGL_INLINE bool writeBF( + const std::string & filename, + const Eigen::PlainObjectBase & WI, + const Eigen::PlainObjectBase & P, + const Eigen::PlainObjectBase & O); +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeBF.cpp" +#endif +#endif diff --git a/src/igl/writeDMAT.cpp b/src/igl/writeDMAT.cpp new file mode 100644 index 000000000..c8f6ae0f6 --- /dev/null +++ b/src/igl/writeDMAT.cpp @@ -0,0 +1,92 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeDMAT.h" +#include "list_to_matrix.h" +#include + +#include + +template +IGL_INLINE bool igl::writeDMAT( + const std::string file_name, + const Eigen::MatrixBase & W, + const bool ascii) +{ + FILE * fp = fopen(file_name.c_str(),"wb"); + if(fp == NULL) + { + fprintf(stderr,"IOError: writeDMAT() could not open %s...",file_name.c_str()); + return false; + } + if(ascii) + { + // first line contains number of rows and number of columns + fprintf(fp,"%d %d\n",(int)W.cols(),(int)W.rows()); + // Loop over columns slowly + for(int j = 0;j < W.cols();j++) + { + // loop over rows (down columns) quickly + for(int i = 0;i < W.rows();i++) + { + fprintf(fp,"%0.17lg\n",(double)W(i,j)); + } + } + }else + { + // write header for ascii + fprintf(fp,"0 0\n"); + // first line contains number of rows and number of columns + fprintf(fp,"%d %d\n",(int)W.cols(),(int)W.rows()); + // reader assumes the binary part is double precision + Eigen::MatrixXd Wd = W.template cast(); + fwrite(Wd.data(),sizeof(double),Wd.size(),fp); + //// Loop over columns slowly + //for(int j = 0;j < W.cols();j++) + //{ + // // loop over rows (down columns) quickly + // for(int i = 0;i < W.rows();i++) + // { + // double d = (double)W(i,j); + // fwrite(&d,sizeof(double),1,fp); + // } + //} + } + fclose(fp); + return true; +} + +template +IGL_INLINE bool igl::writeDMAT( + const std::string file_name, + const std::vector > & W, + const bool ascii) +{ + Eigen::Matrix mW; + list_to_matrix(W,mW); + return igl::writeDMAT(file_name,mW,ascii); +} + +template +IGL_INLINE bool igl::writeDMAT( + const std::string file_name, + const std::vector & W, + const bool ascii) +{ + Eigen::Matrix mW; + list_to_matrix(W,mW); + return igl::writeDMAT(file_name,mW,ascii); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template bool igl::writeDMAT >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, bool); +template bool igl::writeDMAT >(std::string, Eigen::MatrixBase > const&, bool); +template bool igl::writeDMAT >(std::string, Eigen::MatrixBase > const&, bool); +template bool igl::writeDMAT >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, bool); +template bool igl::writeDMAT >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, bool); +#endif diff --git a/src/igl/writeDMAT.h b/src/igl/writeDMAT.h new file mode 100644 index 000000000..08409ef67 --- /dev/null +++ b/src/igl/writeDMAT.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEDMAT_H +#define IGL_WRITEDMAT_H +#include "igl_inline.h" +// See writeDMAT.h for a description of the .dmat file type +#include +#include +#include +namespace igl +{ + // Write a matrix using ascii dmat file type + // + // Template: + // Mat matrix type that supports .rows(), .cols(), operator(i,j) + // Inputs: + // file_name path to .dmat file + // W eigen matrix containing to-be-written coefficients + // ascii write ascii file {true} + // Returns true on success, false on error + // + template + IGL_INLINE bool writeDMAT( + const std::string file_name, + const Eigen::MatrixBase & W, + const bool ascii=true); + template + IGL_INLINE bool writeDMAT( + const std::string file_name, + const std::vector > & W, + const bool ascii=true); + template + IGL_INLINE bool writeDMAT( + const std::string file_name, + const std::vector &W, + const bool ascii=true); +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeDMAT.cpp" +#endif + +#endif diff --git a/src/igl/writeMESH.cpp b/src/igl/writeMESH.cpp new file mode 100644 index 000000000..27b0123cb --- /dev/null +++ b/src/igl/writeMESH.cpp @@ -0,0 +1,152 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeMESH.h" + +#include "verbose.h" +#include "list_to_matrix.h" +#include + +#include +#include +#include + +template +IGL_INLINE bool igl::writeMESH( + const std::string mesh_file_name, + const std::vector > & V, + const std::vector > & T, + const std::vector > & F) +{ + Eigen::MatrixXd mV; + Eigen::MatrixXi mT,mF; + bool is_rect; + is_rect = list_to_matrix(V,mV); + if(!is_rect) + { + return false; + } + is_rect = list_to_matrix(T,mT); + if(!is_rect) + { + return false; + } + is_rect = list_to_matrix(F,mF); + if(!is_rect) + { + return false; + } + return igl::writeMESH(mesh_file_name,mV,mT,mF); +} + + +template +IGL_INLINE bool igl::writeMESH( + const std::string str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & F) +{ + using namespace std; + using namespace Eigen; + + //// This is (surprisingly) slower than the C-ish code below + //ofstream mesh_file; + //mesh_file.open(str.c_str()); + //if(!mesh_file.is_open()) + //{ + // cerr<<"IOError: "<, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +//template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); + +template bool igl::writeMESH, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeMESH(std::basic_string, std::allocator >, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&, std::vector >, std::allocator > > > const&); +#endif diff --git a/src/igl/writeMESH.h b/src/igl/writeMESH.h new file mode 100644 index 000000000..4bb51418e --- /dev/null +++ b/src/igl/writeMESH.h @@ -0,0 +1,58 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEMESH_H +#define IGL_WRITEMESH_H +#include "igl_inline.h" + +#include +#include +#include + +namespace igl +{ + // save a tetrahedral volume mesh to a .mesh file + // + // Templates: + // Scalar type for positions and vectors (will be cast as double) + // Index type for indices (will be cast to int) + // Input: + // mesh_file_name path of .mesh file + // V double matrix of vertex positions #V by 3 + // T #T list of tet indices into vertex positions + // F #F list of face indices into vertex positions + // + // Known bugs: Holes and regions are not supported + template + IGL_INLINE bool writeMESH( + const std::string mesh_file_name, + const std::vector > & V, + const std::vector > & T, + const std::vector > & F); + + // Templates: + // DerivedV real-value: i.e. from MatrixXd + // DerivedT integer-value: i.e. from MatrixXi + // DerivedF integer-value: i.e. from MatrixXi + // Input: + // mesh_file_name path of .mesh file + // V eigen double matrix #V by 3 + // T eigen int matrix #T by 4 + // F eigen int matrix #F by 3 + template + IGL_INLINE bool writeMESH( + const std::string str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & T, + const Eigen::PlainObjectBase & F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeMESH.cpp" +#endif + +#endif diff --git a/src/igl/writeOBJ.cpp b/src/igl/writeOBJ.cpp new file mode 100644 index 000000000..286254174 --- /dev/null +++ b/src/igl/writeOBJ.cpp @@ -0,0 +1,136 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeOBJ.h" + +#include +#include +#include +#include +#include +#include + +template < + typename DerivedV, + typename DerivedF, + typename DerivedCN, + typename DerivedFN, + typename DerivedTC, + typename DerivedFTC> +IGL_INLINE bool igl::writeOBJ( + const std::string str, + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& CN, + const Eigen::MatrixBase& FN, + const Eigen::MatrixBase& TC, + const Eigen::MatrixBase& FTC) +{ + FILE * obj_file = fopen(str.c_str(),"w"); + if(NULL==obj_file) + { + printf("IOError: %s could not be opened for writing...",str.c_str()); + return false; + } + // Loop over V + for(int i = 0;i<(int)V.rows();i++) + { + fprintf(obj_file,"v"); + for(int j = 0;j<(int)V.cols();++j) + { + fprintf(obj_file," %0.17g", V(i,j)); + } + fprintf(obj_file,"\n"); + } + bool write_N = CN.rows() >0; + + if(write_N) + { + for(int i = 0;i<(int)CN.rows();i++) + { + fprintf(obj_file,"vn %0.17g %0.17g %0.17g\n", + CN(i,0), + CN(i,1), + CN(i,2) + ); + } + fprintf(obj_file,"\n"); + } + + bool write_texture_coords = TC.rows() >0; + + if(write_texture_coords) + { + for(int i = 0;i<(int)TC.rows();i++) + { + fprintf(obj_file, "vt %0.17g %0.17g\n",TC(i,0),TC(i,1)); + } + fprintf(obj_file,"\n"); + } + + // loop over F + for(int i = 0;i<(int)F.rows();++i) + { + fprintf(obj_file,"f"); + for(int j = 0; j<(int)F.cols();++j) + { + // OBJ is 1-indexed + fprintf(obj_file," %u",F(i,j)+1); + + if(write_texture_coords) + fprintf(obj_file,"/%u",FTC(i,j)+1); + if(write_N) + { + if (write_texture_coords) + fprintf(obj_file,"/%u",FN(i,j)+1); + else + fprintf(obj_file,"//%u",FN(i,j)+1); + } + } + fprintf(obj_file,"\n"); + } + fclose(obj_file); + return true; +} + +template +IGL_INLINE bool igl::writeOBJ( + const std::string str, + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F) +{ + using namespace std; + using namespace Eigen; + assert(V.cols() == 3 && "V should have 3 columns"); + ofstream s(str); + if(!s.is_open()) + { + fprintf(stderr,"IOError: writeOBJ() could not open %s\n",str.c_str()); + return false; + } + s<< + V.format(IOFormat(FullPrecision,DontAlignCols," ","\n","v ","","","\n"))<< + (F.array()+1).format(IOFormat(FullPrecision,DontAlignCols," ","\n","f ","","","\n")); + return true; +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::writeOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template bool igl::writeOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template bool igl::writeOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +// generated by autoexplicit.sh +template bool igl::writeOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeOBJ, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +template bool igl::writeOBJ, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); +#endif diff --git a/src/igl/writeOBJ.h b/src/igl/writeOBJ.h new file mode 100644 index 000000000..d515796db --- /dev/null +++ b/src/igl/writeOBJ.h @@ -0,0 +1,59 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEOBJ_H +#define IGL_WRITEOBJ_H +#include "igl_inline.h" +// History: +// return type changed from void to bool Alec 20 Sept 2011 + +#include +#include + +namespace igl +{ + // Write a mesh in an ascii obj file + // Inputs: + // str path to outputfile + // V #V by 3 mesh vertex positions + // F #F by 3|4 mesh indices into V + // CN #CN by 3 normal vectors + // FN #F by 3|4 corner normal indices into CN + // TC #TC by 2|3 texture coordinates + // FTC #F by 3|4 corner texture coord indices into TC + // Returns true on success, false on error + // + // Known issues: Horrifyingly, this does not have the same order of + // parameters as readOBJ. + template < + typename DerivedV, + typename DerivedF, + typename DerivedCN, + typename DerivedFN, + typename DerivedTC, + typename DerivedFTC> + IGL_INLINE bool writeOBJ( + const std::string str, + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F, + const Eigen::MatrixBase& CN, + const Eigen::MatrixBase& FN, + const Eigen::MatrixBase& TC, + const Eigen::MatrixBase& FTC); + template + IGL_INLINE bool writeOBJ( + const std::string str, + const Eigen::MatrixBase& V, + const Eigen::MatrixBase& F); + +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeOBJ.cpp" +#endif + +#endif diff --git a/src/igl/writeOFF.cpp b/src/igl/writeOFF.cpp new file mode 100644 index 000000000..9222d76a0 --- /dev/null +++ b/src/igl/writeOFF.cpp @@ -0,0 +1,94 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeOFF.h" +#include +#include + +// write mesh to an ascii off file +template +IGL_INLINE bool igl::writeOFF( + const std::string fname, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F) +{ + using namespace std; + using namespace Eigen; + assert(V.cols() == 3 && "V should have 3 columns"); + ofstream s(fname); + if(!s.is_open()) + { + fprintf(stderr,"IOError: writeOFF() could not open %s\n",fname.c_str()); + return false; + } + + s<< + "OFF\n"< +IGL_INLINE bool igl::writeOFF( + const std::string fname, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& C) +{ + using namespace std; + using namespace Eigen; + assert(V.cols() == 3 && "V should have 3 columns"); + assert(C.cols() == 3 && "C should have 3 columns"); + + if(V.rows() != C.rows()) + { + fprintf(stderr,"IOError: writeOFF() Only color per vertex supported. V and C should have same size.\n"); + return false; + } + + ofstream s(fname); + if(!s.is_open()) + { + fprintf(stderr,"IOError: writeOFF() could not open %s\n",fname.c_str()); + return false; + } + + //Check if RGB values are in the range [0..1] or [0..255] + int rgbScale = (C.maxCoeff() <= 1.0)?255:1; + // Use RGB_Array instead of RGB because of clash with mingw macro + // (https://github.com/libigl/libigl/pull/679) + Eigen::MatrixXd RGB_Array = rgbScale * C; + + s<< "COFF\n"<, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeOFF, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/writeOFF.h b/src/igl/writeOFF.h new file mode 100644 index 000000000..754c18a53 --- /dev/null +++ b/src/igl/writeOFF.h @@ -0,0 +1,50 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEOFF_H +#define IGL_WRITEOFF_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + //Export geometry and colors-by-vertex + // Export a mesh from an ascii OFF file, filling in vertex positions. + // Only triangle meshes are supported + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to .off output file + // V #V by 3 mesh vertex positions + // F #F by 3 mesh indices into V + // C double matrix of rgb values per vertex #V by 3 + // Outputs: + // Returns true on success, false on errors + template + IGL_INLINE bool writeOFF( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const Eigen::PlainObjectBase& C); + + template + IGL_INLINE bool writeOFF( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F); +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeOFF.cpp" +#endif + +#endif diff --git a/src/igl/writePLY.cpp b/src/igl/writePLY.cpp new file mode 100644 index 000000000..55c9b83fd --- /dev/null +++ b/src/igl/writePLY.cpp @@ -0,0 +1,182 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writePLY.h" +#include + +#include +#include + +namespace +{ + template int ply_type(); + template <> int ply_type(){ return PLY_CHAR; } + template <> int ply_type(){ return PLY_SHORT; } + template <> int ply_type(){ return PLY_INT; } + template <> int ply_type(){ return PLY_UCHAR; } + template <> int ply_type(){ return PLY_SHORT; } + template <> int ply_type(){ return PLY_UINT; } + template <> int ply_type(){ return PLY_FLOAT; } + template <> int ply_type(){ return PLY_DOUBLE; } +} + +template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedUV> +IGL_INLINE bool igl::writePLY( + const std::string & filename, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & N, + const Eigen::MatrixBase & UV, + const bool ascii) +{ + // Largely based on obj2ply.c + typedef typename DerivedV::Scalar VScalar; + typedef typename DerivedN::Scalar NScalar; + typedef typename DerivedUV::Scalar UVScalar; + typedef typename DerivedF::Scalar FScalar; + + typedef struct Vertex + { + VScalar x,y,z,w; /* position */ + NScalar nx,ny,nz; /* surface normal */ + UVScalar s,t; /* texture coordinates */ + } Vertex; + + typedef struct Face + { + unsigned char nverts; /* number of vertex indices in list */ + FScalar *verts; /* vertex index list */ + } Face; + + igl::ply::PlyProperty vert_props[] = + { /* list of property information for a vertex */ + {"x", ply_type(), ply_type(),offsetof(Vertex,x),0,0,0,0}, + {"y", ply_type(), ply_type(),offsetof(Vertex,y),0,0,0,0}, + {"z", ply_type(), ply_type(),offsetof(Vertex,z),0,0,0,0}, + {"nx",ply_type(), ply_type(),offsetof(Vertex,nx),0,0,0,0}, + {"ny",ply_type(), ply_type(),offsetof(Vertex,ny),0,0,0,0}, + {"nz",ply_type(), ply_type(),offsetof(Vertex,nz),0,0,0,0}, + {"s", ply_type(),ply_type(),offsetof(Vertex,s),0,0,0,0}, + {"t", ply_type(),ply_type(),offsetof(Vertex,t),0,0,0,0}, + }; + + igl::ply::PlyProperty face_props[] = + { /* list of property information for a face */ + {"vertex_indices", ply_type(), ply_type(), + offsetof(Face,verts), 1, PLY_UCHAR, PLY_UCHAR, offsetof(Face,nverts)}, + }; + const bool has_normals = N.rows() > 0; + const bool has_texture_coords = UV.rows() > 0; + std::vector vlist(V.rows()); + std::vector flist(F.rows()); + for(size_t i = 0;i<(size_t)V.rows();i++) + { + vlist[i].x = V(i,0); + vlist[i].y = V(i,1); + vlist[i].z = V(i,2); + if(has_normals) + { + vlist[i].nx = N(i,0); + vlist[i].ny = N(i,1); + vlist[i].nz = N(i,2); + } + if(has_texture_coords) + { + vlist[i].s = UV(i,0); + vlist[i].t = UV(i,1); + } + } + for(size_t i = 0;i<(size_t)F.rows();i++) + { + flist[i].nverts = F.cols(); + flist[i].verts = new FScalar[F.cols()]; + for(size_t c = 0;c<(size_t)F.cols();c++) + { + flist[i].verts[c] = F(i,c); + } + } + + const char * elem_names[] = {"vertex","face"}; + FILE * fp = fopen(filename.c_str(),"w"); + if(fp==NULL) + { + return false; + } + igl::ply::PlyFile * ply = igl::ply::ply_write(fp, 2,elem_names, + (ascii ? PLY_ASCII : PLY_BINARY_LE)); + if(ply==NULL) + { + return false; + } + + std::vector plist; + plist.push_back(vert_props[0]); + plist.push_back(vert_props[1]); + plist.push_back(vert_props[2]); + if (has_normals) + { + plist.push_back(vert_props[3]); + plist.push_back(vert_props[4]); + plist.push_back(vert_props[5]); + } + if (has_texture_coords) + { + plist.push_back(vert_props[6]); + plist.push_back(vert_props[7]); + } + ply_describe_element(ply, "vertex", V.rows(),plist.size(), + &plist[0]); + + ply_describe_element(ply, "face", F.rows(),1,&face_props[0]); + ply_header_complete(ply); + int native_binary_type = igl::ply::get_native_binary_type2(); + ply_put_element_setup(ply, "vertex"); + for(const auto v : vlist) + { + ply_put_element(ply, (void *) &v, &native_binary_type); + } + ply_put_element_setup(ply, "face"); + for(const auto f : flist) + { + ply_put_element(ply, (void *) &f, &native_binary_type); + } + + ply_close(ply); + for(size_t i = 0;i<(size_t)F.rows();i++) + { + delete[] flist[i].verts; + } + return true; +} + +template < + typename DerivedV, + typename DerivedF> +IGL_INLINE bool igl::writePLY( + const std::string & filename, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const bool ascii) +{ + Eigen::Matrix N,UV; + return writePLY(filename,V,F,N,UV,ascii); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::writePLY, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::writePLY, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool); +template bool igl::writePLY, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool); +template bool igl::writePLY, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool); +template bool igl::writePLY, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, bool); +#endif diff --git a/src/igl/writePLY.h b/src/igl/writePLY.h new file mode 100644 index 000000000..8b99ea1d0 --- /dev/null +++ b/src/igl/writePLY.h @@ -0,0 +1,49 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITEPLY_H +#define IGL_WRITEPLY_H +#include "igl_inline.h" +#include +#include + +namespace igl +{ + // Write a mesh to a .ply file. + // + // Inputs: + // filename path to .ply file + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // N #V by 3 list of vertex normals + // UV #V by 2 list of vertex texture coordinates + // Returns true iff success + template < + typename DerivedV, + typename DerivedF, + typename DerivedN, + typename DerivedUV> + IGL_INLINE bool writePLY( + const std::string & filename, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const Eigen::MatrixBase & N, + const Eigen::MatrixBase & UV, + const bool ascii = true); + template < + typename DerivedV, + typename DerivedF> + IGL_INLINE bool writePLY( + const std::string & filename, + const Eigen::MatrixBase & V, + const Eigen::MatrixBase & F, + const bool ascii = true); +} +#ifndef IGL_STATIC_LIBRARY +# include "writePLY.cpp" +#endif +#endif diff --git a/src/igl/writeSTL.cpp b/src/igl/writeSTL.cpp new file mode 100644 index 000000000..d95f2a8d7 --- /dev/null +++ b/src/igl/writeSTL.cpp @@ -0,0 +1,121 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeSTL.h" +#include + +template +IGL_INLINE bool igl::writeSTL( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const bool ascii) +{ + using namespace std; + assert(N.rows() == 0 || F.rows() == N.rows()); + if(ascii) + { + FILE * stl_file = fopen(filename.c_str(),"w"); + if(stl_file == NULL) + { + cerr<<"IOError: "<0) + { + fprintf(stl_file,"%e %e %e\n", + (float)N(f,0), + (float)N(f,1), + (float)N(f,2)); + }else + { + fprintf(stl_file,"0 0 0\n"); + } + fprintf(stl_file,"outer loop\n"); + for(int c = 0;c n(3,0); + if(N.rows() > 0) + { + n[0] = N(f,0); + n[1] = N(f,1); + n[2] = N(f,2); + } + fwrite(&n[0],sizeof(float),3,stl_file); + for(int c = 0;c<3;c++) + { + vector v(3); + v[0] = V(F(f,c),0); + v[1] = V(F(f,c),1); + v[2] = V(F(f,c),2); + fwrite(&v[0],sizeof(float),3,stl_file); + } + unsigned short att_count = 0; + fwrite(&att_count,sizeof(unsigned short),1,stl_file); + } + fclose(stl_file); + return true; + } +} + +template +IGL_INLINE bool igl::writeSTL( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const bool ascii) +{ + return writeSTL(filename,V,F, DerivedV(), ascii); +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +// generated by autoexplicit.sh +template bool igl::writeSTL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::writeSTL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::writeSTL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +template bool igl::writeSTL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +template bool igl::writeSTL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +#endif diff --git a/src/igl/writeSTL.h b/src/igl/writeSTL.h new file mode 100644 index 000000000..1ffd20281 --- /dev/null +++ b/src/igl/writeSTL.h @@ -0,0 +1,52 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITESTL_H +#define IGL_WRITESTL_H +#include "igl_inline.h" + +#ifndef IGL_NO_EIGEN +# include +#endif +#include +#include + +namespace igl +{ + // Write a mesh to an stl file. + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Inputs: + // filename path to .obj file + // V double matrix of vertex positions #F*3 by 3 + // F index matrix of triangle indices #F by 3 + // N double matrix of vertex positions #F by 3 + // asci write ascii file {true} + // Returns true on success, false on errors + // + template + IGL_INLINE bool writeSTL( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & N, + const bool ascii=true); + template + IGL_INLINE bool writeSTL( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const bool ascii=true); +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeSTL.cpp" +#endif + +#endif diff --git a/src/igl/writeTGF.cpp b/src/igl/writeTGF.cpp new file mode 100644 index 000000000..65c8fc6a4 --- /dev/null +++ b/src/igl/writeTGF.cpp @@ -0,0 +1,73 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeTGF.h" +#include + +IGL_INLINE bool igl::writeTGF( + const std::string tgf_filename, + const std::vector > & C, + const std::vector > & E) +{ + FILE * tgf_file = fopen(tgf_filename.c_str(),"w"); + if(NULL==tgf_file) + { + printf("IOError: %s could not be opened\n",tgf_filename.c_str()); + return false; + } + // Loop over vertices + for(int i = 0; i<(int)C.size();i++) + { + assert(C[i].size() == 3); + // print a line with vertex number then "description" + // Where "description" in our case is the 3d position in space + // + fprintf(tgf_file, + "%4d " + "%10.17g %10.17g %10.17g " // current location + // All others are not needed for this legacy support + "\n", + i+1, + C[i][0], C[i][1], C[i][2]); + } + + // print a comment to separate vertices and edges + fprintf(tgf_file,"#\n"); + + // loop over edges + for(int i = 0;i<(int)E.size();i++) + { + assert(E[i].size()==2); + fprintf(tgf_file,"%4d %4d\n", + E[i][0]+1, + E[i][1]+1); + } + + // print a comment to separate edges and faces + fprintf(tgf_file,"#\n"); + + fclose(tgf_file); + + return true; +} + +#ifndef IGL_NO_EIGEN +#include "matrix_to_list.h" + +IGL_INLINE bool igl::writeTGF( + const std::string tgf_filename, + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & E) +{ + using namespace std; + vector > vC; + vector > vE; + matrix_to_list(C,vC); + matrix_to_list(E,vE); + return writeTGF(tgf_filename,vC,vE); +} +#endif diff --git a/src/igl/writeTGF.h b/src/igl/writeTGF.h new file mode 100644 index 000000000..999584463 --- /dev/null +++ b/src/igl/writeTGF.h @@ -0,0 +1,48 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITETGF_H +#define IGL_WRITETGF_H +#include "igl_inline.h" + +#include +#include +#ifndef IGL_NO_EIGEN +#include +#endif + +namespace igl +{ + // WRITETGF + // + // Write a graph to a .tgf file + // + // Input: + // filename .tgf file name + // V # vertices by 3 list of vertex positions + // E # edges by 2 list of edge indices + // + // Assumes that graph vertices are 3 dimensional + IGL_INLINE bool writeTGF( + const std::string tgf_filename, + const std::vector > & C, + const std::vector > & E); + + #ifndef IGL_NO_EIGEN + IGL_INLINE bool writeTGF( + const std::string tgf_filename, + const Eigen::MatrixXd & C, + const Eigen::MatrixXi & E); + #endif +} + +#ifndef IGL_STATIC_LIBRARY +# include "writeTGF.cpp" +#endif + +#endif + diff --git a/src/igl/writeWRL.cpp b/src/igl/writeWRL.cpp new file mode 100644 index 000000000..2740d1c2f --- /dev/null +++ b/src/igl/writeWRL.cpp @@ -0,0 +1,126 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeWRL.h" +#include +#include +template +IGL_INLINE bool igl::writeWRL( + const std::string & str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F) +{ + using namespace std; + using namespace Eigen; + assert(V.cols() == 3 && "V should have 3 columns"); + assert(F.cols() == 3 && "F should have 3 columns"); + ofstream s(str); + if(!s.is_open()) + { + cerr<<"IOError: writeWRL() could not open "< FF(F.rows(),4); + FF.leftCols(3) = F; + FF.col(3).setConstant(-1); + + s< +IGL_INLINE bool igl::writeWRL( + const std::string & str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & C) +{ + using namespace std; + using namespace Eigen; + assert(V.cols() == 3 && "V should have 3 columns"); + assert(F.cols() == 3 && "F should have 3 columns"); + ofstream s(str); + if(!s.is_open()) + { + cerr<<"IOError: writeWRL() could not open "< FF(F.rows(),4); + FF.leftCols(3) = F; + FF.col(3).setConstant(-1); + + + //Check if RGB values are in the range [0..1] or [0..255] + double rgbScale = (C.maxCoeff() <= 1.0)?1.0:1.0/255.0; + Eigen::MatrixXd RGB = rgbScale * C; + + s<, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeWRL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeWRL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +// generated by autoexplicit.sh +template bool igl::writeWRL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeWRL, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +template bool igl::writeWRL, Eigen::Matrix, Eigen::Matrix >(std::basic_string, std::allocator > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&); +#endif diff --git a/src/igl/writeWRL.h b/src/igl/writeWRL.h new file mode 100644 index 000000000..5ff6a25ed --- /dev/null +++ b/src/igl/writeWRL.h @@ -0,0 +1,46 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2015 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITE_WRL_H +#define IGL_WRITE_WRL_H +#include "igl_inline.h" +#include +#include +namespace igl +{ + // Write mesh to a .wrl file + // + // Inputs: + // str path to .wrl file + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // Returns true iff succes + template + IGL_INLINE bool writeWRL( + const std::string & str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F); + + // Write mesh to a .wrl file + // + // Inputs: + // str path to .wrl file + // V #V by 3 list of vertex positions + // F #F by 3 list of triangle indices + // C double matrix of rgb values per vertex #V by 3 + // Returns true iff succes + template + IGL_INLINE bool writeWRL( + const std::string & str, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F, + const Eigen::PlainObjectBase & C); +} +#ifndef IGL_STATIC_LIBRARY +#include "writeWRL.cpp" +#endif +#endif diff --git a/src/igl/write_triangle_mesh.cpp b/src/igl/write_triangle_mesh.cpp new file mode 100644 index 000000000..3699009f5 --- /dev/null +++ b/src/igl/write_triangle_mesh.cpp @@ -0,0 +1,70 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "write_triangle_mesh.h" +#include "pathinfo.h" +#include "writeMESH.h" +#include "writeOBJ.h" +#include "writeOFF.h" +#include "writePLY.h" +#include "writeSTL.h" +#include "writeWRL.h" + +#include + +template +IGL_INLINE bool igl::write_triangle_mesh( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const bool force_ascii) +{ + using namespace std; + // dirname, basename, extension and filename + string d,b,e,f; + pathinfo(str,d,b,e,f); + // Convert extension to lower case + std::transform(e.begin(), e.end(), e.begin(), ::tolower); + if(e == "mesh") + { + Eigen::MatrixXi _1; + return writeMESH(str,V,_1,F); + }else if(e == "obj") + { + return writeOBJ(str,V,F); + }else if(e == "off") + { + return writeOFF(str,V,F); + }else if(e == "ply") + { + return writePLY(str,V,F,force_ascii); + }else if(e == "stl") + { + return writeSTL(str,V,F,force_ascii); + }else if(e == "wrl") + { + return writeWRL(str,V,F); + }else + { + assert("Unsupported file format"); + cerr<<"Unsupported file format: ."<, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::write_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::write_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +// generated by autoexplicit.sh +template bool igl::write_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, const bool); +template bool igl::write_triangle_mesh, Eigen::Matrix >(std::basic_string, std::allocator >, Eigen::PlainObjectBase > const&, Eigen::PlainObjectBase > const&, bool); +#endif diff --git a/src/igl/write_triangle_mesh.h b/src/igl/write_triangle_mesh.h new file mode 100644 index 000000000..6a7241628 --- /dev/null +++ b/src/igl/write_triangle_mesh.h @@ -0,0 +1,42 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_WRITE_TRIANGLE_MESH_H +#define IGL_WRITE_TRIANGLE_MESH_H +#include "igl_inline.h" + +#include +#include + +namespace igl +{ + // write mesh to a file with automatic detection of file format. supported: + // obj, off, stl, wrl, ply, mesh). + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to file + // V eigen double matrix #V by 3 + // F eigen int matrix #F by 3 + // force_ascii force ascii format even if binary is available + // Returns true iff success + template + IGL_INLINE bool write_triangle_mesh( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const bool force_ascii = true); +} + +#ifndef IGL_STATIC_LIBRARY +# include "write_triangle_mesh.cpp" +#endif + +#endif diff --git a/src/igl/xml/ReAntTweakBarXMLSerialization.h b/src/igl/xml/ReAntTweakBarXMLSerialization.h new file mode 100644 index 000000000..c43af0c80 --- /dev/null +++ b/src/igl/xml/ReAntTweakBarXMLSerialization.h @@ -0,0 +1,269 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2013 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_XML_REANTTWEAKBAR_XML_SERIALIZATION_H +#define IGL_XML_REANTTWEAKBAR_XML_SERIALIZATION_H +#include "../igl_inline.h" +#include "serialize_xml.h" + +#undef IGL_HEADER_ONLY +#include "../anttweakbar/ReAntTweakBar.h" + +// Forward declarations +namespace igl +{ + namespace anttweakbar + { + class ReTwBar; + } +}; +namespace tinyxml2 +{ + class XMLDocument; +}; + +namespace igl +{ + namespace xml + { + +// namespace +// { + +// IGL_INLINE bool save_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, const char* file_name); +// IGL_INLINE bool save_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, tinyxml2::XMLDocument* doc); +// IGL_INLINE bool load_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, const char *file_name); +// IGL_INLINE bool load_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, tinyxml2::XMLDocument* doc); + + + IGL_INLINE bool save_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, const char* file_name) + { + const char * name_chars = TwGetBarName(bar->bar); + std::string name = std::string(name_chars) + "_AntTweakBar"; + + const std::vector< ::igl::anttweakbar::ReTwRWItem>& rw_items = bar->get_rw_items(); + for(std::vector< ::igl::anttweakbar::ReTwRWItem>::const_iterator it = rw_items.begin(); it != rw_items.end(); it++) + { + std::string val = bar->get_value_as_string(it->var,it->type); + //::igl::XMLSerializer::SaveObject(val,it->name,name,file_name,false); + ::igl::serialize_xml(val,it->name,file_name,false,false); + } + + char var[REANTTWEAKBAR_MAX_CB_VAR_SIZE]; + // Print all CB variables + const std::vector< ::igl::anttweakbar::ReTwCBItem>& cb_items = bar->get_cb_items(); + for(std::vector< ::igl::anttweakbar::ReTwCBItem>::const_iterator it = cb_items.begin(); it != cb_items.end(); it++) + { + TwType type = it->type; + //TwSetVarCallback setCallback = it->setCallback; + TwGetVarCallback getCallback = it->getCallback; + void * clientData = it->clientData; + // I'm not sure how to do what I want to do. getCallback needs to be sure + // that it can write to var. So var needs to point to a valid and big + // enough chunk of memory + getCallback(var,clientData); + + std::string val = bar->get_value_as_string(var,type); + //::igl::XMLSerializer::SaveObject(val,it->name,name,file_name,false); + ::igl::serialize_xml(val,it->name,file_name,false,false); + } + + return true; + } + + /*IGL_INLINE bool save_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, tinyxml2::XMLDocument* doc) + { + std::vector buffer; + + const char * name_chars = TwGetBarName(bar->bar); + std::string name = std::string(name_chars) + "_AntTweakBar"; + ::igl::XMLSerializer* s = new ::igl::XMLSerializer(name); + + const std::vector< ::igl::anttweakbar::ReTwRWItem>& rw_items = bar->get_rw_items(); + for(std::vector< ::igl::anttweakbar::ReTwRWItem>::const_iterator it = rw_items.begin(); it != rw_items.end(); it++) + { + std::string val = bar->get_value_as_string(it->var,it->type); + char** cval = new char*; // create char* on heap + *cval = new char[val.size()+1]; + buffer.push_back(cval); + strcpy(*cval,val.c_str()); + s->Add(*cval,it->name); + } + + char var[REANTTWEAKBAR_MAX_CB_VAR_SIZE]; + // Print all CB variables + const std::vector< ::igl::anttweakbar::ReTwCBItem>& cb_items = bar->get_cb_items(); + for(std::vector< ::igl::anttweakbar::ReTwCBItem>::const_iterator it = cb_items.begin(); it != cb_items.end(); it++) + { + TwType type = it->type; + //TwSetVarCallback setCallback = it->setCallback; + TwGetVarCallback getCallback = it->getCallback; + void * clientData = it->clientData; + // I'm not sure how to do what I want to do. getCallback needs to be sure + // that it can write to var. So var needs to point to a valid and big + // enough chunk of memory + getCallback(var,clientData); + + std::string val = bar->get_value_as_string(var,type); + char** cval = new char*; // create char* on heap + *cval = new char[val.size()+1]; + buffer.push_back(cval); + strcpy(*cval,val.c_str()); + s->Add(*cval,it->name); + } + + s->SaveToXMLDoc(name,doc); + + // delete pointer buffers + for(unsigned int i=0;ibar); + std::string name = std::string(name_chars) + "_AntTweakBar"; + + const std::vector< ::igl::anttweakbar::ReTwRWItem>& rw_items = bar->get_rw_items(); + for(std::vector< ::igl::anttweakbar::ReTwRWItem>::const_iterator it = rw_items.begin(); it != rw_items.end(); it++) + { + char* val; + //::igl::XMLSerializer::LoadObject(val,it->name,name,file_name); + ::igl::deserialize_xml(val,it->name,file_name); + sscanf(val,"%s %[^\n]",type_str,value_str); + + if(!bar->type_from_string(type_str,type)) + { + printf("ERROR: %s type not found... Skipping...\n",type_str); + continue; + } + + bar->set_value_from_string(it->name.c_str(),type,value_str); + delete[] val; + } + + const std::vector< ::igl::anttweakbar::ReTwCBItem>& cb_items = bar->get_cb_items(); + for(std::vector< ::igl::anttweakbar::ReTwCBItem>::const_iterator it = cb_items.begin(); it != cb_items.end(); it++) + { + char* val; + //::igl::XMLSerializer::LoadObject(val,it->name,name,file_name); + ::igl::deserialize_xml(val,it->name,file_name); + sscanf(val,"%s %[^\n]",type_str,value_str); + + if(!bar->type_from_string(type_str,type)) + { + printf("ERROR: %s type not found... Skipping...\n",type_str); + continue; + } + + bar->set_value_from_string(it->name.c_str(),type,value_str); + delete[] val; + } + + return true; + } + + /*IGL_INLINE bool load_ReAntTweakBar(::igl::anttweakbar::ReTwBar* bar, tinyxml2::XMLDocument* doc) + { + std::map variables; + std::map cbVariables; + + const char * name_chars = TwGetBarName(bar->bar); + std::string name = std::string(name_chars) + "_AntTweakBar"; + ::igl::XMLSerializer* s = new ::igl::XMLSerializer(name); + + std::map::iterator iter; + const std::vector< ::igl::anttweakbar::ReTwRWItem>& rw_items = bar->get_rw_items(); + for(std::vector< ::igl::anttweakbar::ReTwRWItem>::const_iterator it = rw_items.begin(); it != rw_items.end(); it++) + { + variables[it->name] = NULL; + iter = variables.find(it->name); + s->Add(iter->second,iter->first); + } + + // Add all CB variables + const std::vector< ::igl::anttweakbar::ReTwCBItem>& cb_items = bar->get_cb_items(); + for(std::vector< ::igl::anttweakbar::ReTwCBItem>::const_iterator it = cb_items.begin(); it != cb_items.end(); it++) + { + cbVariables[it->name] = NULL; + iter = cbVariables.find(it->name); + s->Add(iter->second,iter->first); + } + + s->LoadFromXMLDoc(doc); + + // Set loaded values + char type_str[REANTTWEAKBAR_MAX_WORD]; + char value_str[REANTTWEAKBAR_MAX_WORD]; + TwType type; + + for(iter = variables.begin(); iter != variables.end(); iter++) + { + if(iter->second == NULL) + { + printf("ERROR: '%s' entry not found... Skipping...\n",iter->first.c_str()); + continue; + } + + sscanf(iter->second,"%s %[^\n]",type_str,value_str); + + if(!bar->type_from_string(type_str,type)) + { + printf("ERROR: Type '%s' of '%s' not found... Skipping...\n",type_str,iter->first.c_str()); + continue; + } + + bar->set_value_from_string(iter->first.c_str(),type,value_str); + } + + for(iter = cbVariables.begin(); iter != cbVariables.end(); iter++) + { + if(iter->second == NULL) + { + printf("ERROR: '%s' entry not found... Skipping...\n",iter->first.c_str()); + continue; + } + + sscanf(iter->second,"%s %[^\n]",type_str,value_str); + + if(!bar->type_from_string(type_str,type)) + { + printf("ERROR: Type '%s' of '%s' not found... Skipping...\n",type_str,iter->first.c_str()); + continue; + } + + bar->set_value_from_string(iter->first.c_str(),type,value_str); + } + + // delete buffers + for(iter = variables.begin(); iter != variables.end(); iter++) + delete[] iter->second; + + for(iter = cbVariables.begin(); iter != cbVariables.end(); iter++) + delete[] iter->second; + + delete s; + + return true; + }*/ + +// } + } +} + +#endif diff --git a/src/igl/xml/XMLSerializable.h b/src/igl/xml/XMLSerializable.h new file mode 100644 index 000000000..610559643 --- /dev/null +++ b/src/igl/xml/XMLSerializable.h @@ -0,0 +1,225 @@ +#ifndef IGL_XML_XMLSERIALIZABLE_H +#define IGL_XML_XMLSERIALIZABLE_H + +#include "serialize_xml.h" +#include "../igl_inline.h" +#include "../serialize.h" + +#include + + +// Interface for xml-serializable class see serialize_xml.h + +// Pretty sure all of these IGL_INLINE should be inline + +namespace igl +{ + namespace xml + { + // interface for user defined types + struct XMLSerializableBase : public SerializableBase + { + virtual void Serialize(std::vector& buffer) const = 0; + virtual void Deserialize(const std::vector& buffer) = 0; + virtual void Serialize(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element) const = 0; + virtual void Deserialize(const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element) = 0; + }; + + // Convenient interface for user defined types + class XMLSerializable: public XMLSerializableBase + { + private: + + template + struct XMLSerializationObject: public XMLSerializableBase + { + bool Binary; + std::string Name; + T* Object; + + void Serialize(std::vector& buffer) const { + serialize(*Object,Name,buffer); + } + + void Deserialize(const std::vector& buffer) { + deserialize(*Object,Name,buffer); + } + + void Serialize(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element) const { + serialize_xml(*Object,Name,doc,element,Binary); + } + + void Deserialize(const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element) { + deserialize_xml(*Object,Name,doc,element); + } + }; + + mutable bool initialized; + mutable std::vector objects; + + public: + + // Override this function to add your member variables which should be serialized + IGL_INLINE virtual void InitSerialization() = 0; + + // Following functions can be overridden to handle the specific events. + // Return false to prevent the de-/serialization of an object. + IGL_INLINE virtual bool PreSerialization() const; + IGL_INLINE virtual void PostSerialization() const; + IGL_INLINE virtual bool PreDeserialization(); + IGL_INLINE virtual void PostDeserialization(); + + // Default implementation of XMLSerializableBase interface + IGL_INLINE void Serialize(std::vector& buffer) const; + IGL_INLINE void Deserialize(const std::vector& buffer); + IGL_INLINE void Serialize(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element) const; + IGL_INLINE void Deserialize(const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element); + + // Default constructor, destructor, assignment and copy constructor + IGL_INLINE XMLSerializable(); + IGL_INLINE XMLSerializable(const XMLSerializable& obj); + IGL_INLINE ~XMLSerializable(); + IGL_INLINE XMLSerializable& operator=(const XMLSerializable& obj); + + // Use this function to add your variables which should be serialized + template + IGL_INLINE void Add(T& obj,std::string name,bool binary = false); + }; + + // IMPLEMENTATION + + IGL_INLINE bool XMLSerializable::PreSerialization() const + { + return true; + } + + IGL_INLINE void XMLSerializable::PostSerialization() const + { + } + + IGL_INLINE bool XMLSerializable::PreDeserialization() + { + return true; + } + + IGL_INLINE void XMLSerializable::PostDeserialization() + { + } + + IGL_INLINE void XMLSerializable::Serialize(std::vector& buffer) const + { + if(this->PreSerialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(unsigned int i=0;iSerialize(buffer); + + this->PostSerialization(); + } + } + + IGL_INLINE void XMLSerializable::Deserialize(const std::vector& buffer) + { + if(this->PreDeserialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(unsigned int i=0;iDeserialize(buffer); + + this->PostDeserialization(); + } + } + + IGL_INLINE void XMLSerializable::Serialize(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element) const + { + if(this->PreSerialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(unsigned int i=0;iSerialize(doc,element); + + this->PostSerialization(); + } + } + + IGL_INLINE void XMLSerializable::Deserialize(const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element) + { + if(this->PreDeserialization()) + { + if(initialized == false) + { + objects.clear(); + (const_cast(this))->InitSerialization(); + initialized = true; + } + + for(unsigned int i=0;iDeserialize(doc,element); + + this->PostDeserialization(); + } + } + + IGL_INLINE XMLSerializable::XMLSerializable() + { + initialized = false; + } + + IGL_INLINE XMLSerializable::XMLSerializable(const XMLSerializable& obj) + { + initialized = false; + objects.clear(); + } + + IGL_INLINE XMLSerializable::~XMLSerializable() + { + initialized = false; + objects.clear(); + } + + + IGL_INLINE XMLSerializable& XMLSerializable::operator=(const XMLSerializable& obj) + { + if(this != &obj) + { + if(initialized) + { + initialized = false; + objects.clear(); + } + } + return *this; + } + + template + IGL_INLINE void XMLSerializable::Add(T& obj,std::string name,bool binary) + { + XMLSerializationObject* object = new XMLSerializationObject(); + object->Binary = binary; + object->Name = name; + object->Object = &obj; + + objects.push_back(object); + } + + } +} +#endif diff --git a/src/igl/xml/serialization_test.skip b/src/igl/xml/serialization_test.skip new file mode 100644 index 000000000..5888075c4 --- /dev/null +++ b/src/igl/xml/serialization_test.skip @@ -0,0 +1,489 @@ +// +// Copyright (C) 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +//#ifndef IGL_SERIALIZATION_TEST_H +//#define IGL_SERIALIZATION_TEST_H + +//#include +#include "serialize_xml.h" +#include "XMLSerializable.h" + +namespace igl +{ + namespace xml + { + + struct Test1111 + { + }; + + struct Test1 : public XMLSerializable + { + std::string ts; + std::vector tvt; + Test1* tt; + + Test1() + { + tt = NULL; + } + + void InitSerialization() + { + Add(ts,"ts",false); + Add(tvt,"tvt"); + Add(tt,"tt"); + } + }; + + struct Test2: public XMLSerializableBase + { + char tc; + int* ti; + std::vector tvb; + float tf; + + Test2() + { + tc = '1'; + ti = NULL; + tf = 1.0004; + tvb.push_back(2); + tvb.push_back(3); + } + + void Serialize(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element) const + { + serialize_xml(tc,"tc",doc,element); + serialize_xml(ti,"ti",doc,element); + serialize_xml(tvb,"tvb",doc,element); + } + void Deserialize(const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element) + { + deserialize_xml(tc,"tc",doc,element); + deserialize_xml(ti,"ti",doc,element); + deserialize_xml(tvb,"tvb",doc,element); + } + void Serialize(std::vector& buffer) const + { + serialize(tc,"tc",buffer); + serialize(ti,"ti",buffer); + serialize(tvb,"tvb",buffer); + serialize(tf,"tf",buffer); + } + void Deserialize(const std::vector& buffer) + { + deserialize(tc,"tc",buffer); + deserialize(ti,"ti",buffer); + deserialize(tvb,"tvb",buffer); + deserialize(tf,"tf",buffer); + } + }; + + void serialization_test() + { + std::string file("test"); + + bool tbIn = true,tbOut; + char tcIn = 't',tcOut; + unsigned char tucIn = 'u',tucOut; + short tsIn = 6,tsOut; + int tiIn = -10,tiOut; + unsigned int tuiIn = 10,tuiOut; + float tfIn = 1.0005,tfOut; + double tdIn = 1.000000005,tdOut; + + int* tinpIn = NULL,*tinpOut = NULL; + float* tfpIn = new float,*tfpOut = NULL; + *tfpIn = 1.11101; + + std::string tstrIn("test12345"),tstrOut; + + Test2 tObjIn,tObjOut; + int ti = 2; + tObjIn.ti = &ti; + + + Test1 test1,test2,test3; + test1.ts = "100"; + test2.ts = "200"; + test3.ts = "300"; + + Test1 testA, testC; + testA.tt = &test1; + testA.ts = "test123"; + testA.tvt.push_back(&test2); + testA.tvt.push_back(&test3); + + Test1 testB = testA; + testB.ts = "400"; + testB.tvt.pop_back(); + + std::pair tPairIn(10,true); + std::pair tPairOut; + + std::vector tVector1In ={1,2,3,4,5}; + std::vector tVector1Out; + + std::pair p1(10,1); + std::pair p2(1,0); + std::pair p3(10000,1); + std::vector > tVector2In ={p1,p2,p3}; + std::vector > tVector2Out; + + std::set > tSetIn ={p1,p2,p3}; + std::set > tSetOut; + + std::map tMapIn ={p1,p2,p3}; + std::map tMapOut; + + Eigen::Matrix tDenseMatrixIn; + tDenseMatrixIn << Eigen::Matrix::Random(); + tDenseMatrixIn.coeffRef(0,0) = 1.00001; + Eigen::Matrix tDenseMatrixOut; + + Eigen::Matrix tDenseRowMatrixIn; + tDenseRowMatrixIn << Eigen::Matrix::Random(); + Eigen::Matrix tDenseRowMatrixOut; + + Eigen::SparseMatrix tSparseMatrixIn; + tSparseMatrixIn.resize(3,3); + tSparseMatrixIn.insert(0,0) = 1.3; + tSparseMatrixIn.insert(1,1) = 10.2; + tSparseMatrixIn.insert(2,2) = 100.1; + tSparseMatrixIn.finalize(); + Eigen::SparseMatrix tSparseMatrixOut; + + // binary serialization + + serialize(tbIn,file); + deserialize(tbOut,file); + assert(tbIn == tbOut); + + serialize(tcIn,file); + deserialize(tcOut,file); + assert(tcIn == tcOut); + + serialize(tucIn,file); + deserialize(tucOut,file); + assert(tucIn == tucOut); + + serialize(tsIn,file); + deserialize(tsOut,file); + assert(tsIn == tsOut); + + serialize(tiIn,file); + deserialize(tiOut,file); + assert(tiIn == tiOut); + + serialize(tuiIn,file); + deserialize(tuiOut,file); + assert(tuiIn == tuiOut); + + serialize(tfIn,file); + deserialize(tfOut,file); + assert(tfIn == tfOut); + + serialize(tdIn,file); + deserialize(tdOut,file); + assert(tdIn == tdOut); + + serialize(tinpIn,file); + deserialize(tinpOut,file); + assert(tinpIn == tinpOut); + + serialize(tfpIn,file); + deserialize(tfpOut,file); + assert(*tfpIn == *tfpOut); + tfpOut = NULL; + + serialize(tstrIn,file); + deserialize(tstrOut,file); + assert(tstrIn == tstrOut); + + // updating + serialize(tbIn,"tb",file,true); + serialize(tcIn,"tc",file); + serialize(tiIn,"ti",file); + tiIn++; + serialize(tiIn,"ti",file); + tiIn++; + serialize(tiIn,"ti",file); + deserialize(tbOut,"tb",file); + deserialize(tcOut,"tc",file); + deserialize(tiOut,"ti",file); + assert(tbIn == tbOut); + assert(tcIn == tcOut); + assert(tiIn == tiOut); + + serialize(tsIn,"tsIn",file,true); + serialize(tVector1In,"tVector1In",file); + serialize(tVector2In,"tsIn",file); + deserialize(tVector2Out,"tsIn",file); + for(unsigned int i=0;its == testC.tvt[i]->ts); + assert(testB.tvt[i]->tvt.size() == testC.tvt[i]->tvt.size()); + assert(testB.tvt[i]->tt == testC.tvt[i]->tt); + } + assert(testB.tt->ts == testC.tt->ts); + assert(testB.tt->tvt.size() == testC.tt->tvt.size()); + assert(testB.tt->tt == testC.tt->tt); + testC = Test1(); + + // big data test + /*std::vector > bigDataIn,bigDataOut; + for(unsigned int i=0;i<10000;i++) + { + std::vector v; + for(unsigned int j=0;j<10000;j++) + { + v.push_back(j); + } + bigDataIn.push_back(v); + } + + Timer timer; + timer.start(); + serialize(bigDataIn,file); + timer.stop(); + std::cout << "ser: " << timer.getElapsedTimeInMilliSec() << std::endl; + + timer.start(); + deserialize(bigDataOut,file); + timer.stop(); + std::cout << "des: " << timer.getElapsedTimeInMilliSec() << std::endl; + char c; + std::cin >> c; */ + + // xml serialization + + serialize_xml(tbIn,file); + deserialize_xml(tbOut,file); + assert(tbIn == tbOut); + + serialize_xml(tcIn,file); + deserialize_xml(tcOut,file); + assert(tcIn == tcOut); + + serialize_xml(tucIn,file); + deserialize_xml(tucOut,file); + assert(tucIn == tucOut); + + serialize_xml(tsIn,file); + deserialize_xml(tsOut,file); + assert(tsIn == tsOut); + + serialize_xml(tiIn,file); + deserialize_xml(tiOut,file); + assert(tiIn == tiOut); + + serialize_xml(tuiIn,file); + deserialize_xml(tuiOut,file); + assert(tuiIn == tuiOut); + + serialize_xml(tfIn,file); + deserialize_xml(tfOut,file); + assert(tfIn == tfOut); + + serialize_xml(tdIn,file); + deserialize_xml(tdOut,file); + assert(tdIn == tdOut); + + serialize_xml(tinpIn,file); + deserialize_xml(tinpOut,file); + assert(tinpIn == tinpOut); + + serialize_xml(tfpIn,file); + deserialize_xml(tfpOut,file); + assert(*tfpIn == *tfpOut); + + serialize_xml(tstrIn,file); + deserialize_xml(tstrOut,file); + assert(tstrIn == tstrOut); + + // updating + serialize_xml(tbIn,"tb",file,false,true); + serialize_xml(tcIn,"tc",file); + serialize_xml(tiIn,"ti",file); + tiIn++; + serialize_xml(tiIn,"ti",file); + tiIn++; + serialize_xml(tiIn,"ti",file); + deserialize_xml(tbOut,"tb",file); + deserialize_xml(tcOut,"tc",file); + deserialize_xml(tiOut,"ti",file); + assert(tbIn == tbOut); + assert(tcIn == tcOut); + assert(tiIn == tiOut); + + serialize_xml(tsIn,"tsIn",file,false,true); + serialize_xml(tVector1In,"tVector1In",file); + serialize_xml(tVector2In,"tsIn",file); + deserialize_xml(tVector2Out,"tsIn",file); + for(unsigned int i=0;its == testC.tvt[i]->ts); + assert(testB.tvt[i]->tvt.size() == testC.tvt[i]->tvt.size()); + assert(testB.tvt[i]->tt == testC.tvt[i]->tt); + } + assert(testB.tt->ts == testC.tt->ts); + assert(testB.tt->tvt.size() == testC.tt->tvt.size()); + assert(testB.tt->tt == testC.tt->tt); + + // big data test + /*std::vector > bigDataIn,bigDataOut; + for(unsigned int i=0;i<10000;i++) + { + std::vector v; + for(unsigned int j=0;j<10000;j++) + { + v.push_back(j); + } + bigDataIn.push_back(v); + } + + Timer timer; + timer.start(); + serialize_xml(bigDataIn,"bigDataIn",file,seRIALIZE_BINARY); + timer.stop(); + std::cout << "ser: " << timer.getElapsedTimeInMilliSec() << std::endl; + + timer.start(); + deserialize_xml(bigDataOut,"bigDataIn",file); + timer.stop(); + std::cout << "des: " << timer.getElapsedTimeInMilliSec() << std::endl; + char c; + std::cin >> c;*/ + + std::cout << "All tests run successfully!\n"; + } + } +} + +//#endif diff --git a/src/igl/xml/serialize_xml.cpp b/src/igl/xml/serialize_xml.cpp new file mode 100644 index 000000000..ee6ea11bc --- /dev/null +++ b/src/igl/xml/serialize_xml.cpp @@ -0,0 +1,912 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2014 Christian Schüller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. + +#include "serialize_xml.h" +#include "../STR.h" +#include "../serialize.h" +#include "XMLSerializable.h" + +#include +#include +#include + +namespace igl +{ + namespace xml + { + template + IGL_INLINE void serialize_xml( + const T& obj, + const std::string& filename) + { + serialize_xml(obj,"object",filename,false,true); + } + + template + IGL_INLINE void serialize_xml( + const T& obj, + const std::string& objectName, + const std::string& filename, + bool binary, + bool overwrite) + { + tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); + + if(overwrite == false) + { + // Check if file exists + tinyxml2::XMLError error = doc->LoadFile(filename.c_str()); + if(error != tinyxml2::XML_SUCCESS) + { + doc->Clear(); + } + } + + tinyxml2::XMLElement* element = doc->FirstChildElement("serialization"); + if(element == NULL) + { + element = doc->NewElement("serialization"); + doc->InsertEndChild(element); + } + + serialize_xml(obj,objectName,doc,element,binary); + + // Save + tinyxml2::XMLError error = doc->SaveFile(filename.c_str()); + if(error != tinyxml2::XML_SUCCESS) + { + doc->PrintError(); + } + + delete doc; + } + + template + IGL_INLINE void serialize_xml( + const T& obj, + const std::string& objectName, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element, + bool binary) + { + static_assert( + serialization_xml::is_serializable::value, + "'igl::xml::serialize_xml': type is not serializable"); + + std::string name(objectName); + serialization_xml::encodeXMLElementName(name); + + tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + + if(child != NULL) + element->DeleteChild(child); + + child = doc->NewElement(name.c_str()); + element->InsertEndChild(child); + + if(binary) + { + std::vector buffer; + serialize(obj,name,buffer); + std::string data = + serialization_xml::base64_encode( + reinterpret_cast( + buffer.data()),buffer.size()); + + child->SetAttribute("binary",true); + + serialization_xml::serialize(data,doc,element,name); + } + else + { + serialization_xml::serialize(obj,doc,element,name); + } + } + + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& filename) + { + deserialize_xml(obj,"object",filename); + } + + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& objectName,const std::string& filename) + { + tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); + + tinyxml2::XMLError error = doc->LoadFile(filename.c_str()); + if(error != tinyxml2::XML_SUCCESS) + { + std::cerr << "File not found!" << std::endl; + doc->PrintError(); + doc = NULL; + } + else + { + tinyxml2::XMLElement* element = doc->FirstChildElement("serialization"); + if(element == NULL) + { + std::cerr << "Name of object not found! Initialized with default value." << std::endl; + obj = T(); + } + else + { + deserialize_xml(obj,objectName,doc,element); + } + + delete doc; + } + } + + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& objectName,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element) + { + static_assert(serialization::is_serializable::value,"'igl::xml::deserialize_xml': type is not deserializable"); + + std::string name(objectName); + serialization_xml::encodeXMLElementName(name); + + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + bool isBinary = false; + const tinyxml2::XMLAttribute* attr = child->FindAttribute("binary"); + if(attr != NULL) + { + std::string code; + serialization_xml::deserialize(code,doc,element,name); + std::string decoded = serialization_xml::base64_decode(code); + + std::vector buffer; + std::copy(decoded.c_str(),decoded.c_str()+decoded.length(),std::back_inserter(buffer)); + + deserialize(obj,name,buffer); + } + else + { + serialization_xml::deserialize(obj,doc,element,name); + } + } + } + + namespace serialization_xml + { + // fundamental types + + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* child = getElement(doc,element,name.c_str()); + child->SetAttribute("val",obj); + } + + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + getAttribute(child->Attribute("val"),obj); + } + else + { + obj = T(); + } + } + + // std::string + + IGL_INLINE void serialize(const std::string& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* child = getElement(doc,element,name.c_str()); + child->SetAttribute("val",obj.c_str()); + } + + IGL_INLINE void deserialize(std::string& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + getAttribute(child->Attribute("val"),obj); + } + else + { + obj = std::string(""); + } + } + + // Serializable + + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + // Serialize object implementing Serializable interface + const XMLSerializableBase& object = dynamic_cast(obj); + + tinyxml2::XMLElement* child = getElement(doc,element,name.c_str()); + object.Serialize(doc,child); + } + + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + + if(child != NULL) + { + obj.Deserialize(doc,child); + } + else + { + obj = T(); + } + } + + // STL containers + + template + IGL_INLINE void serialize(const std::pair& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* pair = getElement(doc,element,name.c_str()); + serialize(obj.first,doc,pair,"first"); + serialize(obj.second,doc,pair,"second"); + } + + template + IGL_INLINE void deserialize(std::pair& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + deserialize(obj.first,doc,child,"first"); + deserialize(obj.second,doc,child,"second"); + } + else + { + obj.first = T1(); + obj.second = T2(); + } + } + + template + IGL_INLINE void serialize(const std::vector& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* vector = getElement(doc,element,name.c_str()); + vector->SetAttribute("size",(unsigned int)obj.size()); + + std::stringstream num; + for(unsigned int i=0;i + IGL_INLINE void deserialize(std::vector& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + obj.clear(); + + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + unsigned int size = child->UnsignedAttribute("size"); + obj.resize(size); + + std::stringstream num; + for(unsigned int i=0;i + IGL_INLINE void serialize(const std::set& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* set = getElement(doc,element,name.c_str()); + set->SetAttribute("size",(unsigned int)obj.size()); + + std::stringstream num; + typename std::set::iterator iter = obj.begin(); + for(int i=0;iter!=obj.end();iter++,i++) + { + num.str(""); + num << "value" << i; + serialize((T)*iter,doc,set,num.str()); + } + } + + template + IGL_INLINE void deserialize(std::set& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + obj.clear(); + + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + unsigned int size = child->UnsignedAttribute("size"); + + std::stringstream num; + typename std::set::iterator iter = obj.begin(); + for(int i=0;i + IGL_INLINE void serialize(const std::map& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* map = getElement(doc,element,name.c_str()); + map->SetAttribute("size",(unsigned int)obj.size()); + + std::stringstream num; + typename std::map::const_iterator iter = obj.cbegin(); + for(int i=0;iter!=obj.end();iter++,i++) + { + num.str(""); + num << "value" << i; + serialize((std::pair)*iter,doc,map,num.str()); + } + } + + template + IGL_INLINE void deserialize(std::map& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + obj.clear(); + + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + unsigned int size = child->UnsignedAttribute("size"); + + std::stringstream num; + typename std::map::iterator iter = obj.begin(); + for(int i=0;i pair; + deserialize(pair,doc,child,num.str()); + obj.insert(pair); + } + } + else + { + obj.clear(); + } + } + + template + IGL_INLINE void serialize( + const Eigen::Matrix& obj, + const std::string& name, + const std::function& to_string, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element) + { + tinyxml2::XMLElement* matrix = getElement(doc,element,name.c_str()); + + const unsigned int rows = obj.rows(); + const unsigned int cols = obj.cols(); + + matrix->SetAttribute("rows",rows); + matrix->SetAttribute("cols",cols); + + std::stringstream ms; + ms << "\n"; + for(unsigned int r=0;r 1) + mString[mString.size()-2] = '\0'; + + matrix->SetAttribute("matrix",mString.c_str()); + } + + // Eigen types + template + IGL_INLINE void serialize( + const Eigen::Matrix& obj, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element, + const std::string& name) + { + const std::function to_string = + [](const T & v)->std::string + { + return + STR(std::setprecision(std::numeric_limits::digits10+2)< + IGL_INLINE void deserialize( + const tinyxml2::XMLDocument* doc, + const tinyxml2::XMLElement* element, + const std::string& name, + const std::function & from_string, + Eigen::Matrix& obj) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + bool initialized = false; + if(child != NULL) + { + const unsigned int rows = child->UnsignedAttribute("rows"); + const unsigned int cols = child->UnsignedAttribute("cols"); + + if(rows > 0 && cols > 0) + { + obj.resize(rows,cols); + + const tinyxml2::XMLAttribute* attribute = child->FindAttribute("matrix"); + if(attribute != NULL) + { + std::string matTemp; + getAttribute(attribute->Value(),matTemp); + + std::string line,srows,scols; + std::stringstream mats; + mats << matTemp; + + int r=0; + std::string val; + // for each line + getline(mats,line); + while(getline(mats,line)) + { + // get current line + std::stringstream liness(line); + + for(unsigned int c=0;c(); + } + } + + template + IGL_INLINE void deserialize( + Eigen::Matrix& obj, + const tinyxml2::XMLDocument* doc, + const tinyxml2::XMLElement* element, + const std::string& name) + { + const std::function & from_string = + [](const std::string & s,T & v) + { + getAttribute(s.c_str(),v); + }; + deserialize(doc,element,name,from_string,obj); + } + + template + IGL_INLINE void serialize(const Eigen::SparseMatrix& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* matrix = getElement(doc,element,name.c_str()); + + const unsigned int rows = obj.rows(); + const unsigned int cols = obj.cols(); + + matrix->SetAttribute("rows",rows); + matrix->SetAttribute("cols",cols); + + char buffer[200]; + std::stringstream ms; + ms << "\n"; + for(int k=0;k::InnerIterator it(obj,k);it;++it) + { + tinyxml2::XMLUtil::ToStr(it.value(),buffer,200); + ms << it.row() << "," << it.col() << "," << buffer << "\n"; + } + } + + std::string mString = ms.str(); + if(mString.size() > 0) + mString[mString.size()-1] = '\0'; + + matrix->SetAttribute("matrix",mString.c_str()); + } + + template + IGL_INLINE void deserialize(Eigen::SparseMatrix& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + bool initialized = false; + if(child != NULL) + { + const unsigned int rows = child->UnsignedAttribute("rows"); + const unsigned int cols = child->UnsignedAttribute("cols"); + + if(rows > 0 && cols > 0) + { + obj.resize(rows,cols); + obj.setZero(); + + const tinyxml2::XMLAttribute* attribute = child->FindAttribute("matrix"); + if(attribute != NULL) + { + std::string matTemp; + getAttribute(attribute->Value(),matTemp); + + std::string line,srows,scols; + std::stringstream mats; + mats << matTemp; + + std::vector > triplets; + int r=0; + std::string val; + + // for each line + getline(mats,line); + while(getline(mats,line)) + { + // get current line + std::stringstream liness(line); + + // row + getline(liness,val,','); + int row = atoi(val.c_str()); + // col + getline(liness,val,','); + int col = atoi(val.c_str()); + // val + getline(liness,val); + T value; + getAttribute(val.c_str(),value); + + triplets.push_back(Eigen::Triplet(row,col,value)); + + r++; + } + + obj.setFromTriplets(triplets.begin(),triplets.end()); + initialized = true; + } + } + } + + if(!initialized) + { + obj = Eigen::SparseMatrix(); + } + } + + // pointers + + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* pointer = getElement(doc,element,name.c_str()); + + bool isNullPtr = (obj == NULL); + + pointer->SetAttribute("isNullPtr",isNullPtr); + + if(isNullPtr == false) + serialization_xml::serialize(*obj,doc,element,name); + } + + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name) + { + const tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child != NULL) + { + bool isNullPtr = child->BoolAttribute("isNullPtr"); + + if(isNullPtr) + { + if(obj != NULL) + { + std::cout << "deserialization: possible memory leak for '" << typeid(obj).name() << "'" << std::endl; + obj = NULL; + } + } + else + { + if(obj != NULL) + std::cout << "deserialization: possible memory leak for '" << typeid(obj).name() << "'" << std::endl; + + obj = new typename std::remove_pointer::type(); + + serialization_xml::deserialize(*obj,doc,element,name); + } + } + } + + // helper functions + + IGL_INLINE tinyxml2::XMLElement* getElement(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name) + { + tinyxml2::XMLElement* child = element->FirstChildElement(name.c_str()); + if(child == NULL) + { + child = doc->NewElement(name.c_str()); + element->InsertEndChild(child); + } + return child; + } + + IGL_INLINE void getAttribute(const char* src,bool& dest) + { + tinyxml2::XMLUtil::ToBool(src,&dest); + } + + IGL_INLINE void getAttribute(const char* src,char& dest) + { + dest = (char)atoi(src); + } + + IGL_INLINE void getAttribute(const char* src,std::string& dest) + { + dest = src; + } + + IGL_INLINE void getAttribute(const char* src,float& dest) + { + tinyxml2::XMLUtil::ToFloat(src,&dest); + } + + IGL_INLINE void getAttribute(const char* src,double& dest) + { + tinyxml2::XMLUtil::ToDouble(src,&dest); + } + + template + IGL_INLINE typename std::enable_if::value && std::is_unsigned::value>::type getAttribute(const char* src,T& dest) + { + unsigned int val; + tinyxml2::XMLUtil::ToUnsigned(src,&val); + dest = (T)val; + } + + template + IGL_INLINE typename std::enable_if::value && !std::is_unsigned::value>::type getAttribute(const char* src,T& dest) + { + int val; + tinyxml2::XMLUtil::ToInt(src,&val); + dest = (T)val; + } + + // tinyXML2 related stuff + static const int numForbiddenChars = 8; + static const char forbiddenChars[] ={' ','/','~','#','&','>','<','='}; + + IGL_INLINE void replaceSubString(std::string& str,const std::string& search,const std::string& replace) + { + size_t pos = 0; + while((pos = str.find(search,pos)) != std::string::npos) + { + str.replace(pos,search.length(),replace); + pos += replace.length(); + } + } + + IGL_INLINE void encodeXMLElementName(std::string& name) + { + // must not start with a digit + if(isdigit(*name.begin())) + { + name = ":::" + name; + } + + std::stringstream stream; + for(int i=0;i> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + + i = 0; + } + } + + if(i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + } + + return ret; + } + + std::string base64_decode(std::string const& encoded_string) + { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4],char_array_3[3]; + std::string ret; + + // construct fast lookup table + // added by Christian Sch�ller (schuellc@inf.ethz.ch) + int charLookup[200]; + for(int i=0;i<(int)(base64_chars.length());i++) + charLookup[(int)base64_chars[i]] = i; + + while(in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if(i ==4) { + for(i = 0; i <4; i++) + char_array_4[i] = charLookup[char_array_4[i]]; // new fast lookup + //char_array_4[i] = base64_chars.find(char_array_4[i]); // original version + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for(i = 0; (i < 3); i++) + ret += char_array_3[i]; + + i = 0; + } + } + + if(i) { + for(j = i; j <4; j++) + char_array_4[j] = 0; + + for(j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for(j = 0; (j < i - 1); + j++) ret += char_array_3[j]; + } + + return ret; + } + } + } +} + +#ifdef IGL_STATIC_LIBRARY +// Explicit template instantiation +template void igl::xml::serialize_xml > >(std::vector > const&, std::basic_string, std::allocator > const&, std::basic_string, std::allocator > const&, bool, bool); +template void igl::xml::deserialize_xml > >(std::vector >&, std::basic_string, std::allocator > const&, std::basic_string, std::allocator > const&); +#endif diff --git a/src/igl/xml/serialize_xml.h b/src/igl/xml/serialize_xml.h new file mode 100644 index 000000000..2190f676a --- /dev/null +++ b/src/igl/xml/serialize_xml.h @@ -0,0 +1,247 @@ +// +// Copyright (C) 2014 Christian Sch�ller +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_XML_SERIALIZABLE_XML_H +#define IGL_XML_SERIALIZABLE_XML_H +// ----------------------------------------------------------------------------- +// Functions to save and load a serialization of fundamental c++ data types to +// and from a xml file. STL containers, Eigen matrix types and nested data +// structures are also supported. To serialize a user defined class implement +// the interface XMLSerializable or XMLSerializableBase. +// +// See also: serialize.h +// ----------------------------------------------------------------------------- + +#include "../igl_inline.h" + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +//#define SERIALIZE_XML(x) igl::xml::serialize_xml(x,#x,doc,element); +//#define DESERIALIZE_XML(x) igl::xml::deserialize_xml(x,#x,,doc,element); + +namespace igl +{ + namespace xml + { + struct XMLSerializableBase; + // serializes the given object either to a xml file or to the provided doc data + // + // Templates: + // T type of the object to serialize + // Inputs: + // obj object to serialize + // objectName unique object name,used for the identification + // filename name of the file containing the serialization + // binary set to true to serialize the object in binary format (faster for big data) + // overwrite set to true to overwrite an existing xml file + // element tinyxml2 virtual representation of the current xml node + // Outputs: + // doc contains current tinyxml2 virtual representation of the xml data + // + template + IGL_INLINE void serialize_xml(const T& obj,const std::string& filename); + template + IGL_INLINE void serialize_xml( + const T& obj, + const std::string& objectName, + const std::string& filename, + bool binary = false, + bool overwrite = false); + template + IGL_INLINE void serialize_xml( + const T& obj, + const std::string& objectName, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element, + bool binary = false); + + // deserializes the given data from a xml file or doc data back to the provided object + // + // Templates: + // T type of the object to serialize + // Inputs: + // + // objectName unique object name,used for the identification + // filename name of the file containing the serialization + // binary set to true to serialize the object in binary format (faster for big data) + // overwrite set to true to overwrite an existing xml file + // doc contains current tinyxml2 virtual representation of the xml data + // element tinyxml2 virtual representation of the current xml node + // Outputs: + // obj object to load back serialization to + // + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& filename); + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& objectName,const std::string& filename); + template + IGL_INLINE void deserialize_xml(T& obj,const std::string& objectName,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element); + + // internal functions + namespace serialization_xml + { + // fundamental types + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // std::string + IGL_INLINE void serialize(const std::string& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + IGL_INLINE void deserialize(std::string& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // XMLSerializableBase + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // STL containers + template + IGL_INLINE void serialize(const std::pair& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE void deserialize(std::pair& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + template + IGL_INLINE void serialize(const std::vector& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE void deserialize(std::vector& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + template + IGL_INLINE void serialize(const std::set& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE void deserialize(std::set& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + template + IGL_INLINE void serialize(const std::map& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE void deserialize(std::map& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // Eigen types + + // Serialize a Dense Eigen Matrix to xml (in the matrix= attribute, + // awkward...) + // + // Inputs: + // obj MR by MC matrix of T types + // name name of matrix + // to_string function converting T to string + // Outputs: + // doc pointer to xml document + // element pointer to xml element + // + template + IGL_INLINE void serialize( + const Eigen::Matrix& obj, + const std::string& name, + const std::function& to_string, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element); + // De-Serialize a Dense Eigen Matrix from xml (in the matrix= attribute, + // awkward...) + // + // Inputs: + // doc pointer to xml document + // element pointer to xml element + // name name of matrix + // from_string function string to T + // Outputs: + // obj MR by MC matrix of T types + template + IGL_INLINE void deserialize( + const tinyxml2::XMLDocument* doc, + const tinyxml2::XMLElement* element, + const std::string& name, + const std::function & from_string, + Eigen::Matrix& obj); + + // Legacy APIs + template + IGL_INLINE void serialize( + const Eigen::Matrix& obj, + tinyxml2::XMLDocument* doc, + tinyxml2::XMLElement* element, + const std::string& name); + template + IGL_INLINE void deserialize( + Eigen::Matrix& obj, + const tinyxml2::XMLDocument* doc, + const tinyxml2::XMLElement* element, + const std::string& name); + + template + IGL_INLINE void serialize(const Eigen::SparseMatrix& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE void deserialize(Eigen::SparseMatrix& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // raw pointers + template + IGL_INLINE typename std::enable_if::value>::type serialize(const T& obj,tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + template + IGL_INLINE typename std::enable_if::value>::type deserialize(T& obj,const tinyxml2::XMLDocument* doc,const tinyxml2::XMLElement* element,const std::string& name); + + // helper functions + tinyxml2::XMLElement* getElement(tinyxml2::XMLDocument* doc,tinyxml2::XMLElement* element,const std::string& name); + IGL_INLINE void getAttribute(const char* src,bool& dest); + IGL_INLINE void getAttribute(const char* scr,char& dest); + IGL_INLINE void getAttribute(const char* src,std::string& dest); + IGL_INLINE void getAttribute(const char* src,float& dest); + IGL_INLINE void getAttribute(const char* src,double& dest); + template + IGL_INLINE typename std::enable_if::value && std::is_unsigned::value>::type getAttribute(const char* src,T& dest); + template + IGL_INLINE typename std::enable_if::value && !std::is_unsigned::value>::type getAttribute(const char* src,T& dest); + IGL_INLINE void replaceSubString(std::string& str,const std::string& search,const std::string& replace); + IGL_INLINE void encodeXMLElementName(std::string& name); + IGL_INLINE void decodeXMLElementName(std::string& name); + IGL_INLINE std::string base64_encode(unsigned char const* bytes_to_encode,unsigned int in_len); + IGL_INLINE std::string base64_decode(std::string const& encoded_string); + + // compile time type serializable check + template + struct is_stl_container { static const bool value = false; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + template + struct is_stl_container > { static const bool value = true; }; + + template + struct is_eigen_type { static const bool value = false; }; + template + struct is_eigen_type > { static const bool value = true; }; + template + struct is_eigen_type > { static const bool value = true; }; + + template + struct is_serializable { + using T0 = typename std::remove_pointer::type; + static const bool value = std::is_fundamental::value || std::is_same::value || std::is_base_of::value + || is_stl_container::value || is_eigen_type::value; + }; + } + } +} + +#ifndef IGL_STATIC_LIBRARY + #include "serialize_xml.cpp" +#endif + +#endif diff --git a/src/igl/xml/writeDAE.cpp b/src/igl/xml/writeDAE.cpp new file mode 100644 index 000000000..2c2bb4301 --- /dev/null +++ b/src/igl/xml/writeDAE.cpp @@ -0,0 +1,138 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "writeDAE.h" +#include "../STR.h" +#include +#include +#include + +template +IGL_INLINE bool igl::xml::writeDAE( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F) +{ + using namespace std; + using namespace Eigen; + using namespace tinyxml2; + + XMLDocument* doc = new XMLDocument(); + + const auto & ele = [&doc]( + const std::string tag, + // Can't just use `{}` as the default argument because of a clang-bug + // http://stackoverflow.com/questions/17264067/ + const std::map attribs = + std::map(), + const std::string text="", + const std::list children = + std::list() + )->XMLElement * + { + XMLElement * element = doc->NewElement(tag.c_str()); + for(const auto & key_value : attribs) + { + element->SetAttribute(key_value.first.c_str(),key_value.second.c_str()); + } + if(!text.empty()) + { + element->InsertEndChild(doc->NewText(text.c_str())); + } + for(auto & child : children) + { + element->InsertEndChild(child); + } + return element; + }; + + Eigen::IOFormat row_format(Eigen::FullPrecision,0," "," ","","",""); + doc->InsertEndChild( + ele("COLLADA", + { + {"xmlns","http://www.collada.org/2005/11/COLLADASchema"}, + {"version","1.4.1"} + }, + "", + { + ele("asset",{},"", + { + ele("unit",{{"meter","0.0254000"},{"name","inch"}}), + ele("up_axis",{},"Y_UP") + }), + ele("library_visual_scenes",{},"", + { + ele("visual_scene",{{"id","ID2"}},"", + { + ele("node",{{"name","SketchUp"}},"", + { + ele("node",{{"id","ID3"},{"name","group_0"}},"", + { + ele("matrix",{},"1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"), + ele("instance_geometry",{{"url","#ID4"}},"", + { + ele("bind_material",{},"",{ele("technique_common")}), + }), + }), + }), + }), + }), + ele("library_geometries",{},"", + { + ele("geometry",{{"id","ID4"}},"", + { + ele("mesh",{},"", + { + ele("source",{{"id","ID7"}},"", + { + ele( + "float_array", + {{"count",STR(V.size())},{"id","ID10"}}, + STR(V.format(row_format))), + ele("technique_common",{},"", + { + ele( + "accessor", + {{"count",STR(V.rows())},{"source","#ID8"},{"stride","3"}}, + "", + { + ele("param",{{"name","X"},{"type","float"}}), + ele("param",{{"name","Y"},{"type","float"}}), + ele("param",{{"name","Z"},{"type","float"}}), + }) + }) + }), + ele( + "vertices", + {{"id","ID9"}}, + "", + {ele("input",{{"semantic","POSITION"},{"source","#ID7"}})}), + ele( + "triangles", + {{"count",STR(F.rows())}}, + "", + { + ele("input",{{"semantic","VERTEX"},{"source","#ID9"}}), + ele("p",{},STR(F.format(row_format))), + }) + }) + }) + }), + ele("scene",{},"",{ele("instance_visual_scene",{{"url","#ID2"}})}), + })); + // tinyxml2 seems **not** to print the header by default, but it + // also seems that that's OK + XMLError error = doc->SaveFile(filename.c_str()); + bool ret = true; + if(error != XML_NO_ERROR) + { + doc->PrintError(); + ret = false; + } + delete doc; + return ret; +} diff --git a/src/igl/xml/writeDAE.h b/src/igl/xml/writeDAE.h new file mode 100644 index 000000000..731fe7255 --- /dev/null +++ b/src/igl/xml/writeDAE.h @@ -0,0 +1,38 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_XML_WRITEDAE_H +#define IGL_XML_WRITEDAE_H +#include "../igl_inline.h" +#include +#include +namespace igl +{ + namespace xml + { + // Write a mesh to a Collada .dae scene file. The resulting scene contains + // a single "geometry" suitable for solid operaions (boolean union, + // intersection, etc.) in SketchUp. + // + // Inputs: + // filename path to .dae file + // V #V by 3 list of vertex positions + // F #F by 3 list of face indices + // Returns true iff success + // + template + IGL_INLINE bool writeDAE( + const std::string & filename, + const Eigen::PlainObjectBase & V, + const Eigen::PlainObjectBase & F); + } +} + +#ifndef IGL_STATIC_LIBRARY +#include "writeDAE.cpp" +#endif +#endif diff --git a/src/igl/xml/write_triangle_mesh.cpp b/src/igl/xml/write_triangle_mesh.cpp new file mode 100644 index 000000000..1f3d54b8d --- /dev/null +++ b/src/igl/xml/write_triangle_mesh.cpp @@ -0,0 +1,33 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include "write_triangle_mesh.h" +#include "../write_triangle_mesh.h" +#include "../pathinfo.h" +#include "writeDAE.h" + +template +IGL_INLINE bool igl::xml::write_triangle_mesh( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const bool ascii) +{ + using namespace std; + // dirname, basename, extension and filename + string d,b,e,f; + pathinfo(str,d,b,e,f); + // Convert extension to lower case + std::transform(e.begin(), e.end(), e.begin(), ::tolower); + if(e == "dae") + { + return writeDAE(str,V,F); + }else + { + return igl::write_triangle_mesh(str,V,F,ascii); + } +} diff --git a/src/igl/xml/write_triangle_mesh.h b/src/igl/xml/write_triangle_mesh.h new file mode 100644 index 000000000..a5f8dad69 --- /dev/null +++ b/src/igl/xml/write_triangle_mesh.h @@ -0,0 +1,45 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2016 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#ifndef IGL_XML_WRITE_TRIANGLE_MESH_H +#define IGL_XML_WRITE_TRIANGLE_MESH_H +#include "../igl_inline.h" + +#include +#include + +namespace igl +{ + namespace xml + { + // write mesh to a file with automatic detection of file format. supported: + // dae, or any of the formats supported by igl::write_triangle_mesh + // + // Templates: + // Scalar type for positions and vectors (will be read as double and cast + // to Scalar) + // Index type for indices (will be read as int and cast to Index) + // Inputs: + // str path to file + // V eigen double matrix #V by 3 + // F eigen int matrix #F by 3 + // Returns true iff success + template + IGL_INLINE bool write_triangle_mesh( + const std::string str, + const Eigen::PlainObjectBase& V, + const Eigen::PlainObjectBase& F, + const bool ascii = true); + } +} + +#ifndef IGL_STATIC_LIBRARY +# include "write_triangle_mesh.cpp" +#endif + +#endif + diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt new file mode 100644 index 000000000..b0c35c29d --- /dev/null +++ b/src/imgui/CMakeLists.txt @@ -0,0 +1,15 @@ +project(imgui) +cmake_minimum_required(VERSION 2.6) + +add_library(imgui STATIC + imconfig.h + imgui.h + imgui_internal.h + imstb_rectpack.h + imstb_textedit.h + imstb_truetype.h + imgui.cpp + imgui_demo.cpp + imgui_draw.cpp + imgui_widgets.cpp +) diff --git a/src/imgui/LICENSE.txt b/src/imgui/LICENSE.txt new file mode 100644 index 000000000..21b6ee7e2 --- /dev/null +++ b/src/imgui/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 Omar Cornut + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h new file mode 100644 index 000000000..50b7f471c --- /dev/null +++ b/src/imgui/imconfig.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. +// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) +// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" +// If you do so you need to make sure that configuration settings are defined consistently _everywhere_ dear imgui is used, which include +// the imgui*.cpp files but also _any_ of your code that uses imgui. This is because some compile-time options have an affect on data structures. +// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. +// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. +//#define IMGUI_API __declspec( dllexport ) +//#define IMGUI_API __declspec( dllimport ) + +//---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) +//---- It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in imgui_demo.cpp. +//#define IMGUI_DISABLE_DEMO_WINDOWS + +//---- Don't implement some functions to reduce linkage requirements. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself if you don't want to link with vsnprintf. +//#define IMGUI_DISABLE_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare your prototypes in imconfig.h. +//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). + +//---- Include imgui_user.h at the end of imgui.h as a convenience +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version +// By default the embedded implementations are declared static and not available outside of imgui cpp files. +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + +//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (default is 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. +//#define ImDrawIdx unsigned int + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/src/imgui/imgui.cpp b/src/imgui/imgui.cpp new file mode 100644 index 000000000..2822e3e23 --- /dev/null +++ b/src/imgui/imgui.cpp @@ -0,0 +1,9057 @@ +// dear imgui, v1.66 WIP +// (main code and documentation) + +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui +// Releases change-log at https://github.com/ocornut/imgui/releases +// Technical Support for Getting Started https://discourse.dearimgui.org/c/getting-started +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// This library is free but I need your support to sustain development and maintenance. +// If you work for a company, please consider financial support, see README. For individuals: https://www.patreon.com/imgui + +// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. +// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without +// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't +// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you +// to a better solution or official support for them. + +/* + +Index of this file: + +DOCUMENTATION + +- MISSION STATEMENT +- END-USER GUIDE +- PROGRAMMER GUIDE (read me!) + - Read first + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine + - This is how a simple application may look like (2 variations) + - This is how a simple rendering function may look like + - Using gamepad/keyboard navigation controls +- API BREAKING CHANGES (read me when you update!) +- FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + - How can I tell whether to dispatch mouse/keyboard to imgui or to my application? + - How can I display an image? What is ImTextureID, how does it works? + - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack. + - How can I use my own math types instead of ImVec2/ImVec4? + - How can I load a different font than the default? + - How can I easily use icons in my application? + - How can I load multiple fonts? + - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - How can I help? + +CODE +(search for "[SECTION]" in the code to find them) + +// [SECTION] FORWARD DECLARATIONS +// [SECTION] CONTEXT AND MEMORY ALLOCATORS +// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) +// [SECTION] MISC HELPER/UTILITIES (ImText* functions) +// [SECTION] MISC HELPER/UTILITIES (Color functions) +// [SECTION] ImGuiStorage +// [SECTION] ImGuiTextFilter +// [SECTION] ImGuiTextBuffer +// [SECTION] ImGuiListClipper +// [SECTION] RENDER HELPERS +// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] TOOLTIPS +// [SECTION] POPUPS +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +// [SECTION] COLUMNS +// [SECTION] DRAG AND DROP +// [SECTION] LOGGING/CAPTURING +// [SECTION] SETTINGS +// [SECTION] PLATFORM DEPENDENT HELPERS +// [SECTION] METRICS/DEBUG WINDOW + +*/ + +//----------------------------------------------------------------------------- +// DOCUMENTATION +//----------------------------------------------------------------------------- + +/* + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools + - Easy to hack and improve + - Minimize screen real-estate usage + - Minimize setup and maintenance + - Minimize state storage on user side + - Portable, minimize dependencies, run on target (consoles, phones, etc.) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, + opening a tree node for the first time, etc. but a typical frame should not allocate anything) + + Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: + - Doesn't look fancy, doesn't animate + - Limited layout features, intricate layouts are typically crafted in code + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard. + - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW + + + PROGRAMMER GUIDE + ================ + + READ FIRST + + - Read the FAQ below this section! + - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction + or destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, less bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - You can learn about immediate-mode GUI principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 + See README.md for more links describing the IMGUI paradigm. Dear ImGui is an implementation of the IMGUI paradigm. + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + + - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) + - Or maintain your own branch where you have imconfig.h modified. + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed + from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will + likely be a comment about it. Please report any issue to the GitHub page! + - Try to keep your copy of dear imgui reasonably up to date. + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + + - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library. + - Add the Dear ImGui source files to your projects or using your preferred build system. + It is recommended you build and statically link the .cpp files as part of your project and not as shared library (DLL). + - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating imgui types with your own maths types. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide. + Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" + phases of your own application. All rendering informatioe are stored into command-lists that you will retrieve after calling ImGui::Render(). + - Refer to the bindings and demo applications in the examples/ folder for instruction on how to setup your code. + - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_*** files from the examples/ folder. + + HOW A SIMPLE APPLICATION MAY LOOK LIKE + EXHIBIT 1: USING THE EXAMPLE BINDINGS (imgui_impl_XXX.cpp files from the examples/ folder) + + // Application init: create a dear imgui context, setup some options, load fonts + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. + // TODO: Fill optional fields of the io structure later. + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + + // Initialize helper Platform and Renderer bindings (here we are using imgui_impl_win32 and imgui_impl_dx11) + ImGui_ImplWin32_Init(hwnd); + ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); + + // Application main loop + while (true) + { + // Feed inputs to dear imgui, start new frame + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + // Any application code here + ImGui::Text("Hello, world!"); + + // Render dear imgui into screen + ImGui::Render(); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + g_pSwapChain->Present(1, 0); + } + + // Shutdown + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + HOW A SIMPLE APPLICATION MAY LOOK LIKE + EXHIBIT 2: IMPLEMENTING CUSTOM BINDING / CUSTOM ENGINE + + // Application init: create a dear imgui context, setup some options, load fonts + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. + // TODO: Fill optional fields of the io structure later. + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + + // Build and load the texture atlas into a texture + // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) + int width, height; + unsigned char* pixels = NULL; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + // At this point you've got the texture data and you need to upload that your your graphic system: + // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. + // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ below for details about ImTextureID. + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) + io.Fonts->TexID = (void*)texture; + + // Application main loop + while (true) + { + // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc. + // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform bindings) + io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds) + io.DisplaySize.x = 1920.0f; // set the current display width + io.DisplaySize.y = 1280.0f; // set the current display height here + io.MousePos = my_mouse_pos; // set the mouse position + io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states + io.MouseDown[1] = my_mouse_buttons[1]; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use imgui everywhere) + ImGui::NewFrame(); + + // Most of your application code here + ImGui::Text("Hello, world!"); + MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any ImGui functions as well! + + // Render imgui, swap buffers + // (You want to try calling EndFrame/Render as late as you can, to be able to use imgui in your own game rendering code) + ImGui::EndFrame(); + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + MyImGuiRenderFunction(draw_data); + SwapBuffers(); + } + + // Shutdown + ImGui::DestroyContext(); + + HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + + void void MyImGuiRenderFunction(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport using draw_data->DisplaySize + // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls will use the imgui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture((MyTexture*)pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API should supports it. + // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches + // (some elements visible outside their bounds) but you can fix that once everything else works! + // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize) + // In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize. + // However, in the interest of supporting multi-viewport applications in the future (see 'viewport' branch on github), + // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space. + // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) + ImVec2 pos = draw_data->DisplayPos; + MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits in imconfig.h if your engine doesn't support 16-bits indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + - The examples/ folders contains many actual implementation of the pseudo-codes above. + - When calling NewFrame(), the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags are updated. + They tell you if Dear ImGui intends to use your inputs. When a flag is set you want to hide the corresponding inputs + from the rest of your application. In every cases you need to pass on the inputs to imgui. Refer to the FAQ for more information. + - Please read the FAQ below!. Amusingly, it is called a FAQ because people frequently run into the same issues! + + USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS + + - The gamepad/keyboard navigation is fairly functional and keeps being improved. + - Gamepad support is particularly useful to use dear imgui on a console system (e.g. PS4, Switch, XB1) without a mouse! + - You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. + - Gamepad: + - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. + - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame(). + Note that io.NavInputs[] is cleared by EndFrame(). + - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: + 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. + - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). + - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://goo.gl/9LgVZW. + - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo + to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. + - Keyboard: + - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. + NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. + - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag + will be set. For more advanced uses, you may want to read from: + - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. + - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). + - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could be improved. + - Mouse: + - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. + - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. + Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements. + When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. + When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that. + (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!) + (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want + to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. + Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. + When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. + You can read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2018/10/12 (1.66) - Renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files. + - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete). + - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. + If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths. + - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427) + - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp. + NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. + Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions. + - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent). + - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete). + - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly). + - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges to enable the feature. + - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency. + - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time. + - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete). + - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set. + - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details. + - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are not using integers internally any more. + If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format. + To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to users to upgrade their code. + If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase for e.g. "DragInt.*%f" to help you find them. + - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an equivalent and more flexible "const char* format", + consistent with other functions. Kept redirection functions (will obsolete). + - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value. + - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some binding ahead of merging the Nav branch). + - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now. + - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically. + - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums. + - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment. + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. + If your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) + { + float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; + return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); + } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + argument: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount' + ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new. + ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'. + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + font init: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); <..Upload texture to GPU..> + became: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); <..Upload texture to GPU>; io.Fonts->TexId = YourTextureIdentifier; + you now more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended that you sample the font texture with bilinear interpolation. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + ====================================== + + Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application? + A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure (e.g. if (ImGui::GetIO().WantCaptureMouse) { ... } ) + - When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Note: you should always pass your mouse/keyboard inputs to imgui, even when the io.WantCaptureXXX flag are set false. + This is because imgui needs to detect that you clicked in the void to unfocus its own windows. + Note: The 'io.WantCaptureMouse' is more accurate that any attempt to "check if the mouse is hovering a window" (don't do that!). + It handle mouse dragging correctly (both dragging that started over your application or over an imgui window) and handle e.g. modal windows blocking inputs. + Those flags are updated by ImGui::NewFrame(). Preferably read the flags after calling NewFrame() if you can afford it, but reading them before is also + perfectly fine, as the bool toggle fairly rarely. If you have on a touch device, you might find use for an early call to UpdateHoveredWindowAndCaptureFlags(). + Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) + + Q: How can I display an image? What is ImTextureID, how does it works? + A: Short explanation: + - You may use functions such as ImGui::Image(), ImGui::ImageButton() or lower-level ImDrawList::AddImage() to emit draw calls that will use your own textures. + - Actual textures are identified in a way that is up to the user/engine. Those identifiers are stored and passed as ImTextureID (void*) value. + - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). + Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward. + + Long explanation: + - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. + At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code + to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.). + - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. + We carry the information to identify a "texture" in the ImTextureID type. + ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice. + Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function. + - In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying + an image from the end-user perspective. This is what the _examples_ rendering functions are using: + + OpenGL: ImTextureID = GLuint (see ImGui_ImplGlfwGL3_RenderDrawData() function in imgui_impl_glfw_gl3.cpp) + DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() function in imgui_impl_dx9.cpp) + DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() function in imgui_impl_dx11.cpp) + DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() function in imgui_impl_dx12.cpp) + + For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. + Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure + tying together both the texture and information about its format and how to read it. + - If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about + the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase + is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them. + If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID + representation suggested by the example bindings is probably the best choice. + (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer) + + User code may do: + + // Cast our texture type to ImTextureID / void* + MyTexture* texture = g_CoffeeTableTexture; + ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height)); + + The renderer function called after ImGui::Render() will receive that same value that the user code passed: + + // Cast ImTextureID / void* stored in the draw command as our texture type + MyTexture* texture = (MyTexture*)pcmd->TextureId; + MyEngineBindTexture2D(texture); + + Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. + This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them. + If you want to display an image file (e.g. PNG file) into the screen, please refer to documentation and tutorials for the graphics API you are using. + + Here's a simplified OpenGL example using stb_image.h: + + // Use stb_image.h to load a PNG from disk and turn it into raw RGBA pixel data: + #define STB_IMAGE_IMPLEMENTATION + #include + [...] + int my_image_width, my_image_height; + unsigned char* my_image_data = stbi_load("my_image.png", &my_image_width, &my_image_height, NULL, 4); + + // Turn the RGBA pixel data into an OpenGL texture: + GLuint my_opengl_texture; + glGenTextures(1, &my_opengl_texture); + glBindTexture(GL_TEXTURE_2D, my_opengl_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data); + + // Now that we have an OpenGL texture, assuming our imgui rendering function (imgui_impl_xxx.cpp file) takes GLuint as ImTextureID, we can display it: + ImGui::Image((void*)(intptr_t)my_opengl_texture, ImVec2(my_image_width, my_image_height)); + + C/C++ tip: a void* is pointer-sized storage. You may safely store any pointer or integer into it by casting your value to ImTexture / void*, and vice-versa. + Because both end-points (user code and rendering function) are under your control, you know exactly what is stored inside the ImTexture / void*. + Examples: + + GLuint my_tex = XXX; + void* my_void_ptr; + my_void_ptr = (void*)(intptr_t)my_tex; // cast a GLuint into a void* (we don't take its address! we literally store the value inside the pointer) + my_tex = (GLuint)(intptr_t)my_void_ptr; // cast a void* into a GLuint + + ID3D11ShaderResourceView* my_dx11_srv = XXX; + void* my_void_ptr; + my_void_ptr = (void*)my_dx11_srv; // cast a ID3D11ShaderResourceView* into an opaque void* + my_dx11_srv = (ID3D11ShaderResourceView*)my_void_ptr; // cast a void* into a ID3D11ShaderResourceView* + + Finally, you may call ImGui::ShowMetricsWindow() to explore/visualize/understand how the ImDrawList are generated. + + Q: How can I have multiple widgets with the same label or without a label? + Q: I have multiple widgets with the same label, and only the first one works. Why is that? + A: A primer on labels and the ID Stack... + + Dear ImGui internally need to uniquely identify UI elements. + Elements that are typically not clickable (such as calls to the Text functions) don't need an ID. + Interactive widgets (such as calls to Button buttons) need a unique ID. + Unique ID are used internally to track active widgets and occasionally associate state to widgets. + Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element. + + - Unique ID are often derived from a string label: + + Button("OK"); // Label = "OK", ID = hash of (..., "OK") + Button("Cancel"); // Label = "Cancel", ID = hash of (..., "Cancel") + + - ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having + two buttons labeled "OK" in different windows or different tree locations is fine. + We used "..." above to signify whatever was already pushed to the ID stack previously: + + Begin("MyWindow"); + Button("OK"); // Label = "OK", ID = hash of ("MyWindow", "OK") + End(); + + - If you have a same ID twice in the same location, you'll have a conflict: + + Button("OK"); + Button("OK"); // ID collision! Interacting with either button will trigger the first one. + + Fear not! this is easy to solve and there are many ways to solve it! + + - Solving ID conflict in a simple/local context: + When passing a label you can optionally specify extra ID information within string itself. + Use "##" to pass a complement to the ID that won't be visible to the end-user. + This helps solving the simple collision cases when you know e.g. at compilation time which items + are going to be created: + + Begin("MyWindow"); + Button("Play"); // Label = "Play", ID = hash of ("MyWindow", "Play") + Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from above + Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from above + End(); + + - If you want to completely hide the label, but still need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of (..., "##On") // No visible label, just a checkbox! + + - Occasionally/rarely you might want change a label while preserving a constant ID. This allows + you to animate labels. For example you may want to include varying information in a window title bar, + but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID: + + Button("Hello###ID"; // Label = "Hello", ID = hash of (..., "ID") + Button("World###ID"; // Label = "World", ID = hash of (..., "ID") // Same as above, even though the label looks different + + sprintf(buf, "My game (%f FPS)###MyGame", fps); + Begin(buf); // Variable title, ID = hash of "MyGame" + + - Solving ID conflict in a more general manner: + Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts + within the same window. This is the most convenient way of distinguishing ID when iterating and + creating many UI elements programmatically. + You can push a pointer, a string or an integer value into the ID stack. + Remember that ID are formed from the concatenation of _everything_ in the ID stack! + + Begin("Window"); + for (int i = 0; i < 100; i++) + { + PushID(i); // Push i to the id tack + Button("Click"); // Label = "Click", ID = Hash of ("Window", i, "Click") + PopID(); + } + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj); + Button("Click"); // Label = "Click", ID = Hash of ("Window", obj pointer, "Click") + PopID(); + } + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj->Name); + Button("Click"); // Label = "Click", ID = Hash of ("Window", obj->Name, "Click") + PopID(); + } + End(); + + - More example showing that you can stack multiple prefixes into the ID stack: + + Button("Click"); // Label = "Click", ID = hash of (..., "Click") + PushID("node"); + Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click") + PushID(my_ptr); + Button("Click"); // Label = "Click", ID = hash of (..., "node", my_ptr, "Click") + PopID(); + PopID(); + + - Tree nodes implicitly creates a scope for you by calling PushID(). + + Button("Click"); // Label = "Click", ID = hash of (..., "Click") + if (TreeNode("node")) + { + Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click") + TreePop(); + } + + - When working with trees, ID are used to preserve the open/close state of each tree node. + Depending on your use cases you may want to use strings, indices or pointers as ID. + e.g. when following a single pointer that may change over time, using a static string as ID + will preserve your node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the + node open/closed state differently. See what makes more sense in your situation! + + Q: How can I use my own math types instead of ImVec2/ImVec4? + A: You can edit imconfig.h and setup the IM_VEC2_CLASS_EXTRA/IM_VEC4_CLASS_EXTRA macros to add implicit type conversions. + This way you'll be able to use your own types everywhere, e.g. passsing glm::vec2 to ImGui functions instead of ImVec2. + + Q: How can I load a different font than the default? + A: Use the font atlas to load the TTF/OTF file you want: + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + Default is ProggyClean.ttf, rendered at size 13, embedded in dear imgui's source code. + (Read the 'misc/fonts/README.txt' file for more details about font loading.) + + New programmers: remember that in C/C++ and most programming languages if you want to use a + backslash \ within a string literal, you need to write it double backslash "\\": + io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG (you are escape the M here!) + io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT + io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT + + Q: How can I easily use icons in my application? + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you + main font. Then you can refer to icons within your strings. + (Read the 'misc/fonts/README.txt' file for more details about icons font loading.) + + Q: How can I load multiple fonts? + A: Use the font atlas to pack them into a single texture: + (Read the 'misc/fonts/README.txt' file and the code in ImFontAtlas for more details.) + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + // the first loaded font gets used by default + // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime + + // Options + ImFontConfig config; + config.OversampleH = 3; + config.OversampleV = 1; + config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up + config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); + + // Combine multiple fonts into one (e.g. for icon fonts) + static ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontDefault(); + io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs + + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. + + // Add default Japanese ranges + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); + + // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) + ImVector ranges; + ImFontAtlas::GlyphRangesBuilder builder; + builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) + builder.AddChar(0x7262); // Add a specific character + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges + builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + + All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 + by using the u8"hello" syntax. Specifying literal in your source code using a local code page + (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! + Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. + + Text input: it is up to your application to pass the right character code by calling io.AddInputCharacter(). + The applications in examples/ are doing that. + Windows: you can use the WM_CHAR or WM_UNICHAR or WM_IME_CHAR message (depending if your app is built using Unicode or MultiByte mode). + You may also use MultiByteToWideChar() or ToUnicode() to retrieve Unicode codepoints from MultiByte characters or keyboard state. + Windows: if your language is relying on an Input Method Editor (IME), you copy the HWND of your window to io.ImeWindowHandle in order for + the default implementation of io.ImeSetInputScreenPosFn() to set your Microsoft IME position correctly. + + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + A: - You can create a dummy window. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags. + (The ImGuiWindowFlags_NoDecoration flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse) + Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. + - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData, + and then call your rendered code with your own ImDrawList or ImDrawData data. + + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. + A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). + Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. + + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + + Q: How can I help? + A: - If you are experienced with Dear ImGui and C++, look at the github issues, or docs/TODO.txt and see how you want/can help! + - Convince your company to sponsor/fund development! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README. + - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). + + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. + - tip: you can call Render() multiple times (e.g for VR renders). + - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +#include // toupper, isprint +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// Debug options +#define IMGUI_DEBUG_NAV_SCORING 0 +#define IMGUI_DEBUG_NAV_RECTS 0 + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang/GCC warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#if __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif +#endif + +// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. +static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in +static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear + +// Window resizing from edges (when io.ConfigResizeWindowsFromEdges = true) +static const float RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS = 4.0f; // Extend outside and inside windows. Affect FindHoveredWindow(). +static const float RESIZE_WINDOWS_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. + +//------------------------------------------------------------------------- +// [SECTION] FORWARD DECLARATIONS +//------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); +static void FindHoveredWindow(); +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); +static void CheckStacksSize(ImGuiWindow* window, bool write); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges); + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); +static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + +static ImRect GetViewportRect(); + +// Settings +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); +static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); +static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); + +// Platform Dependents default implementation for IO functions +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +namespace ImGui +{ +static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags); + +// Navigation +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavUpdateWindowingList(); +static void NavUpdateMoveResult(); +static float NavUpdatePageUpPageDown(int allowed_dir_flags); +static inline void NavUpdateAnyRequestFlag(); +static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id); +static ImVec2 NavCalcPreferredRefPos(); +static void NavSaveLastChildNavWindow(ImGuiWindow* nav_window); +static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); + +// Misc +static void UpdateMouseInputs(); +static void UpdateMouseWheel(); +static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); +} + +// Test engine hooks (imgui-test) +//#define IMGUI_ENABLE_TEST_ENGINE_HOOKS +#ifdef IMGUI_ENABLE_TEST_ENGINE_HOOKS +extern void ImGuiTestEngineHook_PreNewFrame(); +extern void ImGuiTestEngineHook_PostNewFrame(); +extern void ImGuiTestEngineHook_ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg); +#endif + +//----------------------------------------------------------------------------- +// [SECTION] CONTEXT AND MEMORY ALLOCATORS +//----------------------------------------------------------------------------- + +// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL. +// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). +// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file. +// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can: +// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) +#ifndef GImGui +ImGuiContext* GImGui = NULL; +#endif + +// Memory Allocator functions. Use SetAllocatorFunctions() to change them. +// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. +// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); } +#else +static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; } +static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); } +#endif + +static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; +static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; +static void* GImAllocatorUserData = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns + ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + DisplayWindowPadding = ImVec2(20,20); // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. + DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. + AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + // Default theme + ImGui::StyleColorsDark(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + + // Settings + ConfigFlags = 0x00; + BackendFlags = 0x00; + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f/60.0f; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + + // Miscellaneous configuration options +#ifdef __APPLE__ + ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + ConfigMacOSXBehaviors = false; +#endif + ConfigInputTextCursorBlink = true; + ConfigResizeWindowsFromEdges = false; + + // Settings (User Functions) + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + RenderDrawListsFn = NULL; +#endif + + // Input (NB: we already have memset zero the entire structure) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(ImWchar c) +{ + const int n = ImStrlenW(InputCharacters); + if (n + 1 < IM_ARRAYSIZE(InputCharacters)) + { + InputCharacters[n] = c; + InputCharacters[n+1] = '\0'; + } +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more + const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); + ImWchar wchars[wchars_buf_len]; + ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); + for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) + AddInputCharacter(wchars[i]); +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) +//----------------------------------------------------------------------------- + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + if (dot > ab_len_sqr) + return b; + return a + ab_dir * dot / ab_len_sqr; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) return; + strncpy(dst, src, count); + dst[count-1] = 0; +} + +char* ImStrdup(const char *str) +{ + size_t len = strlen(str) + 1; + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +const char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + const char* p = (const char*)memchr(str, (int)c, str_end - str); + return p; +} + +int ImStrlenW(const ImWchar* str) +{ + //return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bits + int n = 0; + while (*str++) n++; + return n; +} + +// Find end-of-line. Return pointer will point to either first \n, either str_end. +const char* ImStreolRange(const char* str, const char* str_end) +{ + const char* p = (const char*)memchr(str, '\n', str_end - str); + return p ? p : str_end; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible. +void ImStrTrimBlanks(char* buf) +{ + char* p = buf; + while (p[0] == ' ' || p[0] == '\t') // Leading blanks + p++; + char* p_start = p; + while (*p != 0) // Find end of string + p++; + while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks + p--; + if (p_start != buf) // Copy memory if we had leading blanks + memmove(buf, p_start, p - p_start); + buf[p - p_start] = 0; // Zero terminate +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +#if defined(_MSC_VER) && !defined(vsnprintf) +#define vsnprintf _vsnprintf +#endif + +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + int w = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ + int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +// Pass data_size==0 for zero-terminated strings +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImU32 ImHash(const void* data, int data_size, ImU32 seed) +{ + static ImU32 crc32_lut[256] = { 0 }; + if (!crc32_lut[1]) + { + const ImU32 polynomial = 0xEDB88320; + for (ImU32 i = 0; i < 256; i++) + { + ImU32 crc = i; + for (ImU32 j = 0; j < 8; j++) + crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); + crc32_lut[i] = crc; + } + } + + seed = ~seed; + ImU32 crc = seed; + const unsigned char* current = (const unsigned char*)data; + + if (data_size > 0) + { + // Known size + while (data_size--) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; + } + else + { + // Zero-terminated string + while (unsigned char c = *current++) + { + // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. + // Because this syntax is rarely used we are optimizing for the common case. + // - If we reach ### in the string we discard the hash so far and reset to the seed. + // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. + if (c == '#' && current[0] == '#' && current[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +FILE* ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__GNUC__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) + const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; + const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); + ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); + return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// Load file content into memory +// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && file_open_mode); + if (out_file_size) + *out_file_size = 0; + + FILE* f; + if ((f = ImFileOpen(filename, file_open_mode)) == NULL) + return NULL; + + long file_size_signed; + if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) + { + fclose(f); + return NULL; + } + + size_t file_size = (size_t)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); + if (file_data == NULL) + { + fclose(f); + return NULL; + } + if (fread(file_data, 1, file_size, f) != file_size) + { + fclose(f); + ImGui::MemFree(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes); + + fclose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bits character, process single character input. +// Based on stb_from_utf8() from github.com/nothings/stb/ +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + unsigned int c = (unsigned int)-1; + const unsigned char* str = (const unsigned char*)in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 2) return 1; + if (*str < 0xc2) return 2; + c = (unsigned int)((*str++ & 0x1f) << 6); + if ((*str & 0xc0) != 0x80) return 2; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 3) return 1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; + if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x0f) << 12); + if ((*str & 0xc0) != 0x80) return 3; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 3; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 4) return 1; + if (*str > 0xf4) return 4; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; + if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x07) << 18); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 12); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 4; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return 4; + *out_char = c; + return 4; + } + *out_char = 0; + return 0; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +// Not optimal but we very rarely use this function. +int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) +{ + unsigned int dummy = 0; + return ImTextCharFromUtf8(&dummy, in_text, in_text_end); +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPER/UTILTIES (Color functions) +// Note: The Convert functions are early design which are not consistent with other API. +//----------------------------------------------------------------------------- + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f/255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = ImFmod(h, 1.0f) / (60.0f/360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + float style_alpha = GImGui->Style.Alpha; + if (style_alpha >= 1.0f) + return col; + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) +{ + ImVector::iterator first = data.begin(); + ImVector::iterator last = data.end(); + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImVector::iterator mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + ImQsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::PushItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (width != 0.0f) + ImGui::PopItemWidth(); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::TextRange::split(char separator, ImVector* out) const +{ + out->resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out->push_back(TextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out->push_back(TextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); + input_range.split(',', &Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + TextRange& f = Filters[i]; + while (f.b < f.e && ImCharIsBlankA(f.b[0])) + f.b++; + while (f.e > f.b && ImCharIsBlankA(f.e[-1])) + f.e--; + if (f.empty()) + continue; + if (Filters[i].b[0] != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const TextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.b[0] == '-') + { + // Subtract + if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#if defined(__GNUC__) || defined(__clang__) +#define va_copy(dest, src) __builtin_va_copy(dest, src) +#else +#define va_copy(dest, src) (dest = src) +#endif +#endif + +char ImGuiTextBuffer::EmptyString[1] = { 0 }; + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + { + va_end(args_copy); + return; + } + + // Add zero-terminator the first time + const int write_off = (Buf.Size != 0) ? Buf.Size : 1; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int double_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy); + va_end(args_copy); +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiListClipper +// This is currently not as flexible/powerful as it should be, needs some rework (see TODO) +//----------------------------------------------------------------------------- + +static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. + // The clipper should probably have a 4th step to display the last item in a regular manner. + ImGui::SetCursorPosY(pos_y); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. + window->DC.PrevLineSize.y = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (window->DC.ColumnsSet) + window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int count, float items_height) +{ + StartPosY = ImGui::GetCursorPosY(); + ItemsHeight = items_height; + ItemsCount = count; + StepNo = 0; + DisplayEnd = DisplayStart = -1; + if (ItemsHeight > 0.0f) + { + ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display + if (DisplayStart > 0) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor + StepNo = 2; + } +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) + return; + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) + { + ItemsCount = -1; + return false; + } + if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. + { + DisplayStart = 0; + DisplayEnd = 1; + StartPosY = ImGui::GetCursorPosY(); + StepNo = 1; + return true; + } + if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. + { + if (ItemsCount == 1) { ItemsCount = -1; return false; } + float items_height = ImGui::GetCursorPosY() - StartPosY; + IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically + Begin(ItemsCount-1, items_height); + DisplayStart++; + DisplayEnd++; + StepNo = 3; + return true; + } + if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. + { + IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); + StepNo = 3; + return true; + } + if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. + End(); + return false; +} + +//----------------------------------------------------------------------------- +// [SECTION] RENDER HELPERS +// Those (internal) functions are currently quite a legacy mess - their signature and behavior will change. +// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: state. +//----------------------------------------------------------------------------- + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + if (text != text_display_end) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + if (text != text_end) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +// Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state +void ImGui::RenderArrow(ImVec2 p_min, ImGuiDir dir, float scale) +{ + ImGuiContext& g = *GImGui; + + const float h = g.FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + a = ImVec2(+0.000f,+0.750f) * r; + b = ImVec2(-0.866f,-0.750f) * r; + c = ImVec2(+0.866f,-0.750f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + a = ImVec2(+0.750f,+0.000f) * r; + b = ImVec2(-0.750f,+0.866f) * r; + c = ImVec2(-0.750f,-0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_COUNT: + IM_ASSERT(0); + break; + } + + g.CurrentWindow->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); +} + +void ImGui::RenderBullet(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddCircleFilled(pos, g.FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); +} + +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); + window->DrawList->PathLineTo(ImVec2(bx, by)); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); +} + +void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext& g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) + { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE,DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) + { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +//----------------------------------------------------------------------------- + +// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) + : DrawListInst(&context->DrawListSharedData) +{ + Name = ImStrdup(name); + ID = ImHash(name, 0); + IDStack.push_back(ID); + Flags = 0; + Pos = ImVec2(0.0f, 0.0f); + Size = SizeFull = ImVec2(0.0f, 0.0f); + SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); + WindowPadding = ImVec2(0.0f, 0.0f); + WindowRounding = 0.0f; + WindowBorderSize = 0.0f; + MoveId = GetID("#MOVE"); + ChildId = 0; + Scroll = ImVec2(0.0f, 0.0f); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + ScrollbarSizes = ImVec2(0.0f, 0.0f); + ScrollbarX = ScrollbarY = false; + Active = WasActive = false; + WriteAccessed = false; + Collapsed = false; + WantCollapseToggle = false; + SkipItems = false; + Appearing = false; + Hidden = false; + HasCloseButton = false; + BeginCount = 0; + BeginOrderWithinParent = -1; + BeginOrderWithinContext = -1; + PopupId = 0; + AutoFitFramesX = AutoFitFramesY = -1; + AutoFitOnlyGrows = false; + AutoFitChildAxises = 0x00; + AutoPosLastDirection = ImGuiDir_None; + HiddenFramesRegular = HiddenFramesForResize = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + + LastFrameActive = -1; + ItemWidthDefault = 0.0f; + FontWindowScale = 1.0f; + SettingsIdx = -1; + + DrawList = &DrawListInst; + DrawList->_OwnerName = Name; + ParentWindow = NULL; + RootWindow = NULL; + RootWindowForTitleBarHighlight = NULL; + RootWindowForNav = NULL; + + NavLastIds[0] = NavLastIds[1] = 0; + NavRectRel[0] = NavRectRel[1] = ImRect(); + NavLastChildNavWindow = NULL; + + FocusIdxAllCounter = FocusIdxTabCounter = -1; + FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; + FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_ASSERT(DrawList == &DrawListInst); + IM_DELETE(Name); + for (int i = 0; i != ColumnsStorage.Size; i++) + ColumnsStorage[i].~ImGuiColumnsSet(); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + return ImHash(&ptr, sizeof(void*), seed); +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +void ImGui::SetNavID(ImGuiID id, int nav_layer) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow); + IM_ASSERT(nav_layer == 0 || nav_layer == 1); + g.NavId = id; + g.NavWindow->NavLastIds[nav_layer] = id; +} + +void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel) +{ + ImGuiContext& g = *GImGui; + SetNavID(id, nav_layer); + g.NavWindow->NavRectRel[nav_layer] = rect_rel; + g.NavMousePosDirty = true; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + { + g.ActiveIdTimer = 0.0f; + g.ActiveIdHasBeenEdited = false; + if (id != 0) + { + g.LastActiveId = id; + g.LastActiveIdTimer = 0.0f; + } + } + g.ActiveId = id; + g.ActiveIdAllowNavDirFlags = 0; + g.ActiveIdAllowOverlap = false; + g.ActiveIdWindow = window; + if (id) + { + g.ActiveIdIsAlive = id; + g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + } +} + +void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. + const int nav_layer = window->DC.NavLayerCurrent; + if (g.NavWindow != window) + g.NavInitRequest = false; + g.NavId = id; + g.NavWindow = window; + g.NavLayer = nav_layer; + window->NavLastIds[nav_layer] = id; + if (window->DC.LastItemId == id) + window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + if (id != 0 && g.HoveredIdPreviousFrame != id) + g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f; +} + +ImGuiID ImGui::GetHoveredID() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = id; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; +} + +void ImGui::MarkItemEdited(ImGuiID id) +{ + // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). + // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data. + (void)id; // Avoid unused variable warnings when asserts are compiled out. + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive); + //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); + g.ActiveIdHasBeenEdited = true; + g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + +// Advance cursor given item size for layout. +void ImGui::ItemSize(const ImVec2& size, float text_offset_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Always align ourselves on pixel boundaries + const float line_height = ImMax(window->DC.CurrentLineSize.y, size.y); + const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); + window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineSize.y = line_height; + window->DC.PrevLineTextBaseOffset = text_base_offset; + window->DC.CurrentLineSize.y = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_offset_y) +{ + ItemSize(bb.GetSize(), text_offset_y); +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) +{ +#ifdef IMGUI_ENABLE_TEST_ENGINE_HOOKS + ImGuiTestEngineHook_ItemAdd(bb, id, nav_bb_arg); +#endif + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (id != 0) + { + // Navigation processing runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window. + // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick) + window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask; + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + } + + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemStatusFlags = 0; + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id, false); + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (IsMouseHoveringRect(bb.Min, bb.Max)) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavDisableMouseHover && !g.NavDisableHighlight) + return IsItemFocused(); + + // Test for bounding box overlap, as updated as ItemAdd() + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + + // Test if another item is active (e.g. being dragged) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || id != g.ActiveId) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) +{ + ImGuiContext& g = *GImGui; + + const bool is_tab_stop = (window->DC.ItemFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; + window->FocusIdxAllCounter++; + if (is_tab_stop) + window->FocusIdxTabCounter++; + + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + + if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) + return true; + if (is_tab_stop && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) + { + g.NavJustTabbedId = id; + return true; + } + + return false; +} + +void ImGui::FocusableItemUnregister(ImGuiWindow* window) +{ + window->FocusIdxAllCounter--; + window->FocusIdxTabCounter--; +} + +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) +{ + ImGuiContext& g = *GImGui; + ImVec2 content_max; + if (size.x < 0.0f || size.y < 0.0f) + content_max = g.CurrentWindow->Pos + GetContentRegionMax(); + if (size.x <= 0.0f) + size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; + if (size.y <= 0.0f) + size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; + return size; +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiWindow* window = GetCurrentWindowRead(); + if (wrap_pos_x == 0.0f) + wrap_pos_x = GetContentRegionMax().x + window->Pos.x; + else if (wrap_pos_x > 0.0f) + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +void* ImGui::MemAlloc(size_t size) +{ + if (ImGuiContext* ctx = GImGui) + ctx->IO.MetricsActiveAllocations++; + return GImAllocatorAllocFunc(size, GImAllocatorUserData); +} + +void ImGui::MemFree(void* ptr) +{ + if (ptr) + if (ImGuiContext* ctx = GImGui) + ctx->IO.MetricsActiveAllocations--; + return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); +} + +const char* ImGui::GetClipboardText() +{ + return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + if (GImGui->IO.SetClipboardTextFn) + GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +// Helper function to verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit +// If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. you may see different structures from what imgui.cpp sees which is highly problematic. +bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert) +{ + bool error = false; + if (strcmp(version, IMGUI_VERSION)!=0) { error = true; IM_ASSERT(strcmp(version,IMGUI_VERSION)==0 && "Mismatched version string!"); } + if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); } + if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); } + if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); } + if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); } + if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); } + return !error; +} + +void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + if (GImGui == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + if (ctx == NULL) + ctx = GImGui; + Shutdown(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +ImGuiIO& ImGui::GetIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->IO; +} + +ImGuiStyle& ImGui::GetStyle() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + return GImGui->Style; +} + +// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + ImGuiContext& g = *GImGui; + return g.DrawData.Valid ? &g.DrawData : NULL; +} + +double ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +void ImGui::StartMouseMovingWindow(ImGuiWindow* window) +{ + // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows. + ImGuiContext& g = *GImGui; + FocusWindow(window); + SetActiveID(window->MoveId, window); + g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MousePos - window->RootWindow->Pos; + if (!(window->Flags & ImGuiWindowFlags_NoMove) && !(window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) + g.MovingWindow = window; +} + +// Handle mouse moving window +// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() +void ImGui::UpdateMouseMovingWindow() +{ + ImGuiContext& g = *GImGui; + if (g.MovingWindow != NULL) + { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) + { + MarkIniSettingsDirty(moving_window); + SetWindowPos(moving_window, pos, ImGuiCond_Always); + } + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + } + } + else + { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) + { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + } +} + +static bool IsWindowActiveAndVisible(ImGuiWindow* window) +{ + return (window->Active) && (!window->Hidden); +} + +static void ImGui::UpdateMouseInputs() +{ + ImGuiContext& g = *GImGui; + + // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) + if (IsMousePosValid(&g.IO.MousePos)) + g.IO.MousePos = ImFloor(g.IO.MousePos); + + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) + { + ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold + ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos)); + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); + } + if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + g.NavDisableMouseHover = false; + } +} + +void ImGui::UpdateMouseWheel() +{ + ImGuiContext& g = *GImGui; + if (!g.HoveredWindow || g.HoveredWindow->Collapsed) + return; + if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f) + return; + + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). + ImGuiWindow* window = g.HoveredWindow; + ImGuiWindow* scroll_window = window; + while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoMouseInputs) && scroll_window->ParentWindow) + scroll_window = scroll_window->ParentWindow; + const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoMouseInputs); + + if (g.IO.MouseWheel != 0.0f) + { + if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + // Zoom / Scale window + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + window->Pos += offset; + window->Size *= scale; + window->SizeFull *= scale; + } + else if (!g.IO.KeyCtrl && scroll_allowed) + { + // Mouse wheel vertical scrolling + float scroll_amount = 5 * scroll_window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); + } + } + if (g.IO.MouseWheelH != 0.0f && scroll_allowed && !g.IO.KeyCtrl) + { + // Mouse wheel horizontal scrolling (for hardware that supports it) + float scroll_amount = scroll_window->CalcFontSize(); + SetWindowScrollX(scroll_window, scroll_window->Scroll.x - g.IO.MouseWheelH * scroll_amount); + } +} + +// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) +void ImGui::UpdateHoveredWindowAndCaptureFlags() +{ + ImGuiContext& g = *GImGui; + + // Find the window hovered by mouse: + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + FindHoveredWindow(); + + // Modal windows prevents cursor from hovering behind them. + ImGuiWindow* modal_window = GetFrontMostPopupModal(); + if (modal_window) + if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + g.HoveredRootWindow = g.HoveredWindow = NULL; + + // Disabled mouse? + if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app) + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + + // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to imgui + app) + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) + g.IO.WantCaptureKeyboard = true; + + // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; +} + +void ImGui::NewFrame() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + ImGuiContext& g = *GImGui; + +#ifdef IMGUI_ENABLE_TEST_ENGINE_HOOKS + ImGuiTestEngineHook_PreNewFrame(); +#endif + + // Check user data + // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.Initialized); + IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + for (int n = 0; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); + + // Perform simple check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only recently added in 1.60 WIP) + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + + // Perform simple check: the beta io.ConfigResizeWindowsFromEdges option requires back-end to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly. + if (g.IO.ConfigResizeWindowsFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors)) + g.IO.ConfigResizeWindowsFromEdges = false; + + // Load settings on first frame (if not explicitly loaded manually before) + if (!g.SettingsLoaded) + { + IM_ASSERT(g.SettingsWindows.empty()); + if (g.IO.IniFilename) + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.SettingsLoaded = true; + } + + // Save settings (with a delay after the last modification, so we don't spam disk too much) + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + { + if (g.IO.IniFilename != NULL) + SaveIniSettingsToDisk(g.IO.IniFilename); + else + g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. + g.SettingsDirtyTimer = 0.0f; + } + } + + g.Time += g.IO.DeltaTime; + g.FrameScopeActive = true; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + + // Setup current font and draw list + g.IO.Fonts->Locked = true; + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + + g.OverlayDrawList.Clear(); + g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); + g.OverlayDrawList.PushClipRectFullScreen(); + g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it + g.DrawData.Clear(); + + // Drag and drop keep the source ID alive so even if the source disappear our state is consistent + if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId) + KeepAliveID(g.DragDropPayload.SourceId); + + // Clear reference to active widget if the widget isn't alive anymore + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId)) + g.HoveredIdNotActiveTimer = 0.0f; + if (g.HoveredId) + g.HoveredIdTimer += g.IO.DeltaTime; + if (g.HoveredId && g.ActiveId != g.HoveredId) + g.HoveredIdNotActiveTimer += g.IO.DeltaTime; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.LastActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; + g.ActiveIdPreviousFrameHasBeenEdited = g.ActiveIdHasBeenEdited; + g.ActiveIdIsAlive = 0; + g.ActiveIdPreviousFrameIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Drag and drop + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropWithinSourceOrTarget = false; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update gamepad/keyboard directional navigation + NavUpdate(); + + // Update mouse input state + UpdateMouseInputs(); + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX; + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) + UpdateMouseMovingWindow(); + UpdateHoveredWindowAndCaptureFlags(); + + // Background darkening/whitening + if (GetFrontMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f)) + g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f); + else + g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f); + + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // Mouse wheel scrolling, scale + UpdateMouseWheel(); + + // Pressing TAB activate widget focus + if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false)) + { + if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) + g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); + else + g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0; + } + g.NavIdTabCounter = INT_MAX; + + // Mark all windows as not visible + IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + } + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusPreviousWindowIgnoringOne(NULL); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + ClosePopupsOverWindow(g.NavWindow); + + // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); + +#ifdef IMGUI_ENABLE_TEST_ENGINE_HOOKS + ImGuiTestEngineHook_PostNewFrame(); +#endif +} + +void ImGui::Initialize(ImGuiContext* context) +{ + ImGuiContext& g = *context; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_front(ini_handler); + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown(ImGuiContext* context) +{ + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + ImGuiContext& g = *context; + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + { + g.IO.Fonts->Locked = false; + IM_DELETE(g.IO.Fonts); + } + g.IO.Fonts = NULL; + + // Cleanup of other data are conditional on actually having initialized ImGui. + if (!g.Initialized) + return; + + // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) + if (g.SettingsLoaded && g.IO.IniFilename != NULL) + { + ImGuiContext* backup_context = GImGui; + SetCurrentContext(context); + SaveIniSettingsToDisk(g.IO.IniFilename); + SetCurrentContext(backup_context); + } + + // Clear everything else + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsFocusOrder.clear(); + g.WindowsSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = NULL; + g.HoveredRootWindow = NULL; + g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.MovingWindow = NULL; + g.ColorModifiers.clear(); + g.StyleModifiers.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.CurrentPopupStack.clear(); + g.DrawDataBuilder.ClearFreeMemory(); + g.OverlayDrawList.ClearFreeMemory(); + g.PrivateClipboard.clear(); + g.InputTextState.TextW.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempBuffer.clear(); + + for (int i = 0; i < g.SettingsWindows.Size; i++) + IM_DELETE(g.SettingsWindows[i].Name); + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile && g.LogFile != stdout) + { + fclose(g.LogFile); + g.LogFile = NULL; + } + g.LogClipboard.clear(); + + g.Initialized = false; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs; + const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows->push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + ImQsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list) +{ + if (draw_list->CmdBuffer.empty()) + return; + + // Remove trailing command if unused + ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); + if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) + { + draw_list->CmdBuffer.pop_back(); + if (draw_list->CmdBuffer.empty()) + return; + } + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents. + // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. + // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. + // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.IO.MetricsRenderWindows++; + AddDrawListToDrawData(out_render_list, window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (IsWindowActiveAndVisible(child)) // clipped children may have been marked not active + AddWindowToDrawData(out_render_list, child); + } +} + +static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); + else + AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() +{ + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) + size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) + { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) + continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupDrawData(ImVector* draw_lists, ImDrawData* draw_data) +{ + ImGuiIO& io = ImGui::GetIO(); + draw_data->Valid = true; + draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + draw_data->CmdListsCount = draw_lists->Size; + draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; + draw_data->DisplayPos = ImVec2(0.0f, 0.0f); + draw_data->DisplaySize = io.DisplaySize; + for (int n = 0; n < draw_lists->Size; n++) + { + draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; + draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; + } +} + +// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. + return; + IM_ASSERT(g.FrameScopeActive && "Forgot to call ImGui::NewFrame()"); + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f) + { + g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y); + g.PlatformImeLastPos = g.PlatformImePos; + } + + // Hide implicit "Debug" window if it hasn't been used + IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls, did you forget to call end on g.CurrentWindow->Name? + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + End(); + + // Show CTRL+TAB list + if (g.NavWindowingTarget) + NavUpdateWindowingList(); + + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) + if (g.DragDropActive) + { + bool is_delivered = g.DragDropPayload.Delivery; + bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton)); + if (is_delivered || is_elapsed) + ClearDragDrop(); + } + + // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. + if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount) + { + g.DragDropWithinSourceOrTarget = true; + SetTooltip("..."); + g.DragDropWithinSourceOrTarget = false; + } + + // Initiate moving window + if (g.ActiveId == 0 && g.HoveredId == 0) + { + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear + { + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + if (g.HoveredRootWindow != NULL) + StartMouseMovingWindow(g.HoveredWindow); + else if (g.NavWindow != NULL && GetFrontMostPopupModal() == NULL) + FocusWindow(NULL); // Clicking on void disable focus + } + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostPopupModal(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal); + } + } + } + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because childs may not exist yet + g.WindowsSortBuffer.resize(0); + g.WindowsSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortBuffer(&g.WindowsSortBuffer, window); + } + + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + g.Windows.swap(g.WindowsSortBuffer); + g.IO.MetricsActiveWindows = g.WindowsActiveCount; + + // Unlock font atlas + g.IO.Fonts->Locked = false; + + // Clear Input data for next frame + g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + g.FrameScopeActive = false; + g.FrameCountEnded = g.FrameCount; +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + + if (g.FrameCountEnded != g.FrameCount) + ImGui::EndFrame(); + g.FrameCountRendered = g.FrameCount; + + // Gather ImDrawList to render (for each active window) + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsRenderWindows = 0; + g.DrawDataBuilder.Clear(); + ImGuiWindow* windows_to_render_front_most[2]; + windows_to_render_front_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; + windows_to_render_front_most[1] = g.NavWindowingTarget ? g.NavWindowingList : NULL; + for (int n = 0; n != g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_front_most[0] && window != windows_to_render_front_most[1]) + AddWindowToDrawDataSelectLayer(window); + } + for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_front_most); n++) + if (windows_to_render_front_most[n] && IsWindowActiveAndVisible(windows_to_render_front_most[n])) // NavWindowingTarget is always temporarily displayed as the front-most window + AddWindowToDrawDataSelectLayer(windows_to_render_front_most[n]); + g.DrawDataBuilder.FlattenIntoSingleLayer(); + + // Draw software mouse cursor if requested + if (g.IO.MouseDrawCursor) + RenderMouseCursor(&g.OverlayDrawList, g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor); + + if (!g.OverlayDrawList.VtxBuffer.empty()) + AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); + + // Setup ImDrawData structure for end-user + SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); + g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; + g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; + + // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(&g.DrawData); +#endif +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) + const float font_scale = font_size / font->FontSize; + const float character_spacing_x = 1.0f * font_scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + text_size.x = (float)(int)(text_size.x + 0.95f); + + return text_size; +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (window->SkipItems) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect + ImRect unclipped_rect = window->ClipRect; + if (g.NavMoveRequest) + unclipped_rect.Add(g.NavScoringRectScreen); + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); + int end = (int)((unclipped_rect.Max.y - pos.y) / items_height); + + // When performing a navigation request, ensure we have one item extra in the direction we are moving to + if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up) + start--; + if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programatically +// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is +// called, aka before the next Begin(). Moving window isn't affected. +static void FindHoveredWindow() +{ + ImGuiContext& g = *GImGui; + + ImGuiWindow* hovered_window = NULL; + if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) + hovered_window = g.MovingWindow; + + ImVec2 padding_regular = g.Style.TouchExtraPadding; + ImVec2 padding_for_resize_from_edges = g.IO.ConfigResizeWindowsFromEdges ? ImMax(g.Style.TouchExtraPadding, ImVec2(RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS, RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS)) : padding_regular; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window == NULL; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (!window->Active || window->Hidden) + continue; + if (window->Flags & ImGuiWindowFlags_NoMouseInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->OuterRectClipped); + if ((window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_NoResize)) + bb.Expand(padding_regular); + else + bb.Expand(padding_for_resize_from_edges); + if (!bb.Contains(g.IO.MousePos)) + continue; + + // Those seemingly unnecessary extra tests are because the code here is a little different in viewport/docking branches. + if (hovered_window == NULL) + hovered_window = window; + if (hovered_window) + break; + } + + g.HoveredWindow = hovered_window; + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(g.CurrentWindow->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + if (!rect_for_touch.Contains(g.IO.MousePos)) + return false; + return true; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + return GImGui->IO.KeyMap[imgui_key]; +} + +// Note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your back-end/engine stored them into io.KeysDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); + return GImGui->IO.KeysDown[user_key_index]; +} + +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; +} + +bool ImGui::IsMouseDown(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsAnyMouseDown() +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + return true; + return false; +} + +bool ImGui::IsMouseClicked(int button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((ImFmod(t - delay, rate) > rate*0.5f) != (ImFmod(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) + return true; + } + + return false; +} + +bool ImGui::IsMouseReleased(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +bool ImGui::IsMouseDragging(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +ImVec2 ImGui::GetMousePos() +{ + return GImGui->IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentPopupStack.Size > 0) + return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + +// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window. +ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemDeactivated() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); +} + +bool ImGui::IsItemDeactivatedAfterEdit() +{ + ImGuiContext& g = *GImGui; + return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEdited || (g.ActiveId == 0 && g.ActiveIdHasBeenEdited)); +} + +bool ImGui::IsItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; +} + +bool ImGui::IsItemClicked(int mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None); +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId != 0; +} + +bool ImGui::IsAnyItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId != 0 && !g.NavDisableHighlight; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +bool ImGui::IsItemEdited() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0; +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId == g.CurrentWindow->DC.LastItemId) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == g.CurrentWindow->DC.LastItemId) + g.ActiveIdAllowOverlap = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +static ImRect GetViewportRect() +{ + ImGuiContext& g = *GImGui; + if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) + return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); + return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); +} + +static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + + flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + // Size + const ImVec2 content_avail = GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + SetNextWindowSize(size); + + // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. + char title[256]; + if (name) + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + bool ret = Begin(title, NULL, flags); + g.Style.ChildBorderSize = backup_border_size; + + ImGuiWindow* child_window = g.CurrentWindow; + child_window->ChildId = id; + child_window->AutoFitChildAxises = auto_fit_axises; + + // Process navigation-in immediately so NavInit can run on first frame + if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll)) + { + FocusWindow(child_window); + NavInitWindow(child_window, false); + SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss + if (window->BeginCount > 1) + { + End(); + } + else + { + ImVec2 sz = window->Size; + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow* parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, window->ChildId); + + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child + if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } + else + { + // Not navigable into + ItemAdd(bb, 0); + } + } +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); + PopStyleVar(3); + PopStyleColor(); + return ret; +} + +void ImGui::EndChildFrame() +{ + EndChild(); +} + +// Save and compare stack sizes on Begin()/End() to detect usage errors +static void CheckStacksSize(ImGuiWindow* window, bool write) +{ + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + ImGuiContext& g = *GImGui; + int* p_backup = &window->DC.StackSizesBackup[0]; + { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() + { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() + { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() + // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. + { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() + { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() + { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() + IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = ImHash(name, 0); + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + window->Pos = ImVec2(60, 60); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + // Retrieve settings from .ini file + window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + window->Pos = ImFloor(settings->Pos); + window->Collapsed = settings->Collapsed; + if (ImLengthSqr(settings->Size) > 0.00001f) + size = ImFloor(settings->Size); + } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = ImFloor(size); + window->DC.CursorMaxPos = window->Pos; // So first call to CalcSizeContents() doesn't return crazy values + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + g.WindowsFocusOrder.push_back(window); + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.push_front(window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.NextWindowData.SizeConstraintCond != 0) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) + { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + if (window->Flags & ImGuiWindowFlags_Tooltip) + { + // Tooltip always resize + return size_contents; + } + else + { + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0; + const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0; + ImVec2 size_min = style.WindowMinSize; + if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) + size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); + ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, g.IO.DisplaySize - style.DisplaySafeAreaPadding * 2.0f)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize; + return size_auto_fit; + } +} + +ImVec2 ImGui::CalcWindowExpectedSize(ImGuiWindow* window) +{ + ImVec2 size_contents = CalcSizeContents(window); + return CalcSizeAfterConstraint(window, CalcSizeAutoFit(window, size_contents)); +} + +float ImGui::GetWindowScrollMaxX(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); +} + +float ImGui::GetWindowScrollMaxY(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges) +{ + ImGuiContext& g = *GImGui; + ImVec2 scroll = window->Scroll; + if (window->ScrollTarget.x < FLT_MAX) + { + float cr_x = window->ScrollTargetCenterRatio.x; + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + } + if (window->ScrollTarget.y < FLT_MAX) + { + // 'snap_on_edges' allows for a discontinuity at the edge of scrolling limits to take account of WindowPadding so that scrolling to make the last item visible scroll far enough to see the padding. + float cr_y = window->ScrollTargetCenterRatio.y; + float target_y = window->ScrollTarget.y; + if (snap_on_edges && cr_y <= 0.0f && target_y <= window->WindowPadding.y) + target_y = 0.0f; + if (snap_on_edges && cr_y >= 1.0f && target_y >= window->SizeContents.y - window->WindowPadding.y + g.Style.ItemSpacing.y) + target_y = window->SizeContents.y; + scroll.y = target_y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + } + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, ImGui::GetWindowScrollMaxX(window)); + scroll.y = ImMin(scroll.y, ImGui::GetWindowScrollMaxY(window)); + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); + if (border_n == 3) return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + +// Handle resize for: Resize Grips, Borders, Gamepad +static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return; + if (window->WasActive == false) // Early out to avoid running this code for e.g. an hidden implicit Debug window. + return; + + const int resize_border_count = g.IO.ConfigResizeWindowsFromEdges ? 4 : 0; + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_inner_size = (float)(int)(grip_draw_size * 0.75f); + const float grip_hover_outer_size = g.IO.ConfigResizeWindowsFromEdges ? RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS : 0.0f; + + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner - grip.InnerDir * grip_hover_outer_size, corner + grip.InnerDir * grip_hover_inner_size); + if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x); + if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + //GetOverlayDrawList()->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(grip.InnerDir * grip_hover_outer_size, grip.InnerDir * -grip_hover_inner_size, grip.CornerPos); // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + bool hovered, held; + ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + //GetOverlayDrawList()->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255)); + if ((hovered && g.HoveredIdTimer > RESIZE_WINDOWS_FROM_EDGES_FEEDBACK_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) *border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + RESIZE_WINDOWS_FROM_EDGES_HALF_THICKNESS); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } + } + PopID(); + + // Navigation resize (keyboard/gamepad) + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) + { + ImVec2 nav_resize_delta; + if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_NavGamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) + { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. + size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; +} + +void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) +{ + window->ParentWindow = parent_window; + window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; +} + +// Push a new ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL); // Window name required + IM_ASSERT(g.FrameScopeActive); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + const bool window_just_created = (window == NULL); + if (window_just_created) + { + ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. + window = CreateNewWindow(name, size_on_first_use, flags); + } + + // Automatically disable manual moving/resizing when NoInputs is set + if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + if (first_begin_of_the_frame) + window->Flags = (ImGuiWindowFlags)flags; + else + flags = window->Flags; + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + window->HasCloseButton = (p_open != NULL); + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesForResize > 0); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Add to stack + g.CurrentWindowStack.push_back(window); + SetCurrentWindow(window); + CheckStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) + window->NavLastIds[0] = 0; + + // Process SetNextWindow***() calls + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.PosCond) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + } + if (g.NextWindowData.SizeCond) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + } + if (g.NextWindowData.ContentSizeCond) + { + // Adjust passed "client size" to become a "window size" + window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal; + if (window->SizeContentsExplicit.y != 0.0f) + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); + } + else if (first_begin_of_the_frame) + { + window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); + } + if (g.NextWindowData.CollapsedCond) + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + if (g.NextWindowData.FocusCond) + FocusWindow(window); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + // Initialize + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + UpdateWindowParentAndRootLinks(window, flags, parent_window); + + window->Active = true; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + window->BeginCount = 0; + window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); + window->LastFrameActive = current_frame; + window->IDStack.resize(1); + + // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS + + // Update contents size from last frame for auto-fitting (or use explicit size) + window->SizeContents = CalcSizeContents(window); + if (window->HiddenFramesRegular > 0) + window->HiddenFramesRegular--; + if (window->HiddenFramesForResize > 0) + window->HiddenFramesForResize--; + + // Hide new windows for one frame until they calculate their size + if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) + window->HiddenFramesForResize = 1; + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + // We reset Size/SizeContents for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. + if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) + { + window->HiddenFramesForResize = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->SizeContents = ImVec2(0.f, 0.f); + } + } + + SetCurrentWindow(window); + + // Lock border size and padding for the frame (so that altering them doesn't cause inconsistencies) + window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); + window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. + ImRect title_bar_rect = window->TitleBarRect(); + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) + window->WantCollapseToggle = true; + if (window->WantCollapseToggle) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + FocusWindow(window); + } + } + else + { + window->Collapsed = false; + } + window->WantCollapseToggle = false; + + // SIZE + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit may only grow window during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + + // SCROLLBAR STATUS + + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // POSITION + + // Popup latch its initial position, will position itself when it appears next frame + if (window_just_activated_by_user) + { + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->Pos = g.CurrentPopupStack.back().OpenPopupPos; + } + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = parent_window->DC.CursorPos; + } + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesForResize == 0); + if (window_pos_with_pivot) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering) + else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = FindBestWindowPosForPopup(window); + + // Clamp position so it stays visible + if (!(flags & ImGuiWindowFlags_ChildWindow)) + { + if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + { + ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size; + window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding); + } + } + window->Pos = ImFloor(window->Pos); + + // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + + // Prepare for item focus requests + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; + window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // Apply window focus (new and reactivated windows are moved to front) + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = g.IO.ConfigResizeWindowsFromEdges ? 2 : 1; // 4 + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); + if (!window->Collapsed) + UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); + else + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); + + // DRAWING + + // Setup draw list and outer clipping rectangle + window->DrawList->Clear(); + window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + ImRect viewport_rect(GetViewportRect()); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) + PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); + else + PushClipRect(viewport_rect.Min, viewport_rect.Max, true); + + // Draw modal window background (darkens what is behind them, all viewports) + const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0; + const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow); + if (dim_bg_for_modal || dim_bg_for_window_list) + { + const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); + window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col); + } + + // Draw navigation selection/windowing rectangle background + if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim) + { + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + } + + // Draw window + handle manual resize + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; + const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); + const ImRect title_bar_rect = window->TitleBarRect(); + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Window background + if (!(flags & ImGuiWindowFlags_NoBackground)) + { + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + if (g.NextWindowData.BgAlphaCond != 0) + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); + window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + } + g.NextWindowData.BgAlphaCond = 0; + + // Title bar + ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiLayoutType_Horizontal); + if (window->ScrollbarY) + Scrollbar(ImGuiLayoutType_Vertical); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + if (window_border_size > 0.0f && !(flags & ImGuiWindowFlags_NoBackground)) + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetResizeBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Draw navigation selection/windowing rectangle border + if (g.NavWindowingTargetAnim == window) + { + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + } + + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + + // Update various regions. Variables they depends on are set above in this function. + // FIXME: window->ContentsRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. + window->ContentsRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x; + window->ContentsRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); + window->ContentsRegionRect.Max.x = window->Pos.x - window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); + window->ContentsRegionRect.Max.y = window->Pos.y - window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffset.x = 0.0f; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.CurrentLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); + window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; + window->DC.NavLayerActiveMaskNext = 0x00; + window->DC.MenuBarAppending = false; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + window->DC.ChildWindows.resize(0); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemFlagsStack.resize(0); + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + window->DC.ColumnsSet = NULL; + window->DC.TreeDepth = 0; + window->DC.TreeDepthMayJumpToParentOnPop = 0x00; + window->DC.StateStorage = &window->StateStorage; + window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + { + FocusWindow(window); + NavInitWindow(window, false); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + // Close & collapse button are on layer 1 (same as menus) and don't default focus + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos)) + window->WantCollapseToggle = true; // Defer collapsing to next frame as we are too far in the Begin() function + + // Close button + if (p_open != NULL) + { + const float pad = style.FramePadding.y; + const float rad = g.FontSize * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1)) + *p_open = false; + } + + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.ItemFlags = item_flags_backup; + + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers, this is too much code for what it does.) + ImVec2 text_size = CalcTextSize(name, NULL, true); + ImRect text_r = title_bar_rect; + float pad_left = (flags & ImGuiWindowFlags_NoCollapse) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); + float pad_right = (p_open == NULL) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); + if (style.WindowTitleAlign.x > 0.0f) + pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); + text_r.Min.x += pad_left; + text_r.Max.x -= pad_right; + ImRect clip_rect = text_r; + clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() + RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + } + + // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() + window->OuterRectClipped = window->Rect(); + window->OuterRectClipped.ClipWith(window->ClipRect); + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + if (g.ActiveId == move_id) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + ImGui::LogToClipboard(); + */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior. + window->InnerMainRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerMainRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerMainRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerMainRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); + + // Inner clipping rectangle + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerMainRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize))); + window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerMainRect.Min.y); + window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize))); + window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y); + + // After Begin() we fill the last item / hovered data based on title bar data. It is a standard behavior (to allow creation of context menus on title bar only, etc.). + window->DC.LastItemId = window->MoveId; + window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; + window->DC.LastItemRect = title_bar_rect; + } + + PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + if (first_begin_of_the_frame) + window->WriteAccessed = false; + + window->BeginCount++; + g.NextWindowData.Clear(); + + if (flags & ImGuiWindowFlags_ChildWindow) + { + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) + window->HiddenFramesRegular = 1; + + // Completely hide along with parent or if parent is collapsed + if (parent_window && (parent_window->Collapsed || parent_window->Hidden)) + window->HiddenFramesRegular = 1; + } + + // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point) + if (style.Alpha <= 0.0f) + window->HiddenFramesRegular = 1; + + // Update the Hidden flag + window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize); + + // Return false if we don't intend to display anything to allow user to perform an early out optimization + window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0; + + return !window->SkipItems; +} + +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file. + if (size_first_use.x != 0.0f || size_first_use.y != 0.0f) + ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: override the window background alpha with a parameter. + if (bg_alpha_override >= 0.0f) + ImGui::SetNextWindowBgAlpha(bg_alpha_override); + + return ImGui::Begin(name, p_open, flags); +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop from window stack + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.WindowsFocusOrder.back() == window) + return; + for (int i = g.WindowsFocusOrder.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.WindowsFocusOrder[i] == window) + { + memmove(&g.WindowsFocusOrder[i], &g.WindowsFocusOrder[i + 1], (size_t)(g.WindowsFocusOrder.Size - i - 1) * sizeof(ImGuiWindow*)); + g.WindowsFocusOrder[g.WindowsFocusOrder.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.Windows[i] == window) + { + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); + g.Windows[g.Windows.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + if (g.NavWindow != window) + { + g.NavWindow = window; + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavInitRequest = false; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavIdIsAlive = false; + g.NavLayer = 0; + //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL); + } + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Move the root window to the top of the pile + if (window->RootWindow) + window = window->RootWindow; + + // Steal focus on active widgets + if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) + ClearActiveID(); + + // Bring to front + BringWindowToFocusFront(window); + if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + BringWindowToDisplayFront(window); +} + +void ImGui::FocusPreviousWindowIgnoringOne(ImGuiWindow* ignore_window) +{ + ImGuiContext& g = *GImGui; + for (int i = g.WindowsFocusOrder.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.WindowsFocusOrder[i]; + if (window != ignore_window && window->WasActive && !(window->Flags & ImGuiWindowFlags_ChildWindow)) + { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window); + FocusWindow(focus_window); + return; + } + } +} + +void ImGui::PushItemWidth(float item_width) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (w_full <= 0.0f) + w_full = CalcItemWidth(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components-1; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); +} + +float ImGui::CalcItemWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + float w = window->DC.ItemWidth; + if (w < 0.0f) + { + // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. + float width_to_right_edge = GetContentRegionAvail().x; + w = ImMax(1.0f, width_to_right_edge + w); + } + w = (float)(int)w; + return w; +} + +void ImGui::SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +// FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system. +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColorMod& backup = g.ColorModifiers.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorModifiers.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Count; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[] = +{ + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. + ImGuiStyleMod& backup = g.StyleModifiers.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + void* data = info->GetVarPtr(&g.Style); + if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } + else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } + g.StyleModifiers.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; + case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg"; + case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg"; + } + IM_ASSERT(0); + return "Unknown"; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + + if (flags & ImGuiHoveredFlags_AnyWindow) + { + if (g.HoveredWindow == NULL) + return false; + } + else + { + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + return false; + break; + default: + if (g.HoveredWindow != g.CurrentWindow) + return false; + break; + } + } + + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + + if (flags & ImGuiFocusedFlags_AnyWindow) + return g.NavWindow != NULL; + + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.NavWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.NavWindow == g.CurrentWindow; + } +} + +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) +{ + return window->Active && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +void ImGui::SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) +{ + window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.x = new_scroll_x; + window->DC.CursorMaxPos.x -= window->Scroll.x; +} + +void ImGui::SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.y = new_scroll_y; + window->DC.CursorMaxPos.y -= window->Scroll.y; +} + +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->Pos = ImFloor(pos); + window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = ImFloor(size.x); + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = ImFloor(size.y); + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.SizeConstraintCond = ImGuiCond_Always; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + g.NextWindowData.SizeCallback = custom_callback; + g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; +} + +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. + g.NextWindowData.ContentSizeCond = ImGuiCond_Always; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.BgAlphaVal = alpha; + g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) +} + +// In window space (not screen space!) +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImVec2 mx = window->ContentsRegionRect.Max - window->Pos; + if (window->DC.ColumnsSet) + mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); +} + +float ImGui::GetContentRegionAvailWidth() +{ + return GetContentRegionAvail().x; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Min - window->Pos; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max - window->Pos; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.GetWidth(); +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = screen_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +float ImGui::GetScrollX() +{ + return GImGui->CurrentWindow->Scroll.x; +} + +float ImGui::GetScrollY() +{ + return GImGui->CurrentWindow->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + return GetWindowScrollMaxX(GImGui->CurrentWindow); +} + +float ImGui::GetScrollMaxY() +{ + return GetWindowScrollMaxY(GImGui->CurrentWindow); +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY + window->ScrollTargetCenterRatio.y = 0.0f; +} + +void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) +{ + // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); + window->ScrollTargetCenterRatio.y = center_y_ratio; +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHereY(float center_y_ratio) +{ + ImGuiWindow* window = GetCurrentWindow(); + float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space + target_y += (window->DC.PrevLineSize.y * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. + SetScrollFromPosY(target_y, center_y_ratio); +} + +void ImGui::ActivateItem(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiWindow* window = GetCurrentWindow(); + window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; + window->FocusIdxTabRequestNext = INT_MAX; +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!window->Appearing) + return; + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) + { + g.NavInitRequest = false; + g.NavInitResultId = g.NavWindow->DC.LastItemId; + g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); + NavUpdateAnyRequestFlag(); + if (!IsItemVisible()) + SetScrollHereY(); + } +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.StateStorage; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetIDNoKeepAlive(str_id)); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetIDNoKeepAlive(str_id_begin, str_id_end)); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id)); +} + +void ImGui::PushID(int int_id) +{ + const void* ptr_id = (void*)(intptr_t)int_id; + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id)); +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + return GImGui->CurrentWindow->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + return GImGui->CurrentWindow->GetID(ptr_id); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +void ImGui::BeginGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndent = window->DC.Indent; + group_data.BackupGroupOffset = window->DC.GroupOffset; + group_data.BackupCurrentLineSize = window->DC.CurrentLineSize; + group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; + group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; + group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + group_data.AdvanceCursor = true; + + window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; + window->DC.Indent = window->DC.GroupOffset; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + + ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); + group_bb.Max = ImMax(group_bb.Min, group_bb.Max); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.Indent = group_data.BackupIndent; + window->DC.GroupOffset = group_data.BackupGroupOffset; + window->DC.CurrentLineSize = group_data.BackupCurrentLineSize; + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return + + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, 0); + } + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. + // (and if you grep for LastItemId you'll notice it is only used in that context. + if ((group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow) + window->DC.LastItemId = g.ActiveId; + else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow) + window->DC.LastItemId = g.ActiveIdPreviousFrame; + window->DC.LastItemRect = group_bb; + + window->DC.GroupStack.pop_back(); + + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + +// Gets back to previous line and continue with horizontal layout +// pos_x == 0 : follow right after previous item +// pos_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float pos_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (pos_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrentLineSize = window->DC.PrevLineSize; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; +} + +//----------------------------------------------------------------------------- +// [SECTION] TOOLTIPS +//----------------------------------------------------------------------------- + +void ImGui::BeginTooltip() +{ + ImGuiContext& g = *GImGui; + if (g.DragDropWithinSourceOrTarget) + { + // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor) + // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor. + // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do. + //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; + ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale); + SetNextWindowPos(tooltip_pos); + SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( + BeginTooltipEx(0, true); + } + else + { + BeginTooltipEx(0, false); + } +} + +// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) +{ + ImGuiContext& g = *GImGui; + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (override_previous_tooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + window->Hidden = true; + window->HiddenFramesRegular = 1; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + End(); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + if (g.DragDropWithinSourceOrTarget) + BeginTooltip(); + else + BeginTooltipEx(0, true); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// [SECTION] POPUPS +//----------------------------------------------------------------------------- + +bool ImGui::IsPopupOpen(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; +} + +bool ImGui::IsPopupOpen(const char* str_id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); +} + +ImGuiWindow* ImGui::GetFrontMostPopupModal() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +void ImGui::OpenPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id)); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + int current_stack_size = g.CurrentPopupStack.Size; + ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. + popup_ref.PopupId = id; + popup_ref.Window = NULL; + popup_ref.ParentWindow = parent_window; + popup_ref.OpenFrameCount = g.FrameCount; + popup_ref.OpenParentId = parent_window->IDStack.back(); + popup_ref.OpenMousePos = g.IO.MousePos; + popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); + + //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id); + if (g.OpenPopupStack.Size < current_stack_size + 1) + { + g.OpenPopupStack.push_back(popup_ref); + } + else + { + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui + // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing + // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + { + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + } + else + { + // Close child popups if any, then flag popup for open/reopen + g.OpenPopupStack.resize(current_stack_size + 1); + g.OpenPopupStack[current_stack_size] = popup_ref; + } + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). + // This is equivalent to what ClosePopupToLevel() does. + //if (g.OpenPopupStack[current_stack_size].PopupId == id) + // FocusWindow(parent_window); + } +} + +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id); + return true; + } + return false; +} + +void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.empty()) + return; + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. + // Don't close our own child popup windows. + int n = 0; + if (ref_window) + { + for (n = 0; n < g.OpenPopupStack.Size; n++) + { + ImGuiPopupRef& popup = g.OpenPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) + bool has_focus = false; + for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); + if (!has_focus) + break; + } + } + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); +} + +void ImGui::ClosePopupToLevel(int remaining) +{ + IM_ASSERT(remaining >= 0); + ImGuiContext& g = *GImGui; + ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow; + if (g.NavLayer == 0) + focus_window = NavRestoreLastChildNavWindow(focus_window); + FocusWindow(focus_window); + focus_window->DC.NavHideHighlightOneFrame = true; + g.OpenPopupStack.resize(remaining); +} + +void ImGui::ClosePopup(ImGuiID id) +{ + if (!IsPopupOpen(id)) + return; + ImGuiContext& g = *GImGui; + ClosePopupToLevel(g.OpenPopupStack.Size - 1); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.CurrentPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (extra_flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id)) + { + g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default + // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. + if (g.NextWindowData.PosCond == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopup(id); + return false; + } + return is_open; +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.CurrentPopupStack.Size > 0); + + // Make all menus and popups wrap around for now, may need to expose that policy. + NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY); + + End(); +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. +bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) +{ + if (!str_id) + str_id = "window_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) +{ + if (!str_id) + str_id = "void_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + OpenPopupEx(id); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*) +{ + ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding; + ImRect r_screen = GetViewportRect(); + r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); + return r_screen; +} + +// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) +// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. +ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy) +{ + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) + continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + *last_dir = dir; + return pos; + } + + // Fallback, try to keep within display + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + ImRect r_outer = GetWindowAllowedExtentRect(window); + if (window->Flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(g.CurrentWindow == window); + ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2]; + float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). + ImRect r_avoid; + if (parent_window->DC.MenuBarAppending) + r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight()); + else + r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); + } + if (window->Flags & ImGuiWindowFlags_Popup) + { + ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); + } + if (window->Flags & ImGuiWindowFlags_Tooltip) + { + // Position tooltip (always follows mouse) + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = NavCalcPreferredRefPos(); + ImRect r_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) + pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + return pos; + } + IM_ASSERT(0); + return window->Pos; +} + +//----------------------------------------------------------------------------- +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +//----------------------------------------------------------------------------- + +ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) +{ + if (ImFabs(dx) > ImFabs(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; +} + +static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +{ + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; +} + +static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) +{ + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) + { + r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); + r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); + } + else + { + r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); + r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); + } +} + +// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; + + const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; + + // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring + if (window->ParentWindow == g.NavWindow) + { + IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); + if (!window->ClipRect.Contains(cand)) + return false; + cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window + } + + // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + // For example, this ensure that items in one column are not reached when moving vertically from items in another column. + NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = ImFabs(dbx) + ImFabs(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = ImGetDirQuadrantFromDelta(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + ImDrawList* draw_list = ImGui::GetOverlayDrawList(); + draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); + draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + ImDrawList* draw_list = ImGui::GetOverlayDrawList(); + draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + #endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + + // Process Init Request + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Process Move Request (scoring for navigation) + // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) + if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav)) + { + ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + { + result->ID = id; + result->Window = window; + result->RectRel = nav_bb_rel; + } + + const float VISIBLE_RATIO = 0.70f; + if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) + if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) + if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) + { + result = &g.NavMoveResultLocalVisibleSet; + result->ID = id; + result->Window = window; + result->RectRel = nav_bb_rel; + } + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavIdIsAlive = true; + g.NavIdTabCounter = window->FocusIdxTabCounter; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +bool ImGui::NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +void ImGui::NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); + ImGui::NavMoveRequestCancel(); + g.NavMoveDir = move_dir; + g.NavMoveClipDir = clip_dir; + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + g.NavMoveRequestFlags = move_flags; + g.NavWindow->NavRectRel[g.NavLayer] = bb_rel; +} + +void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0) + return; + IM_ASSERT(move_flags != 0); // No points calling this with no wrapping + ImRect bb_rel = window->NavRectRel[0]; + + ImGuiDir clip_dir = g.NavMoveDir; + if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } +} + +static void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window) +{ + ImGuiWindow* parent_window = nav_window; + while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent_window = parent_window->ParentWindow; + if (parent_window && parent_window != nav_window) + parent_window->NavLastChildNavWindow = nav_window; +} + +// Call when we are expected to land on Layer 0 after FocusWindow() +static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; +} + +static void NavRestoreLayer(int layer) +{ + ImGuiContext& g = *GImGui; + g.NavLayer = layer; + if (layer == 0) + g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); + if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) + ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); + else + ImGui::NavInitWindow(g.NavWindow, true); +} + +static inline void ImGui::NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); + if (g.NavAnyRequest) + IM_ASSERT(g.NavWindow != NULL); +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + bool init_for_nav = false; + if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + if (init_for_nav) + { + SetNavID(0, g.NavLayer); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + } +} + +static ImVec2 ImGui::NavCalcPreferredRefPos() +{ + ImGuiContext& g = *GImGui; + if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) + return ImFloor(g.IO.MousePos); + + // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item + const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImRect visible_rect = GetViewportRect(); + return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) + return 0.0f; + if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiInputReadMode_Repeat) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiInputReadMode_RepeatSlow) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiInputReadMode_RepeatFast) + return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +// Scroll to keep newly navigated item fully into view +// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. +static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect) +{ + ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1)); + //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] + if (window_rect.Contains(item_rect)) + return; + + ImGuiContext& g = *GImGui; + if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x) + { + window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x) + { + window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 1.0f; + } + if (item_rect.Min.y < window_rect.Min.y) + { + window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + else if (item_rect.Max.y >= window_rect.Max.y) + { + window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 1.0f; + } +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + g.IO.WantSetMousePos = false; +#if 0 + if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard) + bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (nav_gamepad_active) + if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f) + g.NavInputSource = ImGuiInputSource_NavGamepad; + + // Update Keyboard->Nav inputs mapping + if (nav_keyboard_active) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; + #undef NAV_MAP_KEY + } + memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) + g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + { + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + IM_ASSERT(g.NavWindow); + if (g.NavInitRequestFromMove) + SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + else + SetNavID(g.NavInitResultId, g.NavLayer); + g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + } + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) + NavUpdateMoveResult(); + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the navigated item position from last frame + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + { + if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + { + g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos(); + g.IO.WantSetMousePos = true; + } + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindow(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) + g.NavWindow->NavLastChildNavWindow = NULL; + + // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) + NavUpdateWindowing(); + + // Set output flags for user application + g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + if (g.ActiveId != 0) + { + ClearActiveID(); + } + else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, 0); + g.NavIdIsAlive = false; + if (g.NavDisableMouseHover) + g.NavMousePosDirty = true; + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + } + else if (g.NavLayer != 0) + { + // Leave the "menu" layer + NavRestoreLayer(0); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + g.NavMoveRequestFlags = 0; + if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with Nav directional keys when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + { + SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultLocalVisibleSet.Clear(); + g.NavMoveResultOther.Clear(); + + // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); + g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); + g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y); + g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); + g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; + IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). + //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } +#endif +} + +static void ImGui::NavUpdateMoveResult() +{ + // Select which result to use + ImGuiContext& g = *GImGui; + ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + + // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. + if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) + if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) + result = &g.NavMoveResultLocalVisibleSet; + + // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. + if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) + if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) + result = &g.NavMoveResultOther; + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view. + if (g.NavLayer == 0) + { + ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); + NavScrollToBringItemIntoView(result->Window, rect_abs); + + // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate() + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false); + ImVec2 delta_scroll = result->Window->Scroll - next_scroll; + result->RectRel.Translate(delta_scroll); + + // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy). + if (result->Window->Flags & ImGuiWindowFlags_ChildWindow) + NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll)); + } + + // Apply result from previous frame navigation directional move request + ClearActiveID(); + g.NavWindow = result->Window; + SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel); + g.NavJustMovedToId = result->ID; + g.NavMoveFromClampedRefRect = false; +} + +static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) +{ + ImGuiContext& g = *GImGui; + if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); + bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); + if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held)) + { + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) + { + // Fallback manual-scroll when window has no navigable item + if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) + SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight()); + else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) + SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight()); + } + else + { + const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; + const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + float nav_scoring_rect_offset_y = 0.0f; + if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) + { + nav_scoring_rect_offset_y = -page_offset_y; + g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Up; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) + { + nav_scoring_rect_offset_y = +page_offset_y; + g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Down; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + return nav_scoring_rect_offset_y; + } + } + } + return 0.0f; +} + +static int FindWindowFocusIndex(ImGuiWindow* window) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = g.WindowsFocusOrder.Size-1; i >= 0; i--) + if (g.WindowsFocusOrder[i] == window) + return i; + return -1; +} + +static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i])) + return g.WindowsFocusOrder[i]; + return NULL; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowFocusIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir); + if (window_target) // Don't reset windowing target if there's a single window in the list + g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; + g.NavWindowingToggleLayer = false; +} + +// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + ImGuiWindow* modal_window = GetFrontMostPopupModal(); + if (modal_window != NULL) + { + g.NavWindowingTarget = NULL; + return; + } + + // Fade out + if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) + { + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); + if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) + g.NavWindowingTargetAnim = NULL; + } + + // Start CTRL-TAB or Square+L/R window selection + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) + { + g.NavWindowingTarget = g.NavWindowingTargetAnim = window; + g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + } + + // Gamepad update + g.NavWindowingTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_NavGamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well + g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed; + g.NavDisableMouseHover = true; + MarkIniSettingsDirty(g.NavWindowingTarget); + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + // Move to parent menu if necessary + ImGuiWindow* new_nav_window = g.NavWindow; + while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); + } +} + +// Window has already passed the IsWindowNavFocusable() +static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) +{ + if (window->Flags & ImGuiWindowFlags_Popup) + return "(Popup)"; + if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) + return "(Main menu bar)"; + return "(Untitled)"; +} + +// Overlay displayed when using CTRL+TAB. Called by EndFrame(). +void ImGui::NavUpdateWindowingList() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget != NULL); + + if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) + return; + + if (g.NavWindowingList == NULL) + g.NavWindowingList = FindWindowByName("###NavWindowingList"); + SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); + Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) + { + ImGuiWindow* window = g.WindowsFocusOrder[n]; + if (!IsWindowNavFocusable(window)) + continue; + const char* label = window->Name; + if (label == FindRenderedTextEnd(label)) + label = GetFallbackWindowNameForWindowingList(window); + Selectable(label, g.NavWindowingTarget == window); + } + End(); + PopStyleVar(); +} + +//----------------------------------------------------------------------------- +// [SECTION] COLUMNS +// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system. +//----------------------------------------------------------------------------- + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffset.x = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->LineMinY = columns->LineMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->DC.CursorPos.y = columns->LineMinY; + window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x); + columns->MinX = window->DC.Indent.x - g.Style.ItemSpacing.x; // Lock our horizontal range + columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f); + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count; n++) + { + // Compute clipping rectangle + ImGuiColumnData* column = &columns->Columns[n]; + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->LineMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = columns->StartMaxPosX; // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); +} + +// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags) + return; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +//----------------------------------------------------------------------------- +// [SECTION] DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptFlags = 0; + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; + + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + int mouse_button = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + else + { + g.ActiveIdAllowOverlap = false; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + g.DragDropSourceFrameCount = g.FrameCount; + g.DragDropWithinSourceOrTarget = true; + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) + // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. + BeginTooltip(); + if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + { + ImGuiWindow* tooltip_window = g.CurrentWindow; + tooltip_window->SkipItems = true; + tooltip_window->HiddenFramesRegular = 1; + } + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?"); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + EndTooltip(); + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); + g.DragDropWithinSourceOrTarget = false; +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy(payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy(payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + if (window->SkipItems) + return false; + + IM_ASSERT(g.DragDropWithinSourceOrTarget == false); + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + g.DragDropWithinSourceOrTarget = true; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + IM_ASSERT(g.DragDropWithinSourceOrTarget == false); + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + g.DragDropWithinSourceOrTarget = true; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptFlags = flags; + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1)); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +const ImGuiPayload* ImGui::GetDragDropPayload() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive ? &g.DragDropPayload : NULL; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinSourceOrTarget); + g.DragDropWithinSourceOrTarget = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] LOGGING/CAPTURING +//----------------------------------------------------------------------------- + +// Pass text data straight to log (without being displayed) +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + if (g.LogFile) + vfprintf(g.LogFile, fmt, args); + else + g.LogClipboard.appendfv(fmt, args); + va_end(args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; + + const char* text_remaining = text; + if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth + g.LogStartDepth = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. + const char* line_start = text_remaining; + const char* line_end = ImStreolRange(line_start, text_end); + const bool is_first_line = (line_start == text); + const bool is_last_line = (line_end == text_end); + if (!is_last_line || (line_start != line_end)) + { + const int char_count = (int)(line_end - line_start); + if (log_new_line || !is_first_line) + LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, line_start); + else + LogText(" %.*s", char_count, line_start); + } + + if (is_last_line) + break; + text_remaining = line_end + 1; + } +} + +// Start logging ImGui output to TTY +void ImGui::LogToTTY(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = stdout; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to given file +void ImGui::LogToFile(int max_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + if (!filename) + { + filename = g.IO.LogFilename; + if (!filename) + return; + } + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = ImFileOpen(filename, "ab"); + if (!g.LogFile) + { + IM_ASSERT(g.LogFile != NULL); // Consider this an error + return; + } + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to clipboard +void ImGui::LogToClipboard(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.LogFile == NULL); + g.LogFile = NULL; + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + if (g.LogFile != NULL) + { + if (g.LogFile == stdout) + fflush(g.LogFile); + else + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard.size() > 1) + { + SetClipboardText(g.LogClipboard.begin()); + g.LogClipboard.clear(); + } + g.LogEnabled = false; +} + +// Helper to display logging buttons +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); + const bool log_to_tty = Button("Log To TTY"); SameLine(); + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushItemWidth(80.0f); + PushAllowKeyboardFocus(false); + SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopItemWidth(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(g.LogAutoExpandMaxDepth); + if (log_to_file) + LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); + if (log_to_clipboard) + LogToClipboard(g.LogAutoExpandMaxDepth); +} + +//----------------------------------------------------------------------------- +// [SECTION] SETTINGS +//----------------------------------------------------------------------------- + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + g.SettingsWindows.push_back(ImGuiWindowSettings()); + ImGuiWindowSettings* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->ID = ImHash(name, 0); + return settings; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.SettingsWindows.Size; i++) + if (g.SettingsWindows[i].ID == id) + return &g.SettingsWindows[i]; + return NULL; +} + +void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) +{ + size_t file_data_size = 0; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); + ImGui::MemFree(file_data); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) +{ + ImGuiContext& g = *GImGui; + const ImGuiID type_hash = ImHash(type_name, 0, 0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); + + // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). + // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. + if (ini_size == 0) + ini_size = strlen(ini_data); + char* buf = (char*)ImGui::MemAlloc(ini_size + 1); + char* buf_end = buf + ini_size; + memcpy(buf, ini_data, ini_size); + buf[ini_size] = 0; + + void* entry_data = NULL; + ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + if (line[0] == ';') + continue; + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + { + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; + } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + entry_handler = FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + ImGui::MemFree(buf); + g.SettingsLoaded = true; +} + +void ImGui::SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + size_t ini_data_size = 0; + const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(ini_data, sizeof(char), ini_data_size, f); + fclose(f); +} + +// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer +const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + g.SettingsIniData.Buf.resize(0); + g.SettingsIniData.Buf.push_back(0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &g.SettingsIniData); + } + if (out_size) + *out_size = (size_t)g.SettingsIniData.size(); + return g.SettingsIniData.c_str(); +} + +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); + if (!settings) + settings = ImGui::CreateNewWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + // (if a window wasn't opened in this session we preserve its settings) + ImGuiContext& g = *imgui_ctx; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + + ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID); + if (!settings) + { + settings = ImGui::CreateNewWindowSettings(window->Name); + window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); + } + IM_ASSERT(settings->ID == window->ID); + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write to text buffer + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[%s][%s]\n", handler->TypeName, name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef __MINGW32__ +#include +#else +#include +#endif +#endif + +// Win32 API clipboard implementation +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#endif + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + static ImVector buf_local; + buf_local.clear(); + if (!::OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + ::CloseClipboard(); + return NULL; + } + if (ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle)) + { + int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; + buf_local.resize(buf_len); + ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); + } + ::GlobalUnlock(wbuf_handle); + ::CloseClipboard(); + return buf_local.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!::OpenClipboard(NULL)) + return; + const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; + HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); + if (wbuf_handle == NULL) + { + ::CloseClipboard(); + return; + } + ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle); + ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); + ::GlobalUnlock(wbuf_handle); + ::EmptyClipboard(); + if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL) + ::GlobalFree(wbuf_handle); + ::CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.PrivateClipboard.clear(); + const char* text_end = text + strlen(text); + g.PrivateClipboard.resize((int)(text_end - text) + 1); + memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); + g.PrivateClipboard[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) + if (HIMC himc = ::ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ::ImmSetCompositionWindow(himc, &cf); + ::ImmReleaseContext(hwnd, himc); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// [SECTION] METRICS/DEBUG WINDOW +//----------------------------------------------------------------------------- + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (!ImGui::Begin("ImGui Metrics", p_open)) + { + ImGui::End(); + return; + } + static bool show_draw_cmd_clip_rects = true; + static bool show_window_begin_order = false; + ImGuiIO& io = ImGui::GetIO(); + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); + ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); + ImGui::Text("%d allocations", io.MetricsActiveAllocations); + ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects); + ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order); + + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) + { + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) + { + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } + + ImDrawList* overlay_draw_list = GetOverlayDrawList(); // Render additional visuals into the top-most draw list + if (window && IsItemHovered()) + overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; + + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) + { + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_draw_cmd_clip_rects && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) + { + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + ImGui::Selectable(buf, false); + if (ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + static void NodeWindows(ImVector& windows, const char* label) + { + if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) + return; + for (int i = 0; i < windows.Size; i++) + Funcs::NodeWindow(windows[i], "Window"); + ImGui::TreePop(); + } + + static void NodeWindow(ImGuiWindow* window, const char* label) + { + if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) + return; + ImGuiWindowFlags flags = window->Flags; + NodeDrawList(window, window->DrawList, "DrawList"); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "", + (flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : ""); + ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetWindowScrollMaxX(window), window->Scroll.y, GetWindowScrollMaxY(window)); + ImGui::BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); + ImGui::BulletText("Appearing: %d, Hidden: %d (Reg %d Resize %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesRegular, window->HiddenFramesForResize, window->SkipItems); + ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); + ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + if (!window->NavRectRel[0].IsInverted()) + ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); + else + ImGui::BulletText("NavRectRel[0]: "); + if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow"); + if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); + if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) + { + for (int n = 0; n < window->ColumnsStorage.Size; n++) + { + const ImGuiColumnsSet* columns = &window->ColumnsStorage[n]; + if (ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags)) + { + ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->MaxX - columns->MinX, columns->MinX, columns->MaxX); + for (int column_n = 0; column_n < columns->Columns.Size; column_n++) + ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, OffsetNormToPixels(columns, columns->Columns[column_n].OffsetNorm)); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); + ImGui::TreePop(); + } + }; + + // Access private state, we are going to display the draw lists from last frame + ImGuiContext& g = *GImGui; + Funcs::NodeWindows(g.Windows, "Windows"); + if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) + { + for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) + Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Internal state")) + { + const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); + ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); + ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]); + ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); + ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + ImGui::TreePop(); + } + + + if (g.IO.KeyCtrl && show_window_begin_order) + { + for (int n = 0; n < g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if ((window->Flags & ImGuiWindowFlags_ChildWindow) || !window->WasActive) + continue; + char buf[32]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); + float font_size = ImGui::GetFontSize() * 2; + ImDrawList* overlay_draw_list = GetOverlayDrawList(); + overlay_draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); + overlay_draw_list->AddText(NULL, font_size, window->Pos, IM_COL32(255, 255, 255, 255), buf); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- diff --git a/src/imgui/imgui.h b/src/imgui/imgui.h new file mode 100644 index 000000000..90cde6304 --- /dev/null +++ b/src/imgui/imgui.h @@ -0,0 +1,1993 @@ +// dear imgui, v1.66 WIP +// (headers) + +// See imgui.cpp file for documentation. +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui + +#pragma once + +// Configuration file (edit imconfig.h or define IMGUI_USER_CONFIG to set your own filename) +#ifdef IMGUI_USER_CONFIG +#include IMGUI_USER_CONFIG +#endif +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#include // FLT_MAX +#include // va_list +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +// Version +// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY00 then bounced up to XYY01 when release tagging happens) +#define IMGUI_VERSION "1.66 WIP" +#define IMGUI_VERSION_NUM 16600 +#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert)) + +// Define attributes of all API symbols declarations (e.g. for DLL under Windows) +// IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default bindings files (imgui_impl_xxx.h) +#ifndef IMGUI_API +#define IMGUI_API +#endif +#ifndef IMGUI_IMPL_API +#define IMGUI_IMPL_API IMGUI_API +#endif + +// Helpers +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h +#endif +#if defined(__clang__) || defined(__GNUC__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) // Apply printf-style warnings to user functions. +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) // Size of a static C-style array. Don't use on pointers! +#define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +// Forward declarations +struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) +struct ImDrawData; // All draw command lists required to render the frame +struct ImDrawList; // A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*obsolete* please avoid using) +#ifndef ImTextureID +typedef void* ImTextureID; // User data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +#endif +struct ImGuiContext; // ImGui context (opaque) +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) +struct ImGuiStorage; // Helper for key->value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbb][,ccccc]") +struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) + +// Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file) +// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +typedef unsigned int ImGuiID; // Unique ID used by widgets (typically hashed from a stack of string) +typedef unsigned short ImWchar; // Character for keyboard input/display +typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling +typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for Set*() +typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type +typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction +typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A key identifier (ImGui-side enum) +typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation +typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling +typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect*() etc. +typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas +typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags +typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit*(), ColorPicker*() +typedef int ImGuiColumnsFlags; // -> enum ImGuiColumnsFlags_ // Flags: for Columns(), BeginColumns() +typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags +typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() +typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for *DragDrop*() +typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() +typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. +typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText*() +typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() +typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode*(),CollapsingHeader() +typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin*() +typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData *data); +typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); + +// Scalar data types +typedef signed int ImS32; // 32-bit signed integer == int +typedef unsigned int ImU32; // 32-bit unsigned integer (often used to store packed colors) +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 ImS64; // 64-bit signed integer (pre and post C++11 with Visual Studio) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer (pre and post C++11 with Visual Studio) +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t ImS64; // 64-bit signed integer (pre C++11) +typedef uint64_t ImU64; // 64-bit unsigned integer (pre C++11) +#else +typedef signed long long ImS64; // 64-bit signed integer (post C++11) +typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) +#endif + +// 2D vector (often used to store positions, sizes, etc.) +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } + float operator[] (size_t i) const { IM_ASSERT(i <= 1); return (&x)[i]; } // We very rarely use this [] operator, the assert overhead is fine. +#ifdef IM_VEC2_CLASS_EXTRA + IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. +#endif +}; + +// 4D vector (often used to store floating-point colors) +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA + IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4. +#endif +}; + +// Dear ImGui end-user API +// (In a namespace so you can add extra functions in your own separate file. Please don't modify imgui.cpp/.h!) +namespace ImGui +{ + // Context creation and access + // Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between imgui contexts. + // All those functions are not reliant on the current context. + IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = destroy current context + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert); + + // Main + IMGUI_API ImGuiIO& GetIO(); // access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) + IMGUI_API ImGuiStyle& GetStyle(); // access the Style structure (colors, sizes). Always use PushStyleCol(), PushStyleVar() to modify style mid-frame. + IMGUI_API void NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), you likely don't need to call that yourself directly. If you don't need to render data (skipping rendering) you may call EndFrame() but you'll have wasted CPU already! If you don't need to render, better to not create any imgui windows and not call NewFrame() at all! + IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.) + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.) + + // Demo, Debug, Information + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); // add style selector block (not a window), essentially a combo listing the default styles. + IMGUI_API void ShowFontSelector(const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API const char* GetVersion(); // get the compiled version string e.g. "1.23" + + // Styles + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // new, recommended style (default) + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // best used with borders and a custom, thicker font + + // Windows + // - Begin() = push window to the stack and start appending to it. End() = pop window from the stack. + // - You may append multiple times to the same window during the same frame. + // - Passing 'bool* p_open != NULL' shows a window-closing widget in the upper-right corner of the window, which clicking will set the boolean to false when clicked. + // - Begin() return false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. + // Always call a matching End() for each Begin() call, regardless of its return value [this is due to legacy reason and is inconsistent with most other functions such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function returned true.] + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); + IMGUI_API void End(); + + // Child Windows + // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. + // - For each independent axis of 'size': ==0.0f: use remaining host window size / >0.0f: fixed size / <0.0f: use remaining window size minus abs(size) / Each axis can use a different mode, e.g. ImVec2(0,400). + // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. + // Always call a matching EndChild() for each BeginChild() call, regardless of its return value [this is due to legacy reason and is inconsistent with most other functions such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function returned true.] + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); + IMGUI_API void EndChild(); + + // Windows Utilities + IMGUI_API bool IsWindowAppearing(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ! + IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the window, to append your own drawing primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList API) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) + IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API float GetContentRegionAvailWidth(); // + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() + IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowFontScale(float scale); // set font scale. Adjust IO.FontGlobalScale if you want to scale all windows + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / front-most. use NULL to remove focus. + + // Windows Scrolling + IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] + IMGUI_API void SetScrollHereY(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in. + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side) + IMGUI_API void PopItemWidth(); + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position + IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Cursor / Layout + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally + IMGUI_API void NewLine(); // undo a SameLine() + IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API void EndGroup(); + IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position + IMGUI_API float GetCursorPosX(); // " + IMGUI_API float GetCursorPosY(); // " + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " + IMGUI_API void SetCursorPosX(float x); // " + IMGUI_API void SetCursorPosY(float y); // " + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) + IMGUI_API void SetCursorScreenPos(const ImVec2& screen_pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API void AlignTextToFramePadding(); // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items (call if you have text on a line before a framed item) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // ID stack/scopes + // Read the FAQ for more details about how ID are handled in dear imgui. If you are creating widgets in a loop you most + // likely want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. + // You can also use the "##foobar" syntax within widget label to distinguish them from each others. + // In this header file we use the "label"/"name" terminology to denote a string that will be displayed and used as an ID, + // whereas "str_id" denote a string that is only used as an ID and not aimed to be displayed. + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + + // Widgets: Main + // Most widgets return true when the value has been changed or when pressed/selected + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API bool ArrowButton(const char* str_id, ImGuiDir dir); // square button with an arrow shape + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; } + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); // shortcut to handle the above pattern when value is an integer + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. + // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) + // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + // Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. + // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", const char* format_max = NULL, float power = 1.0f); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d"); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d"); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d"); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d"); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL); + IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min = NULL, const void* v_max = NULL, const char* format = NULL, float power = 1.0f); + IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min = NULL, const void* v_max = NULL, const char* format = NULL, float power = 1.0f); + + // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) + // Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for power curve sliders + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f, const char* format = "%.0f deg"); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* format = "%d"); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format = "%d"); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format = "%d"); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d"); + IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f); + IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d"); + IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f); + + // Widgets: Input with Keyboard + // If you want to use InputText() with a dynamic string type such as std::string or your own, see misc/cpp/imgui_stdlib.h + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputDouble(const char* label, double* v, double step = 0.0f, double step_fast = 0.0f, const char* format = "%.6f", ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputScalar(const char* label, ImGuiDataType data_type, void* v, const void* step = NULL, const void* step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step = NULL, const void* step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags extra_flags = 0); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0)); // display a colored square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + // TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents. + IMGUI_API bool TreeNode(const char* label); + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // helper variation to completely decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired. + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header + + // Widgets: Selectables + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + + // Widgets: List Boxes + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. if the function return true, you can output elements then call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region. only call ListBoxFooter() if ListBoxHeader() returned true! + + // Widgets: Data Plotting + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); + + // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Widgets: Menus + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. + IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). + IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Tooltips + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of items). + IMGUI_API void EndTooltip(); + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + + // Popups + IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returns true! + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // modal dialog (regular window with title bar, block interactions behind the modal window, can't close the modal window by clicking outside) + IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors). return true when just opened. + IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. + + // Columns + // You can also use SameLine(pos_x) for simplified columns. The columns API is work-in-progress and rather lacking (columns are arguably the worst part of dear imgui at the moment!) + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty + IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + + // Drag and Drop + // [BETA API] Missing Demo code. API may evolve. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive a payload. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! + IMGUI_API const ImGuiPayload* GetDragDropPayload(); // peek directly into the current payload from anywhere. may return NULL. use ImGuiPayload::IsDataType() to test for the payload type. + + // Clipping + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Focus, Activation + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item") + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Item/Widgets Utilities + // See Demo Window under "Widgets->Querying Status" for an interactive visualization of many of those functions. + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) + IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) == IsMouseClicked(mouse_button) && IsItemHovered() + IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) + IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets. + IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing. + IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). + IMGUI_API bool IsAnyItemHovered(); + IMGUI_API bool IsAnyItemActive(); + IMGUI_API bool IsAnyItemFocused(); + IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space + IMGUI_API ImVec2 GetItemRectMax(); // " + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + + // Miscellaneous Utilities + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API double GetTime(); + IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); // you may use this when creating your own ImDrawList instances + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); + IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + + // Color Utilities + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs Utilities + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]! + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API bool IsMouseDown(int button); // is mouse button held (0=left, 1=right, 2=middle) + IMGUI_API bool IsAnyMouseDown(); // is any mouse button held + IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) (0=left, 1=right, 2=middle) + IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. + IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) + IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse position at the time of opening popup we have BeginPopup() into + IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API void ResetMouseDragDelta(int button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type + IMGUI_API void CaptureKeyboardFromApp(bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application to handle). e.g. force capture keyboard when your widget is being hovered. + IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application to handle). + + // Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard) + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Settings/.Ini Utilities + // The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). + // Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. + IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). + IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. + IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); + IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. + + // Memory Utilities + // All those functions are not reliant on the current context. + // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again. + IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data = NULL); + IMGUI_API void* MemAlloc(size_t size); + IMGUI_API void MemFree(void* ptr); + +} // namespace ImGui + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_None = 0, + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + ImGuiWindowFlags_NoBackground = 1 << 7, // Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoMouseInputs = 1 << 9, // Disable catching mouse, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, + ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + + // [Internal] + ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() + + // [Obsolete] + //ImGuiWindowFlags_ShowBorders = 1 << 7, // --> Set style.FrameBorderSize=1.0f / style.WindowBorderSize=1.0f to enable borders around windows and items + //ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // --> Set io.ConfigResizeWindowsFromEdges and make sure mouse cursors are supported by back-end (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_None = 0, + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) + ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_None = 0, + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 13, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap +#endif +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_None = 0, + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too + ImGuiSelectableFlags_Disabled = 1 << 3 // Cannot be selected, display greyed out text +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_None = 0, + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_NoArrowButton = 1 << 5, // Display on the preview box without the square arrow button + ImGuiComboFlags_NoPreview = 1 << 6, // Display only a square arrow button + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_None = 0, + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +// Note: if you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that. Please read the FAQ! +// Note: windows with the ImGuiWindowFlags_NoInputs flag are ignored by IsWindowHovered() calls. +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_None = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 3, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_AllowWhenDisabled = 1 << 7, // Return true even if the item is disabled + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + ImGuiDragDropFlags_None = 0, + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + ImGuiDragDropFlags_SourceAutoExpirePayload = 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3]: Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4]: Standard type for colors. User code may use this type. + +// A primary data type +enum ImGuiDataType_ +{ + ImGuiDataType_S32, // int + ImGuiDataType_U32, // unsigned int + ImGuiDataType_S64, // long long, __int64 + ImGuiDataType_U64, // unsigned long long, unsigned __int64 + ImGuiDataType_Float, // float + ImGuiDataType_Double, // double + ImGuiDataType_COUNT +}; + +// A cardinal direction +enum ImGuiDir_ +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_COUNT +}; + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// Gamepad/Keyboard directional navigation +// Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. +// Gamepad: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. Back-end: set ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). +// Read instructions in imgui.cpp for more details. Download PNG/PSD at http://goo.gl/9LgVZW. +enum ImGuiNavInput_ +{ + // Gamepad Mapping + ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Cross (PS4), A (Xbox), A (Switch), Space (Keyboard) + ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Circle (PS4), B (Xbox), B (Switch), Escape (Keyboard) + ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them. + // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) will be directly reading from io.KeysDown[] instead of io.NavInputs[]. + ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt + ImGuiNavInput_KeyLeft_, // move left // = Arrow keys + ImGuiNavInput_KeyRight_, // move right + ImGuiNavInput_KeyUp_, // move up + ImGuiNavInput_KeyDown_, // move down + ImGuiNavInput_COUNT, + ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ +}; + +// Configuration flags stored in io.ConfigFlags. Set by user/application. +enum ImGuiConfigFlags_ +{ + ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeysDown[]. + ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill io.NavInputs[]. Back-end also needs to set ImGuiBackendFlags_HasGamepad. + ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth. + ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. + ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the back-end. + ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct back-end to not alter mouse cursor shape and visibility. Use if the back-end cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. + + // User storage (to allow your back-end/engine to communicate to code that may be shared between multiple projects. Those flags are not used by core ImGui) + ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. + ImGuiConfigFlags_IsTouchScreen = 1 << 21 // Application is using a touch screen instead of a mouse. +}; + +// Back-end capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom back-end. +enum ImGuiBackendFlags_ +{ + ImGuiBackendFlags_HasGamepad = 1 << 0, // Back-end supports gamepad and currently has one connected. + ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Back-end supports honoring GetMouseCursor() value to change the OS cursor shape. + ImGuiBackendFlags_HasSetMousePos = 1 << 2 // Back-end supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_DragDropTarget, + ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item + ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB + ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active + ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active + ImGuiCol_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive + , ImGuiCol_ModalWindowDarkening = ImGuiCol_ModalWindowDimBg + //ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered, // [unused since 1.60+] the close button now uses regular button colors. + //ImGuiCol_ComboBg, // [unused since 1.53+] ComboBg has been merged with PopupBg, so a redirect isn't accurate. +#endif +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly. +// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiStyleVar_Count_ = ImGuiStyleVar_COUNT, ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding +#endif +}; + +// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_None = 0, + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead. + ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. + + // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup. + ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_RGB = 1 << 20, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX. + ImGuiColorEditFlags_HSV = 1 << 21, // [Inputs] // " + ImGuiColorEditFlags_HEX = 1 << 22, // [Inputs] // " + ImGuiColorEditFlags_Uint8 = 1 << 23, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. + + // [Internal] Masks + ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() +}; + +// Enumeration for GetMouseCursor() +// User code may request binding to display given cursor by calling SetMouseCursor(), which is why we have some cursors that are marked unused here +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // (Unused by imgui functions) + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Hand, // (Unused by imgui functions. Use for e.g. hyperlinks) + ImGuiMouseCursor_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiMouseCursor_Count_ = ImGuiMouseCursor_COUNT +#endif +}; + +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always. +enum ImGuiCond_ +{ + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the object/window has no persistently saved data (no entry in .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the object/window is appearing after being hidden/inactive (or the first time) + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing +#endif +}; + +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in ImGui. + ImVec2 WindowPadding; // Padding within a window. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding) + float PopupBorderSize; // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. + ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. + bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +// This is where your app communicate with Dear ImGui. Access via ImGui::GetIO(). +// Read 'Programmer guide' section in .cpp file for general usage. +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Configuration (fill once) // Default value: + //------------------------------------------------------------------ + + ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. + ImGuiBackendFlags BackendFlags; // = 0 // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom back-end to communicate features supported by the back-end. + ImVec2 DisplaySize; // // Main display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. + ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // [obsolete] If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. + ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // [obsolete: just use io.DisplaySize] If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize + + // Miscellaneous configuration options + bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by back-end implementations. + bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl (was called io.OptMacOSXBehaviors prior to 1.63) + bool ConfigInputTextCursorBlink; // = true // Set to false to disable blinking cursor, for users who consider it distracting. (was called: io.OptCursorBlink prior to 1.63) + bool ConfigResizeWindowsFromEdges; // = false // [BETA] Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be the ImGuiWindowFlags_ResizeFromAnySide flag) + + //------------------------------------------------------------------ + // Settings (User Functions) + //------------------------------------------------------------------ + + // Optional: access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // [OBSOLETE since 1.60+] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now! + // You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). See example applications if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawData* data); +#else + // This is only here to keep ImGuiIO the same size, so that IMGUI_DISABLE_OBSOLETE_FUNCTIONS can exceptionally be used outside of imconfig.h. + void* RenderDrawListsFnUnused; +#endif + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. + float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends. + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). + ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by ImGui::NewFrame, all values will be cleared back to zero in ImGui::EndFrame) + + // Functions + IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] + IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string + inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Retrieve after calling NewFrame() + //------------------------------------------------------------------ + + bool WantCaptureMouse; // When io.WantCaptureMouse is true, imgui will use the mouse inputs, do not dispatch them to your main game/application (in both cases, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.). + bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, imgui will use the keyboard inputs, do not dispatch them to your main game/application (in both cases, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). + bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantSetMousePos; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. IMPORTANT: You need to clear io.WantSaveIniSettings yourself. + bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsRenderWindows; // Number of visible windows + int MetricsActiveWindows; // Number of active windows + int MetricsActiveAllocations; // Number of active allocations, updated by MemAlloc/MemFree based on current context. May be off if you have multiple imgui contexts. + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + double MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + float NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.66 (from Sep 2018) + static inline void SetScrollHere(float center_ratio=0.5f){ SetScrollHereY(center_ratio); } + // OBSOLETED in 1.63 (from Aug 2018) + static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } + // OBSOLETED in 1.61 (from Apr 2018) + IMGUI_API bool InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags = 0); // Use the 'const char* format' version instead of 'decimal_precision'! + IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); + // OBSOLETED in 1.60 (from Dec 2017) + static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } + static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) { (void)on_edge; (void)outward; IM_ASSERT(0); return pos; } + // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) + static inline void ShowTestWindow() { return ShowDemoWindow(); } + static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } + static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } + static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } + static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } + // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) + IMGUI_API bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. + static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } + static inline void SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); } + // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. + static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } +} +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +// Helper: Lightweight std::vector<> like class to avoid dragging dependencies (also: Windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// *Important* Our implementation does NOT call C++ constructors/destructors. This is intentional, we do not require it but you have to be mindful of that. Do _not_ use this class as a std::vector replacement in your code! +template +class ImVector +{ +public: + int Size; + int Capacity; + T* Data; + + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ~ImVector() { if (Data) ImGui::MemFree(Data); } + inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(value_type)); return *this; } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int capacity() const { return Capacity; } + inline value_type& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; } + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size,const value_type& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; } + inline void reserve(int new_capacity) + { + if (new_capacity <= Capacity) + return; + value_type* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(value_type)); + if (Data) + { + memcpy(new_data, Data, (size_t)Size * sizeof(value_type)); + ImGui::MemFree(Data); + } + Data = new_data; + Capacity = new_capacity; + } + + // NB: It is forbidden to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden. + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } + inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator erase(const_iterator it, const_iterator it_last){ IM_ASSERT(it >= Data && it < Data+Size && it_last > it && it_last <= Data+Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - count) * sizeof(value_type)); Size -= (int)count; return Data + off; } + inline iterator erase_unsorted(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; if (it < Data+Size-1) memcpy(Data + off, Data + Size - 1, sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); memcpy(&Data[off], &v, sizeof(v)); Size++; return Data + off; } + inline bool contains(const value_type& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } + inline int index_from_pointer(const_iterator it) const { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; return (int)off; } +}; + +// Helper: IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() macros to call MemAlloc + Placement New, Placement Delete + MemFree +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +struct ImNewDummy {}; +inline void* operator new(size_t, ImNewDummy, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewDummy, void*) {} // This is only required so we can use the symetrical new() +#define IM_PLACEMENT_NEW(_PTR) new(ImNewDummy(), _PTR) +#define IM_NEW(_TYPE) new(ImNewDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p); } } + +// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper: Macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete +#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) +#endif + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } + + // [Internal] + struct TextRange + { + const char* b; + const char* e; + + TextRange() { b = e = NULL; } + TextRange(const char* _b, const char* _e) { b = _b; e = _e; } + const char* begin() const { return b; } + const char* end () const { return e; } + bool empty() const { return b == e; } + IMGUI_API void split(char separator, ImVector* out) const; + }; + char InputBuf[256]; + ImVector Filters; + int CountGrep; +}; + +// Helper: Growable text buffer for logging/accumulating text +// (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder') +struct ImGuiTextBuffer +{ + ImVector Buf; + static char EmptyString[1]; + + ImGuiTextBuffer() { } + inline char operator[](int i) { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; } + const char* begin() const { return Buf.Data ? &Buf.front() : EmptyString; } + const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Data ? Buf.Size - 1 : 0; } + bool empty() { return Buf.Size <= 1; } + void clear() { Buf.clear(); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data ? Buf.Data : EmptyString; } + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Key->Value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1) +// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + struct Pair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + Pair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used. +// The callback function should return 0 by default. +// Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details) +// - ImGuiInputTextFlags_CallbackCompletion: Callback on pressing TAB +// - ImGuiInputTextFlags_CallbackHistory: Callback on pressing Up/Down arrows +// - ImGuiInputTextFlags_CallbackAlways: Callback on each iteration +// - ImGuiInputTextFlags_CallbackCharFilter: Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. +// - ImGuiInputTextFlags_CallbackResize: Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. +struct ImGuiInputTextCallbackData +{ + ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + + // Arguments for the different callback events + // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary. + // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. + ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] + char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! + int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() + int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 + bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] + int CursorPos; // // Read-write // [Completion,History,Always] + int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection) + int SelectionEnd; // // Read-write // [Completion,History,Always] + + // Helper functions for text manipulation. + // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. + IMGUI_API ImGuiInputTextCallbackData(); + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImGuiInputTextCallback ImGuiTextEditCallback; // [OBSOLETE 1.63+] Made the names consistent +typedef ImGuiInputTextCallbackData ImGuiTextEditCallbackData; +#endif + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[32+1]; // Data type tag (short user-supplied string, 32 characters max) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Helpers macros to generate 32-bits encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); } +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null. +// Usage: +// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor). +// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. +// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.) +// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. +struct ImGuiListClipper +{ + float StartPosY; + float ItemsHeight; + int ItemsCount, StepNo, DisplayStart, DisplayEnd; + + // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step). + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). + ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). + ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. + + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. +}; + +//----------------------------------------------------------------------------- +// Draw List +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// Draw callbacks for advanced uses. +// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that) +// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()' +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +struct ImDrawCmd +{ + unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates + ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // The draw callback code can access this. + + ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = (ImTextureID)NULL; UserCallback = NULL; UserCallbackData = NULL; } +}; + +// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef) +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together. +// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered. +struct ImDrawChannel +{ + ImVector CmdBuffer; + ImVector IdxBuffer; +}; + +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 + ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 + ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 + ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA + ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience +}; + +enum ImDrawListFlags_ +{ + ImDrawListFlags_AntiAliasedLines = 1 << 0, + ImDrawListFlags_AntiAliasedFill = 1 << 1 +}; + +// Draw command list +// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), but you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + + // [Internal, used while building lists] + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + int _ChannelsCurrent; // [Internal] current channel number (0) + int _ChannelsCount; // [Internal] number of active channels (1+) + ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size) + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) + ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); } + ~ImDrawList() { ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(ImTextureID texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round + IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right + IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order. + IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0); + + // Stateful path API, add points then finish with PathFillConvex() or PathStroke() + inline void PathClear() { _Path.resize(0); } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); } // Note: Anti-aliased filling requires points to be in clockwise order. + inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); } + IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); + IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); + + // Channels + // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end) + IMGUI_API void ChannelsSplit(int channels_count); + IMGUI_API void ChannelsMerge(); + IMGUI_API void ChannelsSetCurrent(int channel_index); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. + + // Internal helpers + // NB: all primitives needs to be reserved via PrimReserve() beforehand! + IMGUI_API void Clear(); + IMGUI_API void ClearFreeMemory(); + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } + IMGUI_API void UpdateClipRect(); + IMGUI_API void UpdateTextureID(); +}; + +// All draw data to render an ImGui frame +// (NB: the style and the naming convention here is a little inconsistent but we preserve them for backward compatibility purpose) +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + ImDrawList** CmdLists; // Array of ImDrawList* to render. The ImDrawList are owned by ImGuiContext and only pointed to from here. + int CmdListsCount; // Number of ImDrawList* to render + int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size + int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size + ImVec2 DisplayPos; // Upper-left position of the viewport to render (== upper-left of the orthogonal projection matrix to use) + ImVec2 DisplaySize; // Size of the viewport to render (== io.DisplaySize for the main viewport) (DisplayPos + DisplaySize == lower-right of the orthogonal projection matrix to use) + + // Functions + ImDrawData() { Valid = false; Clear(); } + ~ImDrawData() { Clear(); } + void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; DisplayPos = DisplaySize = ImVec2(0.f, 0.f); } // The ImDrawList are owned by ImGuiContext! + IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + int OversampleH; // 3 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font + float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + + // [Internal] + char Name[40]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +enum ImFontAtlasFlags_ +{ + ImFontAtlasFlags_None = 0, + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + ImFontAtlasFlags_NoMouseCursors = 1 << 1 // Don't build software mouse cursors into the atlas +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: +// - One or more fonts. +// - Custom graphics data needed to render the shapes needed by Dear ImGui. +// - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). +// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. +// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. +// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. +// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. +// Common pitfalls: +// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the +// atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. +// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction. +// You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, +// - Even though many functions are suffixed with "TTF", OTF data is supported just as well. +// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the future! +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). + IMGUI_API void Clear(); // Clear all input and output. + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // The pitch is always = Width * BytesPerPixels (1 or 4) + // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API bool IsBuilt() { return Fonts.Size > 0 && (TexPixelsAlpha8 != NULL || TexPixelsRGBA32 != NULL); } + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + // NB: Consider using GlyphRangesBuilder to build glyph ranges from textual data. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChineseSimplifiedCommon();// Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). + struct GlyphRangesBuilder + { + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + GlyphRangesBuilder() { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); } + bool GetBit(int n) const { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } + void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array + void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges + }; + + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + // [Internal] + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Internal data + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~62/78 bytes + float FontSize; // // Height of characters, set during loading (don't change after loading) + float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.f,0.f) // Offset font rendering by xx pixels + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() + + // Members: Cold ~18/26 bytes + short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData + ImFontAtlas* ContainerAtlas; // // What we has been loaded into + float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + bool DirtyLookupTables; + int MetricsTotalSurface;// // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void ClearOutputData(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const; + IMGUI_API void SetFallbackChar(ImWchar c); + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif diff --git a/src/imgui/imgui_demo.cpp b/src/imgui/imgui_demo.cpp new file mode 100644 index 000000000..3173ab858 --- /dev/null +++ b/src/imgui/imgui_demo.cpp @@ -0,0 +1,3657 @@ +// dear imgui, v1.66 WIP +// (demo code) + +// Message to the person tempted to delete this file when integrating Dear ImGui into their code base: +// Do NOT remove this file from your project! Think again! It is the most useful reference code that you and other coders +// will want to refer to and call. Have the ImGui::ShowDemoWindow() function wired in an always-available debug menu of +// your game/app! Removing this file from your project is hindering access to documentation for everyone in your team, +// likely leading you to poorer usage of the library. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// If you want to link core Dear ImGui in your shipped builds but want an easy guarantee that the demo will not be linked, +// you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. +// In other situation, whenever you have Dear ImGui available you probably want this to be available for reference. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (that you won't delete) + +// Message to beginner C/C++ programmers about the meaning of the 'static' keyword: +// In this demo code, we frequently we use 'static' variables inside functions. A static variable persist across calls, so it is +// essentially like a global variable but declared inside the scope of the function. We do this as a way to gather code and data +// in the same place, to make the demo source code faster to read, faster to write, and smaller in size. +// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant +// or used in threads. This might be a pattern you will want to use in your code, but most of the real data you would be editing is +// likely going to be stored outside your functions. + +/* + +Index of this file: + +// [SECTION] Forward Declarations, Helpers +// [SECTION] Demo Window / ShowDemoWindow() +// [SECTION] Style Editor / ShowStyleEditor() +// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +// [SECTION] Example App: Debug Log / ShowExampleAppLog() +// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +// [SECTION] Example App: Long Text / ShowExampleAppLongText() +// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() +// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() +// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() +// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include // toupper, isprint +#include // INT_MIN, INT_MAX +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define vsnprintf _vsnprintf +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if (__GNUC__ >= 6) +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif +#endif + +// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations, Helpers +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO +#define IMGUI_DISABLE_DEMO_WINDOWS +#endif + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +// Forward Declarations +static void ShowExampleAppMainMenuBar(); +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppSimpleOverlay(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleMenuFile(); + +// Helper to display a little (?) mark which shows a tooltip when hovered. +static void ShowHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +// Helper to display basic user controls. +void ImGui::ShowUserGuide() +{ + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); + ImGui::BulletText("Click and drag on any empty space to move window."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + if (ImGui::GetIO().FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("Mouse Wheel to scroll."); + ImGui::BulletText("While editing text:\n"); + ImGui::Indent(); + ImGui::BulletText("Hold SHIFT or use mouse to select text."); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Demo Window / ShowDemoWindow() +//----------------------------------------------------------------------------- + +// We split the contents of the big ShowDemoWindow() function into smaller functions (because the link time of very large functions grow non-linearly) +static void ShowDemoWindowWidgets(); +static void ShowDemoWindowLayout(); +static void ShowDemoWindowPopups(); +static void ShowDemoWindowColumns(); +static void ShowDemoWindowMisc(); + +// Demonstrate most Dear ImGui features (this is big function!) +// You may execute this function to experiment with the UI and understand what it does. You may then search for keywords in the code when you are interested by a specific feature. +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Examples Apps (accessible from the "Examples" menu) + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_simple_overlay = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + // Dear ImGui Apps (accessible from the "Help" menu) + static bool show_app_metrics = false; + static bool show_app_style_editor = false; + static bool show_app_about = false; + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_about) + { + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Text("Dear ImGui, %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::End(); + } + + // Demonstrate the various window flags. Typically you would just use the default! + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + static bool no_background = false; + static bool no_bring_to_front = false; + + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; + if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + // We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming. + ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + + // Main body of the Demo window starts here. + if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + + // Most "big" widgets share a common width settings by default. + //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // Use 2/3 of the space for widgets and 1/3 for labels (default) + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // Use fixed width for labels (by passing a negative value), the rest goes to widgets. We choose a width proportional to our font size. + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + if (ImGui::CollapsingHeader("Help")) + { + ImGui::Text("PROGRAMMER GUIDE:"); + ImGui::BulletText("Please see the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); + ImGui::BulletText("Please see the comments in imgui.cpp."); + ImGui::BulletText("Please see the examples/ in application."); + ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); + ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); + ImGui::Separator(); + + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Configuration")) + { + ImGuiIO& io = ImGui::GetIO(); + + if (ImGui::TreeNode("Configuration##2")) + { + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); + ImGui::SameLine(); ShowHelpMarker("Required back-end to feed in gamepad inputs in io.NavInputs[] and set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); + ImGui::SameLine(); ShowHelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); + ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouse); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) // Create a way to restore this flag otherwise we could be stuck completely! + { + if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) + { + ImGui::SameLine(); + ImGui::Text("<>"); + } + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) + io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + } + ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); + ImGui::SameLine(); ShowHelpMarker("Instruct back-end to not alter mouse cursor shape and visibility."); + ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); + ImGui::SameLine(); ShowHelpMarker("Set to false to disable blinking cursor, for users who consider it distracting"); + ImGui::Checkbox("io.ConfigResizeWindowsFromEdges [beta]", &io.ConfigResizeWindowsFromEdges); + ImGui::SameLine(); ShowHelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); ShowHelpMarker("Instruct Dear ImGui to render a mouse cursor for you. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Backend Flags")) + { + ImGuiBackendFlags backend_flags = io.BackendFlags; // Make a local copy to avoid modifying the back-end flags. + ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasGamepad); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasMouseCursors); + ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasSetMousePos); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Style")) + { + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded."); + ShowHelpMarker("Try opening any of the contents below in this window and then click one of the \"Log To\" button."); + ImGui::LogButtons(); + ImGui::TextWrapped("You can also call ImGui::LogText() to output directly to the log without a visual output."); + if (ImGui::Button("Copy \"Hello, world!\" to clipboard")) + { + ImGui::LogToClipboard(); + ImGui::LogText("Hello, world!"); + ImGui::LogFinish(); + } + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Window options")) + { + ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); + ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); + ImGui::Checkbox("No menu", &no_menu); + ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); + ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); + ImGui::Checkbox("No nav", &no_nav); ImGui::SameLine(300); + ImGui::Checkbox("No background", &no_background); + ImGui::Checkbox("No bring to front", &no_bring_to_front); + } + + // All demo contents + ShowDemoWindowWidgets(); + ShowDemoWindowLayout(); + ShowDemoWindowPopups(); + ShowDemoWindowColumns(); + ShowDemoWindowMisc(); + + // End of ShowDemoWindow() + ImGui::End(); +} + +static void ShowDemoWindowWidgets() +{ + if (!ImGui::CollapsingHeader("Widgets")) + return; + + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements (otherwise a Text+SameLine+Button sequence will have the text a little too high by default) + ImGui::AlignTextToFramePadding(); + ImGui::Text("Hold to repeat:"); + ImGui::SameLine(); + + // Arrow buttons with Repeater + static int counter = 0; + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushButtonRepeat(true); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } + ImGui::SameLine(0.0f, spacing); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } + ImGui::PopButtonRepeat(); + ImGui::SameLine(); + ImGui::Text("%d", counter); + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Using the _simplified_ one-liner Combo() api here + // See "Combo" section for examples of how to use the more complete BeginCombo()/EndCombo() api. + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; + static int item_current = 0; + ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); + ImGui::SameLine(); ShowHelpMarker("Refer to the \"Combo\" section below for an explanation of the full BeginCombo/EndCombo API, and demonstration of various flags.\n"); + } + + { + static char str0[128] = "Hello, world!"; + static int i0 = 123; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ShowHelpMarker("USER:\nHold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n\nPROGRAMMER:\nYou can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example (this is not demonstrated in imgui_demo.cpp)."); + + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + + static float f0 = 0.001f; + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + + static double d0 = 999999.00000001; + ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f"); + + static float f1 = 1.e10f; + ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e"); + ImGui::SameLine(); ShowHelpMarker("You can input value using the scientific notation,\n e.g. \"1e+8\" becomes \"100000000\".\n"); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1 = 50, i2 = 42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%"); + + static float f1=1.00f, f2=0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1=0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); + + static float f1=0.123f, f2=0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider float (curve)", &f2, -10.0f, 10.0f, "%.4f", 2.0f); + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + } + + { + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nClick and hold to use drag and drop.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + } + + { + // List box + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //static int listbox_item_current2 = 2; + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); + } + + ImGui::TreePop(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) { }; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static bool align_label_with_current_x_position = false; + ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. + int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. + for (int i = 0; i < 6; i++) + { + // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); + if (i < 3) + { + // Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (node_open) + { + ImGui::Text("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + } + } + if (node_clicked != -1) + { + // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + ImGui::PopStyleVar(); + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); + if (ImGui::CollapsingHeader("Header")) + { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colored Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImGui::Text("Test paragraph 1:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::Text("Test paragraph 2:"); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (Needs a suitable font, try Noto, or Arial Unicode, or M+ fonts. Read misc/fonts/README.txt for details.) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') + // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. + // Instead we are encoding a few strings with hexadecimal constants. Don't do this in your application! + // Please use u8"text in any language" in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges. Read misc/fonts/README.txt for details."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; + //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGuiIO& io = ImGui::GetIO(); + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); + + // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. + // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. + // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) + // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. + // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. + // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float region_sz = 32.0f; + float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz; + float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz; + float zoom = 4.0f; + ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); + ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); + ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); + ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::EndTooltip(); + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 = uses default padding + if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Combo")) + { + // Expose flags as checkbox for the demo + static ImGuiComboFlags flags = 0; + ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", (unsigned int*)&flags, ImGuiComboFlags_PopupAlignLeft); + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", (unsigned int*)&flags, ImGuiComboFlags_NoArrowButton)) + flags &= ~ImGuiComboFlags_NoPreview; // Clear the other flag, as we cannot combine both + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", (unsigned int*)&flags, ImGuiComboFlags_NoPreview)) + flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both + + // General BeginCombo() API, you have full control over your selection data and display type. + // (your selection data could be an index, a pointer to the object, an id for the object, a flag stored in the object itself, etc.) + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; + static const char* item_current = items[0]; // Here our selection is a single pointer stored outside the object. + if (ImGui::BeginCombo("combo 1", item_current, flags)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + bool is_selected = (item_current == items[n]); + if (ImGui::Selectable(items[n], is_selected)) + item_current = items[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) + } + ImGui::EndCombo(); + } + + // Simplified one-liner Combo() API, using values packed in a single constant string + static int item_current_2 = 0; + ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + + // Simplified one-liner Combo() using an array of const char* + static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview + ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); + + // Simplified one-liner Combo() using an accessor function + struct FuncHolder { static bool ItemGetter(void* data, int idx, const char** out_str) { *out_str = ((const char**)data)[idx]; return true; } }; + static int item_current_4 = 0; + ImGui::Combo("combo 4 (function)", &item_current_4, &FuncHolder::ItemGetter, items, IM_ARRAYSIZE(items)); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc). + if (ImGui::TreeNode("Basic")) + { + static bool selection[5] = { false, true, false, false, false }; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selection[3]); + if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[4] = !selection[4]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Single Selection")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Multiple Selection")) + { + ShowHelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same line")) + { + // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically. + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + ImGui::Columns(3, NULL, false); + static bool selected[16] = { 0 }; + for (int i = 0; i < 16; i++) + { + char label[32]; sprintf(label, "Item %d", i); + if (ImGui::Selectable(label, &selected[i])) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; + for (int i = 0; i < 16; i++) + { + ImGui::PushID(i); + if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) + { + int x = i % 4, y = i / 4; + if (x > 0) selected[i - 1] ^= 1; + if (x < 3) selected[i + 1] ^= 1; + if (y > 0) selected[i - 4] ^= 1; + if (y < 3) selected[i + 4] ^= 1; + } + if ((i % 4) < 3) ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + struct TextFilters { static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + + ImGui::Text("Password input"); + static char bufpass[64] = "password123"; + ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-line Text Input")) + { + static bool read_only = false; + static char text[1024*16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + ShowHelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp)"); + ImGui::Checkbox("Read-only", &read_only); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), flags); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Plots Widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Create a dummy array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. + static float values[90] = { 0 }; + static int values_offset = 0; + static double refresh_time = 0.0; + if (!animate || refresh_time == 0.0f) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset+1) % IM_ARRAYSIZE(values); + phase += 0.10f*values_offset; + refresh_time += 1.0f/60.0f; + } + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImColor(114, 144, 154, 200); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool drag_and_drop = true; + static bool options_menu = true; + static bool hdr = false; + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Drag and Drop", &drag_and_drop); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a dummy palette + static bool saved_palette_inited = false; + static ImVec4 saved_palette[32]; + if (!saved_palette_inited) + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_inited = true; + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + // FIXME: Adding a drag and drop example here would be perfect! + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); + static int inputs_mode = 2; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; + if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; + if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; + if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Programmatically set defaults:"); + ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV | ImGuiColorEditFlags_PickerHueBar); + if (ImGui::Button("Default: Float + HDR + Hue Wheel")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Data Types")) + { + // The DragScalar/InputScalar/SliderScalar functions allow various data types: signed/unsigned int/long long and float/double + // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum to pass the type, + // and passing all arguments by address. + // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types. + // In practice, if you frequently use a given type that is not covered by the normal API entry points, you can wrap it + // yourself inside a 1 line function which can take typed argument as value instead of void*, and then pass their address + // to the generic function. For example: + // bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") + // { + // return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); + // } + + // Limits (as helper variables that we can take the address of) + // Note that the SliderScalar function has a maximum usable range of half the natural type maximum, hence the /2 below. + #ifndef LLONG_MIN + ImS64 LLONG_MIN = -9223372036854775807LL - 1; + ImS64 LLONG_MAX = 9223372036854775807LL; + ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1); + #endif + const ImS32 s32_zero = 0, s32_one = 1, s32_fifty = 50, s32_min = INT_MIN/2, s32_max = INT_MAX/2, s32_hi_a = INT_MAX/2 - 100, s32_hi_b = INT_MAX/2; + const ImU32 u32_zero = 0, u32_one = 1, u32_fifty = 50, u32_min = 0, u32_max = UINT_MAX/2, u32_hi_a = UINT_MAX/2 - 100, u32_hi_b = UINT_MAX/2; + const ImS64 s64_zero = 0, s64_one = 1, s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2, s64_hi_a = LLONG_MAX/2 - 100, s64_hi_b = LLONG_MAX/2; + const ImU64 u64_zero = 0, u64_one = 1, u64_fifty = 50, u64_min = 0, u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2; + const float f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f; + const double f64_zero = 0., f64_one = 1., f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0; + + // State + static ImS32 s32_v = -1; + static ImU32 u32_v = (ImU32)-1; + static ImS64 s64_v = -1; + static ImU64 u64_v = (ImU64)-1; + static float f32_v = 0.123f; + static double f64_v = 90000.01234567890123456789; + + const float drag_speed = 0.2f; + static bool drag_clamp = false; + ImGui::Text("Drags:"); + ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); ImGui::SameLine(); ShowHelpMarker("As with every widgets in dear imgui, we never modify values unless there is a user interaction.\nYou can override the clamping limits by using CTRL+Click to input a value."); + ImGui::DragScalar("drag s32", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL); + ImGui::DragScalar("drag u32", ImGuiDataType_U32, &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s64", ImGuiDataType_S64, &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL); + ImGui::DragScalar("drag u64", ImGuiDataType_U64, &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL); + ImGui::DragScalar("drag float", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", 1.0f); + ImGui::DragScalar("drag float ^2", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", 2.0f); ImGui::SameLine(); ShowHelpMarker("You can use the 'power' parameter to increase tweaking precision on one side of the range."); + ImGui::DragScalar("drag double", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL, "%.10f grams", 1.0f); + ImGui::DragScalar("drag double ^2", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", 2.0f); + + ImGui::Text("Sliders"); + ImGui::SliderScalar("slider s32 low", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty,"%d"); + ImGui::SliderScalar("slider s32 high", ImGuiDataType_S32, &s32_v, &s32_hi_a, &s32_hi_b, "%d"); + ImGui::SliderScalar("slider s32 full", ImGuiDataType_S32, &s32_v, &s32_min, &s32_max, "%d"); + ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty,"%u"); + ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); + ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); + ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%I64d"); + ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%I64d"); + ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%I64d"); + ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%I64u ms"); + ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%I64u ms"); + ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%I64u ms"); + ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); + ImGui::SliderScalar("slider float low^2", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", 2.0f); + ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); + ImGui::SliderScalar("slider double low", ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f grams", 1.0f); + ImGui::SliderScalar("slider double low^2",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", 2.0f); + ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams", 1.0f); + + static bool inputs_step = true; + ImGui::Text("Inputs"); + ImGui::Checkbox("Show step buttons", &inputs_step); + ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); + ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); + ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); + ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); + ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); + ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::InputInt2("input int2", vec4i); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::InputInt3("input int3", vec4i); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx*rows+ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Drag and Drop")) + { + { + // ColorEdit widgets automatically act as drag source and drag target. + // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F to allow your own widgets + // to use colors in their drag and drop interaction. Also see the demo in Color Picker -> Palette demo. + ImGui::BulletText("Drag and drop in standard widgets"); + ImGui::Indent(); + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::ColorEdit4("color 2", col2); + ImGui::Unindent(); + } + + { + ImGui::BulletText("Drag and drop to copy/swap items"); + ImGui::Indent(); + enum Mode + { + Mode_Copy, + Mode_Move, + Mode_Swap + }; + static int mode = 0; + if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine(); + if (ImGui::RadioButton("Move", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine(); + if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { mode = Mode_Swap; } + static const char* names[9] = { "Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn" }; + for (int n = 0; n < IM_ARRAYSIZE(names); n++) + { + ImGui::PushID(n); + if ((n % 3) != 0) + ImGui::SameLine(); + ImGui::Button(names[n], ImVec2(60,60)); + + // Our buttons are both drag sources and drag targets here! + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int)); // Set payload to carry the index of our item (could be anything) + if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); } // Display preview (could be anything, e.g. when dragging an image we could decide to display the filename and a small preview of the image, etc.) + if (mode == Mode_Move) { ImGui::Text("Move %s", names[n]); } + if (mode == Mode_Swap) { ImGui::Text("Swap %s", names[n]); } + ImGui::EndDragDropSource(); + } + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) + { + IM_ASSERT(payload->DataSize == sizeof(int)); + int payload_n = *(const int*)payload->Data; + if (mode == Mode_Copy) + { + names[n] = names[payload_n]; + } + if (mode == Mode_Move) + { + names[n] = names[payload_n]; + names[payload_n] = ""; + } + if (mode == Mode_Swap) + { + const char* tmp = names[n]; + names[n] = names[payload_n]; + names[payload_n] = tmp; + } + } + ImGui::EndDragDropTarget(); + } + ImGui::PopID(); + } + ImGui::Unindent(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Querying Status (Active/Focused/Hovered etc.)")) + { + // Display the value of IsItemHovered() and other common item state functions. Note that the flags can be combined. + // (because BulletText is an item itself and that would affect the output of IsItemHovered() we pass all state in a single call to simplify the code). + static int item_type = 1; + static bool b = false; + static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; + ImGui::RadioButton("Text", &item_type, 0); + ImGui::RadioButton("Button", &item_type, 1); + ImGui::RadioButton("CheckBox", &item_type, 2); + ImGui::RadioButton("SliderFloat", &item_type, 3); + ImGui::RadioButton("ColorEdit4", &item_type, 4); + ImGui::RadioButton("ListBox", &item_type, 5); + ImGui::Separator(); + bool ret = false; + if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction + if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button + if (item_type == 2) { ret = ImGui::Checkbox("ITEM: CheckBox", &b); } // Testing checkbox + if (item_type == 3) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item + if (item_type == 4) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 5) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + ImGui::BulletText( + "Return value = %d\n" + "IsItemFocused() = %d\n" + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemHovered(_RectOnly) = %d\n" + "IsItemActive() = %d\n" + "IsItemEdited() = %d\n" + "IsItemDeactivated() = %d\n" + "IsItemDeactivatedEdit() = %d\n" + "IsItemVisible() = %d\n" + "GetItemRectMin() = (%.1f, %.1f)\n" + "GetItemRectMax() = (%.1f, %.1f)\n" + "GetItemRectSize() = (%.1f, %.1f)", + ret, + ImGui::IsItemFocused(), + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), + ImGui::IsItemActive(), + ImGui::IsItemEdited(), + ImGui::IsItemDeactivated(), + ImGui::IsItemDeactivatedAfterEdit(), + ImGui::IsItemVisible(), + ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y, + ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y, + ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y + ); + + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20), true); + + // Testing IsWindowFocused() function with its various flags. Note that the flags can be combined. + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags. Note that the flags can be combined. + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); + + ImGui::BeginChild("child", ImVec2(0, 50), true); + ImGui::Text("This is another child window for testing with the _ChildWindows flag."); + ImGui::EndChild(); + if (embed_all_inside_a_child_window) + ImGui::EndChild(); + + // Calling IsItemHovered() after begin returns the hovered status of the title bar. + // This is useful in particular if you want to create a context menu (with BeginPopupContextItem) associated to the title bar of a window. + static bool test_window = false; + ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); + if (test_window) + { + ImGui::Begin("Title bar Hovered/Active tests", &test_window); + if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() + { + if (ImGui::MenuItem("Close")) { test_window = false; } + ImGui::EndPopup(); + } + ImGui::Text( + "IsItemHovered() after begin = %d (== is title bar hovered)\n" + "IsItemActive() after begin = %d (== is window being clicked/moved)\n", + ImGui::IsItemHovered(), ImGui::IsItemActive()); + ImGui::End(); + } + + ImGui::TreePop(); + } +} + +static void ShowDemoWindowLayout() +{ + if (!ImGui::CollapsingHeader("Layout")) + return; + + if (ImGui::TreeNode("Child regions")) + { + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + static int line = 50; + bool goto_line = ImGui::Button("Goto"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + + // Child 1: no border, enable horizontal scrollbar + { + ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + for (int i = 0; i < 100; i++) + { + ImGui::Text("%04d: scrollable region", i); + if (goto_line && line == i) + ImGui::SetScrollHereY(); + } + if (goto_line && line >= 100) + ImGui::SetScrollHereY(); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("Child2", ImVec2(0, 300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar)); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Columns(2); + for (int i = 0; i < 100; i++) + { + char buf[32]; + sprintf(buf, "%03d", i); + ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); + ImGui::NextColumn(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + ImGui::Text("PushItemWidth(100)"); + ImGui::SameLine(); ShowHelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of window width."); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::DragFloat("float##2", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); + ImGui::DragFloat("float##3", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-100)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##4", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-1)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge"); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("float##5", &f); + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1 = false, c2 = false, c3 = false, c4 = false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f, 5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f, 5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f, 5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 button_sz(40, 40); + ImGui::Button("A", button_sz); ImGui::SameLine(); + ImGui::Dummy(button_sz); ImGui::SameLine(); + ImGui::Button("B", button_sz); + + // Manually wrapping (we should eventually provide this as an automatic layout feature, but for now you can do it manually) + ImGui::Text("Manually wrapping:"); + ImGuiStyle& style = ImGui::GetStyle(); + int buttons_count = 20; + float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + for (int n = 0; n < buttons_count; n++) + { + ImGui::PushID(n); + ImGui::Button("Box", button_sz); + float last_button_x2 = ImGui::GetItemRectMax().x; + float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line + if (n + 1 < buttons_count && next_button_x2 < window_visible_x2) + ImGui::SameLine(); + ImGui::PopID(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f, size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f, size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + if (ImGui::ListBoxHeader("List", size)) + { + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::ListBoxFooter(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); + + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); + ImGui::Button("Widget##1"); ImGui::SameLine(); + ImGui::Text("Widget"); ImGui::SameLine(); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + ImGui::TextWrapped("(Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given position.)"); + static bool track = true; + static int track_line = 50, scroll_to_px = 200; + ImGui::Checkbox("Track", &track); + ImGui::PushItemWidth(100); + ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %d"); + bool scroll_to = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %d px"); + ImGui::PopItemWidth(); + if (scroll_to) track = false; + + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); + ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); + if (scroll_to) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); + for (int line = 0; line < 100; line++) + { + if (track && line == track_line) + { + ImGui::TextColored(ImVec4(1,1,0,1), "Line %d", line); + ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Line %d", line); + } + } + float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); + ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off + // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; + float hue = n*0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::End(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag)"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); + ImGui::InvisibleButton("##dummy", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(90, 90, 120, 255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x + offset.x, pos.y + offset.y), IM_COL32(255, 255, 255, 255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::TreePop(); + } +} + +static void ShowDemoWindowPopups() +{ + if (!ImGui::CollapsingHeader("Popups & Modal windows")) + return; + + // Popups are windows with a few special properties: + // - They block normal mouse hovering detection outside them. (*) + // - Unless modal, they can be closed by clicking anywhere outside them, or by pressing ESCAPE. + // - Their visibility state (~bool) is held internally by imgui instead of being held by the programmer as we are used to with regular Begin() calls. + // (*) One can use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) to bypass it and detect hovering even when normally blocked by a popup. + // Those three properties are intimately connected. The library needs to hold their visibility state because it can close popups at any time. + + // Typical use for regular windows: + // bool my_tool_is_active = false; if (ImGui::Button("Open")) my_tool_is_active = true; [...] if (my_tool_is_active) Begin("My Tool", &my_tool_is_active) { [...] } End(); + // Typical use for popups: + // if (ImGui::Button("Open")) ImGui::OpenPopup("MyPopup"); if (ImGui::BeginPopup("MyPopup") { [...] EndPopup(); } + + // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state. + // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. + + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup + // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("my_select_popup"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("my_select_popup")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("my_toggle_popup"); + if (ImGui::BeginPopup("my_toggle_popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + // Call the more complete ShowExampleMenuFile which we use in various places of this demo + if (ImGui::Button("File Menu..")) + ImGui::OpenPopup("my_file_popup"); + if (ImGui::BeginPopup("my_file_popup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseReleased(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. + static float value = 0.5f; + ImGui::Text("Value = %.3f (<-- right-click here)", value); + if (ImGui::BeginPopupContextItem("item context menu")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } + + // We can also use OpenPopupOnItemClick() which is the same as BeginPopupContextItem() but without the Begin call. + // So here we will make it that clicking on the text field with the right mouse button (1) will toggle the visibility of the popup above. + ImGui::Text("(You can also right-click me to the same popup as above.)"); + ImGui::OpenPopupOnItemClick("item context menu", 1); + + // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). + // BeginPopupContextItem() will use the last item ID as the popup ID. + // In addition here, we want to include your editable label inside the button label. We use the ### operator to override the ID (read FAQ about ID for details) + static char name[32] = "Label1"; + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int dummy_i = 0; + //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1")) + { + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it."); + static int item = 1; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + if (ImGui::BeginPopupModal("Stacked 2")) + { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } +} + +static void ShowDemoWindowColumns() +{ + if (!ImGui::CollapsingHeader("Columns")) + return; + + ImGui::PushID("Columns"); + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-1,0))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f"); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f"); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(4, NULL, v_borders); + for (int i = 0; i < 4*3; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); + ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Scrolling columns + /* + if (ImGui::TreeNode("Vertical Scrolling")) + { + ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); + ImGui::Columns(3); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::EndChild(); + ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); + ImGui::Columns(3); + for (int i = 0; i < 10; i++) + { + ImGui::Text("%04d", i); ImGui::NextColumn(); + ImGui::Text("Foobar"); ImGui::NextColumn(); + ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + */ + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + bool node_open = ImGui::TreeNode("Tree within single cell"); + ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); + if (node_open) + { + ImGui::Columns(2, "tree items"); + ImGui::Separator(); + if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); + if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::PopID(); +} + +static void ShowDemoWindowMisc() +{ + if (ImGui::CollapsingHeader("Filtering")) + { + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos); + ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); + + if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } + ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } + ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "dummy"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::PopAllowKeyboardFocus(); + + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item + static float f3[3] = { 0.0f, 0.0f, 0.0f }; + int focus_ahead = -1; + if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine(); + if (ImGui::Button("Focus on Z")) focus_ahead = 2; + if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); + ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", + button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + { + // Draw a line between the button and the mouse cursor + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); + draw_list->PopClipRect(); + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) + // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Style Editor / ShowStyleEditor() +//----------------------------------------------------------------------------- + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsClassic(); break; + case 1: ImGui::StyleColorsDark(); break; + case 2: ImGui::StyleColorsLight(); break; + } + return true; + } + return false; +} + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) + io.FontDefault = io.Fonts->Fonts[n]; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and documentation in misc/fonts/ for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); + + if (ImGui::TreeNode("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, "%.2f", 2.0f); + if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("BorderSize"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::Text("Safe Area Padding"); ImGui::SameLine(); ShowHelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export Unsaved")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + + static ImGuiColorEditFlags alpha_flags = 0; + ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); + ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); + ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. + // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::TreePop(); + } + + bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); + if (fonts_opened) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::TreePop(); + } + ImGui::PushItemWidth(100); + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; + if (font_details_opened) + { + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); + ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, "%.0f"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + if (ImFontConfig* cfg = &font->ConfigData[config_i]) + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + // Display all glyphs of the fonts in separate pages of 256 characters + for (int base = 0; base < 0x10000; base += 256) + { + int count = 0; + for (int n = 0; n < 256; n++) + count += font->FindGlyphNoFallback((ImWchar)(base + n)) ? 1 : 0; + if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) + { + float cell_size = font->FontSize * 1; + float cell_spacing = style.ItemSpacing.y; + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 256; n++) + { + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base+n)); + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); + if (glyph) + font->RenderChar(draw_list, cell_size, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base+n); + ImGui::Separator(); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + static float window_scale = 1.0f; + ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::PopItemWidth(); + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() +//----------------------------------------------------------------------------- + +// Demonstrate creating a fullscreen menu bar and populating it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + static bool b = true; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::Checkbox("Check", &b); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +//----------------------------------------------------------------------------- + +// Demonstrate creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); // "classify" is only here to provide an example of "C"+[tab] completing to "CL" and displaying matches. + AddLog("Welcome to Dear ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close Console")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator(); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However, note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!filter.PassFilter(item)) + continue; + ImVec4 col = col_default_text; + if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + if (ScrollToBottom) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* s = InputBuf; + Strtrim(s); + if (s[0]) + ExecCommand(s); + strcpy(s, ""); + reclaim_focus = true; + } + + // Auto-focus on window apparition + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size-1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiInputTextCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, history_str); + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Debug Log / ShowExampleAppLog() +//----------------------------------------------------------------------------- + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { Buf.clear(); LineOffsets.clear(); } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open = NULL) + { + ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) + { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) + { + const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } + else + { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // Demo: add random items (unless Ctrl is held) + static double last_time = -1.0; + double time = ImGui::GetTime(); + if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) + { + const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); + last_time = time; + } + + log.Draw("Example: Log", p_open); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() +//----------------------------------------------------------------------------- + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // left + static int selected = 0; + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + ImGui::SameLine(); + + // right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +//----------------------------------------------------------------------------- + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); + ImGui::Columns(2); + ImGui::Separator(); + + struct funcs + { + static void ShowDummyObject(const char* prefix, int uid) + { + ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::NextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("my sailor is rich"); + ImGui::NextColumn(); + if (node_open) + { + static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowDummyObject("Child", 424242); + } + else + { + // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) + ImGui::AlignTextToFramePadding(); + ImGui::TreeNodeEx("Field", ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet, "Field_%d", i); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + if (i >= 5) + ImGui::InputFloat("##value", &dummy_members[i], 1.0f); + else + ImGui::DragFloat("##value", &dummy_members[i], 0.01f); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + }; + + // Iterate dummy objects with dummy members (all the same data) + for (int obj_i = 0; obj_i < 3; obj_i++) + funcs::ShowDummyObject("Object", obj_i); + + ImGui::Columns(1); + ImGui::Separator(); + ImGui::PopStyleVar(); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Long Text / ShowExampleAppLongText() +//----------------------------------------------------------------------------- + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGuiListClipper clipper(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() +//----------------------------------------------------------------------------- + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() +//----------------------------------------------------------------------------- + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::PushItemWidth(200); + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::PopItemWidth(); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() +//----------------------------------------------------------------------------- + +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppSimpleOverlay(bool* p_open) +{ + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + if (corner != -1) + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background + if (ImGui::Begin("Example: Simple Overlay", p_open, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) + { + ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); + ImGui::Separator(); + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + else + ImGui::Text("Mouse Position: "); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (p_open && ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndPopup(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() +//----------------------------------------------------------------------------- + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to all regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) +{ + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(100, 200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(100, 300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +//----------------------------------------------------------------------------- + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(350, 560), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. + // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. + // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) + // In this example we are not using the maths operators! + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Primitives + ImGui::Text("Primitives"); + static float sz = 36.0f; + static float thickness = 4.0f; + static ImVec4 col = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::ColorEdit3("Color", &col.x); + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col32 = ImColor(col); + float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; + for (int n = 0; n < 2; n++) + { + float curr_thickness = (n == 0) ? 1.0f : thickness; + draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, curr_thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, curr_thickness); x += sz+spacing; + draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, curr_thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, curr_thickness); x += sz+spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, curr_thickness); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, curr_thickness); x += sz+spacing; // Diagonal line + draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, curr_thickness); + x = p.x + 4; + y += sz+spacing; + } + draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+thickness), col32); x += sz+spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+thickness, y+sz), col32); x += spacing+spacing; // Vertical line (faster than AddLine, but only handle integer thickness) + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+1, y+1), col32); x += sz; // Pixel (faster than AddLine) + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); + ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); + } + ImGui::Separator(); + { + static ImVector points; + static bool adding_line = false; + ImGui::Text("Canvas example"); + if (ImGui::Button("Clear")) points.clear(); + if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } + ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); + + // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() + // But you can also draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). + // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; + if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; + draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50, 50, 50, 255), IM_COL32(50, 50, 60, 255), IM_COL32(60, 60, 70, 255), IM_COL32(50, 50, 60, 255)); + draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255, 255, 255, 255)); + + bool adding_preview = false; + ImGui::InvisibleButton("canvas", canvas_size); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + if (adding_line) + { + adding_preview = true; + points.push_back(mouse_pos_in_canvas); + if (!ImGui::IsMouseDown(0)) + adding_line = adding_preview = false; + } + if (ImGui::IsItemHovered()) + { + if (!adding_line && ImGui::IsMouseClicked(0)) + { + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (ImGui::IsMouseClicked(1) && !points.empty()) + { + adding_line = adding_preview = false; + points.pop_back(); + points.pop_back(); + } + } + draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) + for (int i = 0; i < points.Size - 1; i += 2) + draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i + 1].x, canvas_pos.y + points[i + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); + draw_list->PopClipRect(); + if (adding_preview) + points.pop_back(); + } + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif diff --git a/src/imgui/imgui_draw.cpp b/src/imgui/imgui_draw.cpp new file mode 100644 index 000000000..553fb16ee --- /dev/null +++ b/src/imgui/imgui_draw.cpp @@ -0,0 +1,3156 @@ +// dear imgui, v1.66 WIP +// (drawing and font code) + +/* + +Index of this file: + +// [SECTION] STB libraries implementation +// [SECTION] Style functions +// [SECTION] ImDrawList +// [SECTION] ImDrawData +// [SECTION] Helpers ShadeVertsXXX functions +// [SECTION] ImFontConfig +// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder +// [SECTION] ImFont +// [SECTION] Internal Render Helpers +// [SECTION] Decompression code +// [SECTION] Default font data (ProggyClean.ttf) + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#if defined(__GLIBC__) || defined(__sun) || defined(__CYGWIN__) +#include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) +#elif defined(_WIN32) +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#else +#include // alloca +#endif +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang/GCC warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // +#endif +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif +#endif + +//------------------------------------------------------------------------- +// [SECTION] STB libraries implementation +//------------------------------------------------------------------------- + +// Compile time options: +//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wcast-qual" // warning : cast from 'const xxxx *' to 'xxx *' drops const qualifier // +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#endif + +#ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STBRP_ASSERT(x) IM_ASSERT(x) +#define STBRP_SORT ImQsort +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#ifdef IMGUI_STB_RECT_PACK_FILENAME +#include IMGUI_STB_RECT_PACK_FILENAME +#else +#include "imstb_rectpack.h" +#endif +#endif + +#ifndef STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#define STBTT_assert(x) IM_ASSERT(x) +#define STBTT_fmod(x,y) ImFmod(x,y) +#define STBTT_sqrt(x) ImSqrt(x) +#define STBTT_pow(x,y) ImPow(x,y) +#define STBTT_fabs(x) ImFabs(x) +#define STBTT_ifloor(x) ((int)ImFloorStd(x)) +#define STBTT_iceil(x) ((int)ImCeil(x)) +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#ifdef IMGUI_STB_TRUETYPE_FILENAME +#include IMGUI_STB_TRUETYPE_FILENAME +#else +#include "imstb_truetype.h" +#endif +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImGuiStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); +} + +//----------------------------------------------------------------------------- +// ImDrawList +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + Font = NULL; + FontSize = 0.0f; + CurveTessellationTol = 0.0f; + ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); + + // Const data + for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); + CircleVtx12[i] = ImVec2(ImCos(a), ImSin(a)); + } +} + +void ImDrawList::Clear() +{ + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + // NB: Do not clear channels so our allocations are re-used after the first frame. +} + +void ImDrawList::ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + for (int i = 0; i < _Channels.Size; i++) + { + if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i].CmdBuffer.clear(); + _Channels[i].IdxBuffer.clear(); + } + _Channels.clear(); +} + +ImDrawList* ImDrawList::CloneOutput() const +{ + ImDrawList* dst = IM_NEW(ImDrawList(NULL)); + dst->CmdBuffer = CmdBuffer; + dst->IdxBuffer = IdxBuffer; + dst->VtxBuffer = VtxBuffer; + dst->Flags = Flags; + return dst; +} + +// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds +#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) +#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = GetCurrentClipRect(); + draw_cmd.TextureId = GetCurrentTextureId(); + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) + { + AddDrawCmd(); + current_cmd = &CmdBuffer.back(); + } + current_cmd->UserCallback = callback; + current_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::UpdateClipRect() +{ + // If current command is used with different settings we need to add a new command + const ImVec4 curr_clip_rect = GetCurrentClipRect(); + ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->ClipRect = curr_clip_rect; +} + +void ImDrawList::UpdateTextureID() +{ + // If current command is used with different settings we need to add a new command + const ImTextureID curr_texture_id = GetCurrentTextureId(); + ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->TextureId = curr_texture_id; +} + +#undef GetCurrentClipRect +#undef GetCurrentTextureId + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect && _ClipRectStack.Size) + { + ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + UpdateClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + IM_ASSERT(_ClipRectStack.Size > 0); + _ClipRectStack.pop_back(); + UpdateClipRect(); +} + +void ImDrawList::PushTextureID(ImTextureID texture_id) +{ + _TextureIdStack.push_back(texture_id); + UpdateTextureID(); +} + +void ImDrawList::PopTextureID() +{ + IM_ASSERT(_TextureIdStack.Size > 0); + _TextureIdStack.pop_back(); + UpdateTextureID(); +} + +void ImDrawList::ChannelsSplit(int channels_count) +{ + IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + _Channels.resize(channels_count); + _ChannelsCount = channels_count; + + // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer + // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i].CmdBuffer.resize(0); + _Channels[i].IdxBuffer.resize(0); + } + if (_Channels[i].CmdBuffer.Size == 0) + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _ClipRectStack.back(); + draw_cmd.TextureId = _TextureIdStack.back(); + _Channels[i].CmdBuffer.push_back(draw_cmd); + } + } +} + +void ImDrawList::ChannelsMerge() +{ + // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_ChannelsCount <= 1) + return; + + ChannelsSetCurrent(0); + if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) + CmdBuffer.pop_back(); + + int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) + ch.CmdBuffer.pop_back(); + new_cmd_buffer_count += ch.CmdBuffer.Size; + new_idx_buffer_count += ch.IdxBuffer.Size; + } + CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); + IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); + + ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } + } + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. + _ChannelsCount = 1; +} + +void ImDrawList::ChannelsSetCurrent(int idx) +{ + IM_ASSERT(idx < _ChannelsCount); + if (_ChannelsCurrent == idx) return; + memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times + memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); + _ChannelsCurrent = idx; + memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); + memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; +} + +// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; + draw_cmd.ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +{ + if (points_count < 2) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + int count = points_count; + if (!closed) + count = points_count-1; + + const bool thick_line = thickness > 1.0f; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const int idx_count = thick_line ? count*18 : count*12; + const int vtx_count = thick_line ? points_count*4 : points_count*3; + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); + ImVec2* temp_points = temp_normals + points_count; + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + ImVec2 diff = points[i2] - points[i1]; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i1].x = diff.y; + temp_normals[i1].y = -diff.x; + } + if (!closed) + temp_normals[points_count-1] = temp_normals[points_count-2]; + + if (!thick_line) + { + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; + temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE; + temp_points[i2*2+0] = points[i2] + dm; + temp_points[i2*2+1] = points[i2] - dm; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); + _IdxWritePtr += 12; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; + _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; + _VtxWritePtr += 3; + } + } + else + { + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); + ImVec2 dm_in = dm * half_inner_thickness; + temp_points[i2*4+0] = points[i2] + dm_out; + temp_points[i2*4+1] = points[i2] + dm_in; + temp_points[i2*4+2] = points[i2] - dm_in; + temp_points[i2*4+3] = points[i2] - dm_out; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Stroke + const int idx_count = count*6; + const int vtx_count = count*4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + ImVec2 diff = p2 - p1; + diff *= ImInvLength(diff, 1.0f); + + const float dx = diff.x * (thickness * 0.5f); + const float dy = diff.y * (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + if (points_count < 3) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count-2)*3 + points_count*6; + const int vtx_count = (points_count*2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx+1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + ImVec2 diff = p1 - p0; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i0].x = diff.y; + temp_normals[i0].y = -diff.x; + } + + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + ImVec2 dm = (n0 + n1) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count-2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius == 0.0f || a_min_of_12 > a_max_of_12) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) + { + const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); + } +} + +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) +{ + if (radius == 0.0f) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius)); + } +} + +static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; + float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; + float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; + float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; + float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; + float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; + + PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); + PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); + } +} + +void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + // Auto-tessellated + PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + float t = t_step * i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t*t*t; + _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); + } + } +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) +{ + rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || rounding_corners == 0) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(a + ImVec2(0.5f,0.5f)); + PathLineTo(b + ImVec2(0.5f,0.5f)); + PathStroke(col, false, thickness); +} + +// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (Flags & ImDrawListFlags_AntiAliasedLines) + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.50f,0.50f), rounding, rounding_corners_flags); + else + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.49f,0.49f), rounding, rounding_corners_flags); // Better looking lower-right corner and rounded non-AA shapes. + PathStroke(col, true, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding > 0.0f) + { + PathRect(a, b, rounding, rounding_corners_flags); + PathFillConvex(col); + } + else + { + PrimReserve(6, 4); + PrimRect(a, b, col); + } +} + +void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); + PrimWriteVtx(a, uv, col_upr_left); + PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); + PrimWriteVtx(c, uv, col_bot_right); + PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius, 0.0f, a_max, num_segments); + PathFillConvex(col); +} + +void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(pos0); + PathBezierCurveTo(cp0, cp1, pos1, num_segments); + PathStroke(col, false, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _ClipRectStack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(a, b, uv_a, uv_b, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + { + AddImage(user_texture_id, a, b, uv_a, uv_b, col); + return; + } + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(a, b, rounding, rounding_corners); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, a, b, uv_a, uv_b, true); + + if (push_texture_id) + PopTextureID(); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Helpers ShadeVertsXXX functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; + ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; + ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + FontData = NULL; + FontDataSize = 0; + FontDataOwnedByAtlas = true; + FontNo = 0; + SizePixels = 0.0f; + OversampleH = 3; + OversampleV = 1; + PixelSnapH = false; + GlyphExtraSpacing = ImVec2(0.0f, 0.0f); + GlyphOffset = ImVec2(0.0f, 0.0f); + GlyphRanges = NULL; + GlyphMinAdvanceX = 0.0f; + GlyphMaxAdvanceX = FLT_MAX; + MergeMode = false; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; + memset(Name, 0, sizeof(Name)); + DstFont = NULL; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 108; +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX- XX " + "..- -X.....X- X.X - X.X -X.....X - X.....X- X..X " + "--- -XXX.XXX- X...X - X...X -X....X - X....X- X..X " + "X - X.X - X.....X - X.....X -X...X - X...X- X..X " + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X- X..X " + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X- X..XXX " + "X..X - X.X - X.X - X.X -XX X.X - X.X XX- X..X..XXX " + "X...X - X.X - X.X - XX X.X XX - X.X - X.X - X..X..X..XX " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X - X..X..X..X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X -XXX X..X..X..X..X" + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X -X..XX........X..X" + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X -X...X...........X" + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X - X..............X" + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X - X.............X" + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X - X.............X" + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X - X............X" + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX - X...........X " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------- X..........X " + "X.X X..X - -X.......X- X.......X - XX XX - - X..........X " + "XX X..X - - X.....X - X.....X - X.X X.X - - X........X " + " X..X - X...X - X...X - X..X X..X - - X........X " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - - XXXXXXXXXX " + "------------ - X - X -X.....................X- ------------------" + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2( 0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2( 7,16), ImVec2( 1, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 4,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 4) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE + { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand +}; + +ImFontAtlas::ImFontAtlas() +{ + Locked = false; + Flags = ImFontAtlasFlags_None; + TexID = (ImTextureID)NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; + + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexWidth = TexHeight = 0; + TexUvScale = ImVec2(0.0f, 0.0f); + TexUvWhitePixel = ImVec2(0.0f, 0.0f); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + ImGui::MemFree(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +void ImFontAtlas::ClearTexData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels = NULL; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + if (pixels) + { + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (!new_font_cfg.DstFont) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(const unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, const unsigned char *input, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); + if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); + font->DisplayOffset.y = 1.0f; + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + size_t data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT(0); // Could not load file. + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + ImGui::MemFree(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) +{ + IM_ASSERT(id >= 0x10000); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); +} + +bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +{ + if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT) + return false; + if (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + IM_ASSERT(CustomRectIds[0] != -1); + ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y); + ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; + *out_size = size; + *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; + out_uv_border[0] = (pos) * TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + out_uv_fill[0] = (pos) * TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + return ImFontAtlasBuildWithStbTruetype(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildRegisterDefaultCustomRects(atlas); + + atlas->TexID = (ImTextureID)NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvScale = ImVec2(0.0f, 0.0f); + atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); + atlas->ClearTexData(); + + // Count glyphs/ranges + int total_glyphs_count = 0; + int total_ranges_count = 0; + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + if (!cfg.GlyphRanges) + cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) + total_glyphs_count += (in_range[1] - in_range[0]) + 1; + } + + // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. + // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; + atlas->TexHeight = 0; + + // Start packing + const int max_tex_height = 1024*32; + stbtt_pack_context spc = {}; + if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL)) + return false; + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // Initialize font information (so we can error without any cleanup) + struct ImFontTempBuildData + { + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + int RectsCount; + stbtt_pack_range* Ranges; + int RangesCount; + }; + ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); + if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure + ImGui::MemFree(tmp_array); + return false; + } + } + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); + memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. + memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); + + // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + + // Setup ranges + int font_glyphs_count = 0; + int font_ranges_count = 0; + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) + font_glyphs_count += (in_range[1] - in_range[0]) + 1; + tmp.Ranges = buf_ranges + buf_ranges_n; + tmp.RangesCount = font_ranges_count; + buf_ranges_n += font_ranges_count; + for (int i = 0; i < font_ranges_count; i++) + { + const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; + stbtt_pack_range& range = tmp.Ranges[i]; + range.font_size = cfg.SizePixels; + range.first_unicode_codepoint_in_range = in_range[0]; + range.num_chars = (in_range[1] - in_range[0]) + 1; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars; + } + + // Gather the sizes of all rectangle we need + tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; + buf_rects_n += font_glyphs_count; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + IM_ASSERT(n == font_glyphs_count); + + // Detect missing glyphs and replace them with a zero-sized box instead of relying on the default glyphs + // This allows us merging overlapping icon fonts more easily. + int rect_i = 0; + for (int range_i = 0; range_i < tmp.RangesCount; range_i++) + for (int char_i = 0; char_i < tmp.Ranges[range_i].num_chars; char_i++, rect_i++) + if (stbtt_FindGlyphIndex(&tmp.FontInfo, tmp.Ranges[range_i].first_unicode_codepoint_in_range + char_i) == 0) + tmp.Rects[rect_i].w = tmp.Rects[rect_i].h = 0; + + // Pack + stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); + + // Extend texture height + // Also mark missing glyphs as non-packed so we don't attempt to render into them + for (int i = 0; i < n; i++) + { + if (tmp.Rects[i].w == 0 && tmp.Rects[i].h == 0) + tmp.Rects[i].was_packed = 0; + if (tmp.Rects[i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); + } + } + IM_ASSERT(buf_rects_n == total_glyphs_count); + IM_ASSERT(buf_packedchars_n == total_glyphs_count); + IM_ASSERT(buf_ranges_n == total_ranges_count); + + // Create texture + atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // Second pass: render font characters + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } + tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + ImGui::MemFree(buf_rects); + buf_rects = NULL; + + // Third pass: setup ImFont and glyphs for runtime + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) + if (cfg.MergeMode) + dst_font->BuildLookupTable(); + + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); + const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float font_off_x = cfg.GlyphOffset.x; + const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + + for (int i = 0; i < tmp.RangesCount; i++) + { + stbtt_pack_range& range = tmp.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) + { + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + const int codepoint = range.first_unicode_codepoint_in_range + char_idx; + if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint)) + continue; + + float char_advance_x_org = pc.xadvance; + float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX); + float char_off_x = font_off_x; + if (char_advance_x_org != char_advance_x_mod) + char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f; + + stbtt_aligned_quad q; + float dummy_x = 0.0f, dummy_y = 0.0f; + stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod); + } + } + } + + // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + ImGui::MemFree(tmp_array); + + ImFontAtlasBuildFinish(atlas); + + return true; +} + +void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) +{ + if (atlas->CustomRectIds[0] >= 0) + return; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + else + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2); +} + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + IM_ASSERT(r.IsPacked()); + + const int w = atlas->TexWidth; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + { + // Render/copy pixels + IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) + for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) + { + const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w; + const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; + atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; + } + } + else + { + IM_ASSERT(r.Width == 2 && r.Height == 2); + const int offset = (int)(r.X) + (int)(r.Y) * w; + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y); +} + +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + if (atlas->Fonts[i]->DirtyLookupTables) + atlas->Fonts[i]->BuildLookupTable(); +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD79D, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges) +{ + for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2) + { + out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]); + base_codepoint += accumulative_offsets[n]; + } + out_ranges[0] = 0; +} + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder +//------------------------------------------------------------------------- + +const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() +{ + // Store 2500 regularly used characters for Simplified Chinese. + // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8 + // This table covers 97.97% of all characters used during the month in July, 1987. + // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = + { + 0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2, + 1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4, + 2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1, + 1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2, + 3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6, + 1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1, + 1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3, + 2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4, + 27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12, + 3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1, + 1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23, + 176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6, + 5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6, + 1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1, + 6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5, + 2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15, + 2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6, + 2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4, + 3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5, + 3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2, + 3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16, + 1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31, + 140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7, + 5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2, + 2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13, + 4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3, + 2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4, + 4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1, + 3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3, + 3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11, + 2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9, + 5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2, + 3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3, + 1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12, + 4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8, + 4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5, + 26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1, + 3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5, + 2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6, + 10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6 + }; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; + if (!full_ranges[0]) + { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // 1946 common ideograms code points for Japanese + // Sourced from http://theinstructionlimit.com/common-kanji-character-ranges-for-xna-spritefont-rendering + // FIXME: Source a list of the revised 2136 Joyo Kanji list from 2010 and rebuild this. + // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = + { + 0,1,2,4,1,1,1,1,2,1,6,2,2,1,8,5,7,11,1,2,10,10,8,2,4,20,2,11,8,2,1,2,1,6,2,1,7,5,3,7,1,1,13,7,9,1,4,6,1,2,1,10,1,1,9,2,2,4,5,6,14,1,1,9,3,18, + 5,4,2,2,10,7,1,1,1,3,2,4,3,23,2,10,12,2,14,2,4,13,1,6,10,3,1,7,13,6,4,13,5,2,3,17,2,2,5,7,6,4,1,7,14,16,6,13,9,15,1,1,7,16,4,7,1,19,9,2,7,15, + 2,6,5,13,25,4,14,13,11,25,1,1,1,2,1,2,2,3,10,11,3,3,1,1,4,4,2,1,4,9,1,4,3,5,5,2,7,12,11,15,7,16,4,5,16,2,1,1,6,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1, + 2,1,12,3,3,9,5,8,1,11,1,2,3,18,20,4,1,3,6,1,7,3,5,5,7,2,2,12,3,1,4,2,3,2,3,11,8,7,4,17,1,9,25,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,6,16,1,2,1,1,3,12, + 20,2,5,20,8,7,6,2,1,1,1,1,6,2,1,2,10,1,1,6,1,3,1,2,1,4,1,12,4,1,3,1,1,1,1,1,10,4,7,5,13,1,15,1,1,30,11,9,1,15,38,14,1,32,17,20,1,9,31,2,21,9, + 4,49,22,2,1,13,1,11,45,35,43,55,12,19,83,1,3,2,3,13,2,1,7,3,18,3,13,8,1,8,18,5,3,7,25,24,9,24,40,3,17,24,2,1,6,2,3,16,15,6,7,3,12,1,9,7,3,3, + 3,15,21,5,16,4,5,12,11,11,3,6,3,2,31,3,2,1,1,23,6,6,1,4,2,6,5,2,1,1,3,3,22,2,6,2,3,17,3,2,4,5,1,9,5,1,1,6,15,12,3,17,2,14,2,8,1,23,16,4,2,23, + 8,15,23,20,12,25,19,47,11,21,65,46,4,3,1,5,6,1,2,5,26,2,1,1,3,11,1,1,1,2,1,2,3,1,1,10,2,3,1,1,1,3,6,3,2,2,6,6,9,2,2,2,6,2,5,10,2,4,1,2,1,2,2, + 3,1,1,3,1,2,9,23,9,2,1,1,1,1,5,3,2,1,10,9,6,1,10,2,31,25,3,7,5,40,1,15,6,17,7,27,180,1,3,2,2,1,1,1,6,3,10,7,1,3,6,17,8,6,2,2,1,3,5,5,8,16,14, + 15,1,1,4,1,2,1,1,1,3,2,7,5,6,2,5,10,1,4,2,9,1,1,11,6,1,44,1,3,7,9,5,1,3,1,1,10,7,1,10,4,2,7,21,15,7,2,5,1,8,3,4,1,3,1,6,1,4,2,1,4,10,8,1,4,5, + 1,5,10,2,7,1,10,1,1,3,4,11,10,29,4,7,3,5,2,3,33,5,2,19,3,1,4,2,6,31,11,1,3,3,3,1,8,10,9,12,11,12,8,3,14,8,6,11,1,4,41,3,1,2,7,13,1,5,6,2,6,12, + 12,22,5,9,4,8,9,9,34,6,24,1,1,20,9,9,3,4,1,7,2,2,2,6,2,28,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,8,8,3,2,1,5,1,2,2,3,1,11,11,7,3,6,10,8,6,16,16, + 22,7,12,6,21,5,4,6,6,3,6,1,3,2,1,2,8,29,1,10,1,6,13,6,6,19,31,1,13,4,4,22,17,26,33,10,4,15,12,25,6,67,10,2,3,1,6,10,2,6,2,9,1,9,4,4,1,2,16,2, + 5,9,2,3,8,1,8,3,9,4,8,6,4,8,11,3,2,1,1,3,26,1,7,5,1,11,1,5,3,5,2,13,6,39,5,1,5,2,11,6,10,5,1,15,5,3,6,19,21,22,2,4,1,6,1,8,1,4,8,2,4,2,2,9,2, + 1,1,1,4,3,6,3,12,7,1,14,2,4,10,2,13,1,17,7,3,2,1,3,2,13,7,14,12,3,1,29,2,8,9,15,14,9,14,1,3,1,6,5,9,11,3,38,43,20,7,7,8,5,15,12,19,15,81,8,7, + 1,5,73,13,37,28,8,8,1,15,18,20,165,28,1,6,11,8,4,14,7,15,1,3,3,6,4,1,7,14,1,1,11,30,1,5,1,4,14,1,4,2,7,52,2,6,29,3,1,9,1,21,3,5,1,26,3,11,14, + 11,1,17,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,7,7,5,17,3,3,3,1,23,10,4,4,6,3,1,16,17,22,3,10,21,16,16,6,4,10,2,1,1,2,8,8,6,5,3,3,3,39,25, + 15,1,1,16,6,7,25,15,6,6,12,1,22,13,1,4,9,5,12,2,9,1,12,28,8,3,5,10,22,60,1,2,40,4,61,63,4,1,13,12,1,4,31,12,1,14,89,5,16,6,29,14,2,5,49,18,18, + 5,29,33,47,1,17,1,19,12,2,9,7,39,12,3,7,12,39,3,1,46,4,12,3,8,9,5,31,15,18,3,2,2,66,19,13,17,5,3,46,124,13,57,34,2,5,4,5,8,1,1,1,4,3,1,17,5, + 3,5,3,1,8,5,6,3,27,3,26,7,12,7,2,17,3,7,18,78,16,4,36,1,2,1,6,2,1,39,17,7,4,13,4,4,4,1,10,4,2,4,6,3,10,1,19,1,26,2,4,33,2,73,47,7,3,8,2,4,15, + 18,1,29,2,41,14,1,21,16,41,7,39,25,13,44,2,2,10,1,13,7,1,7,3,5,20,4,8,2,49,1,10,6,1,6,7,10,7,11,16,3,12,20,4,10,3,1,2,11,2,28,9,2,4,7,2,15,1, + 27,1,28,17,4,5,10,7,3,24,10,11,6,26,3,2,7,2,2,49,16,10,16,15,4,5,27,61,30,14,38,22,2,7,5,1,3,12,23,24,17,17,3,3,2,4,1,6,2,7,5,1,1,5,1,1,9,4, + 1,3,6,1,8,2,8,4,14,3,5,11,4,1,3,32,1,19,4,1,13,11,5,2,1,8,6,8,1,6,5,13,3,23,11,5,3,16,3,9,10,1,24,3,198,52,4,2,2,5,14,5,4,22,5,20,4,11,6,41, + 1,5,2,2,11,5,2,28,35,8,22,3,18,3,10,7,5,3,4,1,5,3,8,9,3,6,2,16,22,4,5,5,3,3,18,23,2,6,23,5,27,8,1,33,2,12,43,16,5,2,3,6,1,20,4,2,9,7,1,11,2, + 10,3,14,31,9,3,25,18,20,2,5,5,26,14,1,11,17,12,40,19,9,6,31,83,2,7,9,19,78,12,14,21,76,12,113,79,34,4,1,1,61,18,85,10,2,2,13,31,11,50,6,33,159, + 179,6,6,7,4,4,2,4,2,5,8,7,20,32,22,1,3,10,6,7,28,5,10,9,2,77,19,13,2,5,1,4,4,7,4,13,3,9,31,17,3,26,2,6,6,5,4,1,7,11,3,4,2,1,6,2,20,4,1,9,2,6, + 3,7,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,5,13,8,4,11,23,1,10,6,2,1,3,21,2,2,4,24,31,4,10,10,2,5,192,15,4,16,7,9,51,1,2,1,1,5,1,1,2,1,3,5,3,1,3,4,1, + 3,1,3,3,9,8,1,2,2,2,4,4,18,12,92,2,10,4,3,14,5,25,16,42,4,14,4,2,21,5,126,30,31,2,1,5,13,3,22,5,6,6,20,12,1,14,12,87,3,19,1,8,2,9,9,3,3,23,2, + 3,7,6,3,1,2,3,9,1,3,1,6,3,2,1,3,11,3,1,6,10,3,2,3,1,2,1,5,1,1,11,3,6,4,1,7,2,1,2,5,5,34,4,14,18,4,19,7,5,8,2,6,79,1,5,2,14,8,2,9,2,1,36,28,16, + 4,1,1,1,2,12,6,42,39,16,23,7,15,15,3,2,12,7,21,64,6,9,28,8,12,3,3,41,59,24,51,55,57,294,9,9,2,6,2,15,1,2,13,38,90,9,9,9,3,11,7,1,1,1,5,6,3,2, + 1,2,2,3,8,1,4,4,1,5,7,1,4,3,20,4,9,1,1,1,5,5,17,1,5,2,6,2,4,1,4,5,7,3,18,11,11,32,7,5,4,7,11,127,8,4,3,3,1,10,1,1,6,21,14,1,16,1,7,1,3,6,9,65, + 51,4,3,13,3,10,1,1,12,9,21,110,3,19,24,1,1,10,62,4,1,29,42,78,28,20,18,82,6,3,15,6,84,58,253,15,155,264,15,21,9,14,7,58,40,39, + }; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; + if (!full_ranges[0]) + { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + if (c < 0x10000) + AddChar((ImWchar)c); + } +} + +void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + for (int n = 0; n < 0x10000; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < 0x10000 && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + DisplayOffset = ImVec2(0.0f, 0.0f); + ClearOutputData(); +} + +ImFont::~ImFont() +{ + // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. + // If you want to delete fonts you need to do it between Render() and NewFrame(). + // FIXME-CLEANUP + /* + ImGuiContext& g = *GImGui; + if (g.Font == this) + g.Font = NULL; + */ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + FallbackAdvanceX = 0.0f; + ConfigDataCount = 0; + ConfigData = NULL; + ContainerAtlas = NULL; + Ascent = Descent = 0.0f; + DirtyLookupTables = true; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + DirtyLookupTables = false; + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (ImWchar)i; + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((ImWchar)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((ImWchar)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (ImWchar)(Glyphs.Size-1); + } + + FallbackGlyph = FindGlyphNoFallback(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (ImWchar)-1); +} + +// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. +// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + DirtyLookupTables = true; + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + int index_size = IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (ImWchar)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImWchar)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c >= IndexLookup.Size) + return FallbackGlyph; + const ImWchar i = IndexLookup[c]; + if (i == (ImWchar)-1) + return FallbackGlyph; + return &Glyphs.Data[i]; +} + +const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const +{ + if (c >= IndexLookup.Size) + return NULL; + const ImWchar i = IndexLookup[c]; + if (i == (ImWchar)-1) + return NULL; + return &Glyphs.Data[i]; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); + if (ImCharIsBlankW(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width >= wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. + return; + if (const ImFontGlyph* glyph = FindGlyph(c)) + { + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + } +} + +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Fast-forward to first visible line + const char* s = text_begin; + if (y + line_height < clip_rect.y && !word_wrap_enabled) + while (y + line_height < clip_rect.y && s < text_end) + { + s = (const char*)memchr(s, '\n', text_end - s); + s = s ? s + 1 : text_end; + y += line_height; + } + + // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve() + // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm) + if (text_end - s > 10000 && !word_wrap_enabled) + { + const char* s_end = s; + float y_end = y; + while (y_end < clip_rect.w && s_end < text_end) + { + s_end = (const char*)memchr(s_end, '\n', text_end - s_end); + s = s ? s + 1 : text_end; + y_end += line_height; + } + text_end = s_end; + } + if (s == text_end) + return; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + if (y > clip_rect.w) + break; // break out of main loop + continue; + } + if (c == '\r') + continue; + } + + float char_width = 0.0f; + if (const ImFontGlyph* glyph = FindGlyph((ImWchar)c)) + { + char_width = glyph->AdvanceX * scale; + + // Arbitrarily assume that both space and tabs are empty glyphs as an optimization + if (c != ' ' && c != '\t') + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + } + + x += char_width; + } + + // Give back unused vertices + draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); + draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; +} + +//----------------------------------------------------------------------------- +// [SECTION] Internal Render Helpers +// (progressively moved from imgui.cpp to here when they are redesigned to stop accessing ImGui global state) +//----------------------------------------------------------------------------- +// - RenderMouseCursor() +// - RenderArrowPointingAt() +// - RenderRectFilledRangeH() +//----------------------------------------------------------------------------- + +void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor) +{ + if (mouse_cursor == ImGuiMouseCursor_None) + return; + IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); + + const ImU32 col_shadow = IM_COL32(0, 0, 0, 48); + const ImU32 col_border = IM_COL32(0, 0, 0, 255); // Black + const ImU32 col_fill = IM_COL32(255, 255, 255, 255); // White + + ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; + ImVec2 offset, size, uv[4]; + if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) + { + pos -= offset; + const ImTextureID tex_id = font_atlas->TexID; + draw_list->PushTextureID(tex_id); + draw_list->AddImage(tex_id, pos + ImVec2(1,0)*scale, pos + ImVec2(1,0)*scale + size*scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos + ImVec2(2,0)*scale, pos + ImVec2(2,0)*scale + size*scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos, pos + size*scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_id, pos, pos + size*scale, uv[0], uv[1], col_fill); + draw_list->PopTextureID(); + } +} + +// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings + } +} + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return ImAcos(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + + +//----------------------------------------------------------------------------- +// [SECTION] Decompression code +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array and encoded as base85. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(const unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier_out_e, *stb__barrier_out_b; +static const unsigned char *stb__barrier_in_b; +static unsigned char *stb__dout; +static void stb__match(const unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT(stb__dout + length <= stb__barrier_out_e); + if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } + if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(const unsigned char *data, unsigned int length) +{ + IM_ASSERT(stb__dout + length <= stb__barrier_out_e); + if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } + if (data < stb__barrier_in_b) { stb__dout = stb__barrier_out_e+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static const unsigned char *stb_decompress_token(const unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier_in_b = i; + stb__barrier_out_e = output + olen; + stb__barrier_out_b = output; + i += 16; + + stb__dout = output; + for (;;) { + const unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Default font data (ProggyClean.ttf) +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). +// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} diff --git a/src/imgui/imgui_internal.h b/src/imgui/imgui_internal.h new file mode 100644 index 000000000..bac04ae6a --- /dev/null +++ b/src/imgui/imgui_internal.h @@ -0,0 +1,1297 @@ +// dear imgui, v1.66 WIP +// (internal structures/api) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +#pragma once + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE* +#include // NULL, malloc, free, qsort, atoi, atof +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // INT_MIN, INT_MAX + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +//----------------------------------------------------------------------------- +// Forward Declarations +//----------------------------------------------------------------------------- + +struct ImRect; // An axis-aligned rectangle (2 points) +struct ImDrawDataBuilder; // Helper to build a ImDrawData instance +struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColumnData; // Storage data for a single column +struct ImGuiColumnsSet; // Storage data for a columns set +struct ImGuiContext; // Main imgui context +struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() +struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box +struct ImGuiItemHoveredDataBackup; // Backup and restore IsItemHovered() internal data +struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only +struct ImGuiNavMoveResult; // Result of a directional navigation move query result +struct ImGuiNextWindowData; // Storage for SetNexWindow** functions +struct ImGuiPopupRef; // Storage for current popup stack +struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file +struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it +struct ImGuiWindow; // Storage for one window +struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame) +struct ImGuiWindowSettings; // Storage for window settings stored in .ini file (we keep one of those even if the actual window wasn't instanced during this session) + +// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical +typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for ButtonEx(), ButtonBehavior() +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags +typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() +typedef int ImGuiNavDirSourceFlags; // -> enum ImGuiNavDirSourceFlags_ // Flags: for GetNavInputAmount2d() +typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests +typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // Flags: for Separator() - internal +typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for SliderBehavior() + +//------------------------------------------------------------------------- +// STB libraries +//------------------------------------------------------------------------- + +namespace ImGuiStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiInputTextState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f +#include "imstb_textedit.h" + +} // namespace ImGuiStb + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +#define IM_PI 3.14159265358979323846f +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" // Play it nice with Windows users (2018/05 news: Microsoft announced that Notepad will finally display Unix-style carriage returns!) +#else +#define IM_NEWLINE "\n" +#endif +#define IM_STATIC_ASSERT(_COND) typedef char static_assertion_##__line__[(_COND)?1:-1] +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif + +// Helpers: UTF-8 <> wchar +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 + +// Helpers: Misc +IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); +static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +#define ImQsort qsort + +// Helpers: Geometry +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); +IMGUI_API ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy); + +// Helpers: String +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API void ImStrTrimBlanks(char* str); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); +IMGUI_API const char* ImParseFormatFindStart(const char* format); +IMGUI_API const char* ImParseFormatFindEnd(const char* format); +IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* buf, int buf_size); +IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); + +// Helpers: ImVec2/ImVec4 operators +// We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.) +// We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself. +#ifdef IMGUI_DEFINE_MATH_OPERATORS +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x/rhs, lhs.y/rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } +#endif + +// Helpers: Maths +// - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy) +#ifndef IMGUI_DISABLE_MATH_FUNCTIONS +static inline float ImFabs(float x) { return fabsf(x); } +static inline float ImSqrt(float x) { return sqrtf(x); } +static inline float ImPow(float x, float y) { return powf(x, y); } +static inline double ImPow(double x, double y) { return pow(x, y); } +static inline float ImFmod(float x, float y) { return fmodf(x, y); } +static inline double ImFmod(double x, double y) { return fmod(x, y); } +static inline float ImCos(float x) { return cosf(x); } +static inline float ImSin(float x) { return sinf(x); } +static inline float ImAcos(float x) { return acosf(x); } +static inline float ImAtan2(float y, float x) { return atan2f(y, x); } +static inline double ImAtof(const char* s) { return atof(s); } +static inline float ImFloorStd(float x) { return floorf(x); } // we already uses our own ImFloor() { return (float)(int)v } internally so the standard one wrapper is named differently (it's used by stb_truetype) +static inline float ImCeil(float x) { return ceilf(x); } +#endif +// - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support for variety of types: signed/unsigned int/long long float/double, using templates here but we could also redefine them 6 times +template static inline T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template static inline T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +template static inline T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +template static inline T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template static inline void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } +// - Misc maths helpers +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2& mn, ImVec2 mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / ImSqrt(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)f; } +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +// 1D vector (this odd construct is used to facilitate the transition between 1D and 2D and maintenance of some patches) +struct ImVec1 +{ + float x; + ImVec1() { x = 0.0f; } + ImVec1(float _x) { x = _x; } +}; + +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_None = 0, + ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated +}; + +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_None = 0, + ImGuiSliderFlags_Vertical = 1 << 0 +}; + +enum ImGuiColumnsFlags_ +{ + // Default: 0 + ImGuiColumnsFlags_None = 0, + ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. +}; + +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_NoHoldingActiveID = 1 << 10, + ImGuiSelectableFlags_PressedOnClick = 1 << 11, + ImGuiSelectableFlags_PressedOnRelease = 1 << 12, + ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 13 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_None = 0, + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + +// Storage for LastItem data +enum ImGuiItemStatusFlags_ +{ + ImGuiItemStatusFlags_None = 0, + ImGuiItemStatusFlags_HoveredRect = 1 << 0, + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, + ImGuiItemStatusFlags_Edited = 1 << 2 // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiInputSource +{ + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, + ImGuiInputSource_Nav, + ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code + ImGuiInputSource_NavGamepad, // " + ImGuiInputSource_COUNT +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiInputReadMode +{ + ImGuiInputReadMode_Down, + ImGuiInputReadMode_Pressed, + ImGuiInputReadMode_Released, + ImGuiInputReadMode_Repeat, + ImGuiInputReadMode_RepeatSlow, + ImGuiInputReadMode_RepeatFast +}; + +enum ImGuiNavHighlightFlags_ +{ + ImGuiNavHighlightFlags_None = 0, + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, + ImGuiNavHighlightFlags_NoRounding = 1 << 3 +}; + +enum ImGuiNavDirSourceFlags_ +{ + ImGuiNavDirSourceFlags_None = 0, + ImGuiNavDirSourceFlags_Keyboard = 1 << 0, + ImGuiNavDirSourceFlags_PadDPad = 1 << 1, + ImGuiNavDirSourceFlags_PadLStick = 1 << 2 +}; + +enum ImGuiNavMoveFlags_ +{ + ImGuiNavMoveFlags_None = 0, + ImGuiNavMoveFlags_LoopX = 1 << 0, // On failed request, restart from opposite side + ImGuiNavMoveFlags_LoopY = 1 << 1, + ImGuiNavMoveFlags_WrapX = 1 << 2, // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left) + ImGuiNavMoveFlags_WrapY = 1 << 3, // This is not super useful for provided for completeness + ImGuiNavMoveFlags_AllowCurrentNavId = 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place) + ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5 // Store alternate result in NavMoveResultLocalVisibleSet that only comprise elements that are already fully visible. +}; + +enum ImGuiNavForward +{ + ImGuiNavForward_None, + ImGuiNavForward_ForwardQueued, + ImGuiNavForward_ForwardActive +}; + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +// 2D axis aligned bounding-box +// NB: we can't rely on ImVec2 math operators being available here +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } + float GetWidth() const { return Max.x - Min.x; } + float GetHeight() const { return Max.y - Min.y; } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } + void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; } + void TranslateX(float dx) { Min.x += dx; Max.x += dx; } + void TranslateY(float dy) { Min.y += dy; Max.y += dy; } + void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColorMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked storage data for BeginGroup()/EndGroup() +struct ImGuiGroupData +{ + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + ImVec1 BackupIndent; + ImVec1 BackupGroupOffset; + ImVec2 BackupCurrentLineSize; + float BackupCurrentLineTextBaseOffset; + float BackupLogLinePosY; + ImGuiID BackupActiveIdIsAlive; + bool BackupActiveIdPreviousFrameIsAlive; + bool AdvanceCursor; +}; + +// Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiMenuColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[4], NextWidths[4]; + + ImGuiMenuColumns(); + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w); +}; + +// Internal state of the currently focused/edited text input box +struct IMGUI_API ImGuiInputTextState +{ + ImGuiID ID; // widget id owning the text state + ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempBuffer; // temporary buffer for callback and other other operations. size=capacity. + int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. + int BufCapacityA; // end-user buffer capacity + float ScrollX; + ImGuiStb::STB_TexteditState StbState; + float CursorAnim; + bool CursorFollow; + bool SelectedAllMouseLock; + + // Temporarily set when active + ImGuiInputTextFlags UserFlags; + ImGuiInputTextCallback UserCallback; + void* UserCallbackData; + + ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } + bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } + void SelectAll() { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = false; } + void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation +}; + +// Windows data saved in imgui.ini file +struct ImGuiWindowSettings +{ + char* Name; + ImGuiID ID; + ImVec2 Pos; + ImVec2 Size; + bool Collapsed; + + ImGuiWindowSettings() { Name = NULL; ID = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); // Read: Called when entering into a new ini entry e.g. "[Window][Name]" + void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); // Read: Called for every line of text within an ini entry + void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' + void* UserData; + + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } +}; + +// Storage for current popup stack +struct ImGuiPopupRef +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup +}; + +struct ImGuiColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiColumnsFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; } +}; + +struct ImGuiColumnsSet +{ + ImGuiID ID; + ImGuiColumnsFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float MinX, MaxX; + float LineMinY, LineMaxY; + float StartPosY; // Copy of CursorPos + float StartMaxPosX; // Copy of CursorMaxPos + ImVector Columns; + + ImGuiColumnsSet() { Clear(); } + void Clear() + { + ID = 0; + Flags = 0; + IsFirstFrame = false; + IsBeingResized = false; + Current = 0; + Count = 1; + MinX = MaxX = 0.0f; + LineMinY = LineMaxY = 0.0f; + StartPosY = 0.0f; + StartMaxPosX = 0.0f; + Columns.clear(); + } +}; + +// Data shared between all ImDrawList instances +struct IMGUI_API ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + + // Const data + // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 CircleVtx12[12]; + + ImDrawListSharedData(); +}; + +struct ImDrawDataBuilder +{ + ImVector Layers[2]; // Global layers for: regular, tooltip + + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); } + void ClearFreeMemory() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +struct ImGuiNavMoveResult +{ + ImGuiID ID; // Best candidate + ImGuiWindow* Window; // Best candidate window + float DistBox; // Best candidate box distance to current NavId + float DistCenter; // Best candidate center distance to current NavId + float DistAxial; + ImRect RectRel; // Best candidate bounding box in window relative space + + ImGuiNavMoveResult() { Clear(); } + void Clear() { ID = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); } +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData +{ + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond ContentSizeCond; + ImGuiCond CollapsedCond; + ImGuiCond SizeConstraintCond; + ImGuiCond FocusCond; + ImGuiCond BgAlphaCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + bool CollapsedVal; + ImRect SizeConstraintRect; + ImGuiSizeCallback SizeCallback; + void* SizeCallbackUserData; + float BgAlphaVal; + ImVec2 MenuBarOffsetMinVal; // This is not exposed publicly, so we don't clear it. + + ImGuiNextWindowData() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); + ContentSizeVal = ImVec2(0.0f, 0.0f); + CollapsedVal = false; + SizeConstraintRect = ImRect(); + SizeCallback = NULL; + SizeCallbackUserData = NULL; + BgAlphaVal = FLT_MAX; + MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + } + + void Clear() + { + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + } +}; + +// Main imgui context +struct ImGuiContext +{ + bool Initialized; + bool FrameScopeActive; // Set by NewFrame(), cleared by EndFrame()/Render() + bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + + double Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + ImVector Windows; // Windows, sorted in display order, back to front + ImVector WindowsFocusOrder; // Windows, sorted in focus order, back to front + ImVector WindowsSortBuffer; + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; + int WindowsActiveCount; + ImGuiWindow* CurrentWindow; // Being drawn into + ImGuiWindow* HoveredWindow; // Will catch mouse inputs + ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiID HoveredId; // Hovered widget + bool HoveredIdAllowOverlap; + ImGuiID HoveredIdPreviousFrame; + float HoveredIdTimer; // Measure contiguous hovering time + float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdPreviousFrame; + ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) + float ActiveIdTimer; + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + bool ActiveIdHasBeenEdited; // Was the value associated to the widget Edited over the course of the Active state. + bool ActiveIdPreviousFrameIsAlive; + bool ActiveIdPreviousFrameHasBeenEdited; + int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and move away from it) + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiWindow* ActiveIdPreviousFrameWindow; + ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) + ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. + float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actually window that is moved is generally MovingWindow->RootWindow. + ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() + ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() + ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions + ImGuiCond NextTreeNodeOpenCond; + + // Navigation data (for gamepad/keyboard) + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 + ImGuiID NavJustTabbedId; // Just tabbed to this id. + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest) + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame + ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? + ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring. + int NavScoringCount; // Metrics for debugging + ImGuiWindow* NavWindowingTarget; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most. + ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f + ImGuiWindow* NavWindowingList; + float NavWindowingTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest + bool NavInitRequest; // Init request for appearing window to select first item + bool NavInitRequestFromMove; + ImGuiID NavInitResultId; + ImRect NavInitResultRectRel; + bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items + bool NavMoveRequest; // Move request for this frame + ImGuiNavMoveFlags NavMoveRequestFlags; + ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) + ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + ImGuiDir NavMoveClipDir; + ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavMoveResult NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) + ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) + + // Render + ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user + ImDrawDataBuilder DrawDataBuilder; + float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) + ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays + ImGuiMouseCursor MouseCursor; + + // Drag and Drop + bool DragDropActive; + bool DragDropWithinSourceOrTarget; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropSourceFrameCount; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; + ImGuiID DragDropTargetId; + ImGuiDragDropFlags DragDropAcceptFlags; + float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the smaller surface) + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; // Local buffer for small payloads + + // Widget state + ImGuiInputTextState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImVec4 ColorPickerRef; + bool DragCurrentAccumDirty; + float DragCurrentAccum; // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + ImVector PrivateClipboard; // If no custom clipboard handler is defined + ImVec2 PlatformImePos, PlatformImeLastPos; // Cursor position request & last passed to the OS Input Method Editor + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero + ImGuiTextBuffer SettingsIniData; // In memory .ini settings + ImVector SettingsHandlers; // List of .ini settings handlers + ImVector SettingsWindows; // ImGuiWindow .ini settings entries (parsed from the last loaded .ini file and maintained on saving) + + // Logging + bool LogEnabled; + FILE* LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer LogClipboard; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + int LogStartDepth; + int LogAutoExpandMaxDepth; + + // Misc + float FramerateSecPerFrame[120]; // Calculate estimate of framerate for user over the last 2 seconds. + int FramerateSecPerFrameIdx; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // Explicit capture via CaptureKeyboardFromApp()/CaptureMouseFromApp() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024*3+1]; // Temporary text buffer + + ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) + { + Initialized = false; + FrameScopeActive = false; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredRootWindow = NULL; + HoveredId = 0; + HoveredIdAllowOverlap = false; + HoveredIdPreviousFrame = 0; + HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; + ActiveId = 0; + ActiveIdPreviousFrame = 0; + ActiveIdIsAlive = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdHasBeenEdited = false; + ActiveIdPreviousFrameIsAlive = false; + ActiveIdPreviousFrameHasBeenEdited = false; + ActiveIdAllowNavDirFlags = 0; + ActiveIdClickOffset = ImVec2(-1,-1); + ActiveIdWindow = ActiveIdPreviousFrameWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + LastActiveId = 0; + LastActiveIdTimer = 0.0f; + MovingWindow = NULL; + NextTreeNodeOpenVal = false; + NextTreeNodeOpenCond = 0; + + NavWindow = NULL; + NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; + NavInputSource = ImGuiInputSource_None; + NavScoringRectScreen = ImRect(); + NavScoringCount = 0; + NavWindowingTarget = NavWindowingTargetAnim = NavWindowingList = NULL; + NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + NavLayer = 0; + NavIdTabCounter = INT_MAX; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveFromClampedRefRect = false; + NavMoveRequest = false; + NavMoveRequestFlags = 0; + NavMoveRequestForward = ImGuiNavForward_None; + NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None; + + DimBgRatio = 0.0f; + OverlayDrawList._Data = &DrawListSharedData; + OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging + MouseCursor = ImGuiMouseCursor_Arrow; + + DragDropActive = DragDropWithinSourceOrTarget = false; + DragDropSourceFlags = 0; + DragDropSourceFrameCount = -1; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptFlags = 0; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ScalarAsInputTextId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + DragCurrentAccumDirty = false; + DragCurrentAccum = 0.0f; + DragSpeedDefaultRatio = 1.0f / 100.0f; + ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); + TooltipOverrideCount = 0; + PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX); + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + + LogEnabled = false; + LogFile = NULL; + LogStartDepth = 0; + LogAutoExpandMaxDepth = 2; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_NoTabStop = 1 << 0, // false + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211 + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = 0 +}; + +// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. +// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered. +struct IMGUI_API ImGuiWindowTempData +{ + ImVec2 CursorPos; + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; // Initial position in client area with padding + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame + ImVec2 CurrentLineSize; + float CurrentLineTextBaseOffset; + ImVec2 PrevLineSize; + float PrevLineTextBaseOffset; + float LogLinePosY; + int TreeDepth; + ImU32 TreeDepthMayJumpToParentOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31 + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; // Interaction rect + ImRect LastItemDisplayRect; // End-user display rect (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) + int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) + int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping. + int NavLayerActiveMask; // Which layer have been written to (result from previous frame) + int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame) + bool MenuBarAppending; // FIXME: Remove this + ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. + ImVector ChildWindows; + ImGuiStorage* StateStorage; + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVectorItemFlagsStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + ImVec1 Indent; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + ImVec1 GroupOffset; + ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImGuiColumnsSet* ColumnsSet; // Current columns set + + ImGuiWindowTempData() + { + CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); + CurrentLineSize = PrevLineSize = ImVec2(0.0f, 0.0f); + CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; + LogLinePosY = -1.0f; + TreeDepth = 0; + TreeDepthMayJumpToParentOnPop = 0x00; + LastItemId = 0; + LastItemStatusFlags = 0; + LastItemRect = LastItemDisplayRect = ImRect(); + NavHideHighlightOneFrame = false; + NavHasScroll = false; + NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; + NavLayerCurrent = 0; + NavLayerCurrentMask = 1 << 0; + MenuBarAppending = false; + MenuBarOffset = ImVec2(0.0f, 0.0f); + StateStorage = NULL; + LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; + ItemWidth = 0.0f; + ItemFlags = ImGuiItemFlags_Default_; + TextWrapPos = -1.0f; + memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); + + Indent = ImVec1(0.0f); + GroupOffset = ImVec1(0.0f); + ColumnsOffset = ImVec1(0.0f); + ColumnsSet = NULL; + } +}; + +// Storage for one window +struct IMGUI_API ImGuiWindow +{ + char* Name; + ImGuiID ID; // == ImHash(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 Pos; // Position (always rounded-up to nearest pixel) + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. + ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() + ImVec2 WindowPadding; // Window padding at the time of begin. + float WindowRounding; // Window rounding at the time of begin. + float WindowBorderSize; // Window border size at the time of begin. + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) + ImVec2 Scroll; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + ImVec2 ScrollbarSizes; // Size taken by scrollbars on each axis + bool ScrollbarX, ScrollbarY; + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool WantCollapseToggle; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool Hidden; // Do not display (== (HiddenFramesForResize > 0) || + bool HasCloseButton; // Set when the window has a close button (p_open != NULL) + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. + int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + int AutoFitFramesX, AutoFitFramesY; + bool AutoFitOnlyGrows; + int AutoFitChildAxises; + ImGuiDir AutoPosLastDirection; + int HiddenFramesRegular; // Hide the window for N frames + int HiddenFramesForResize; // Hide the window for N frames while allowing items to be submitted so we can measure their size + ImGuiCond SetWindowPosAllowFlags; // store acceptable condition flags for SetNextWindowPos() use. + ImGuiCond SetWindowSizeAllowFlags; // store acceptable condition flags for SetNextWindowSize() use. + ImGuiCond SetWindowCollapsedAllowFlags; // store acceptable condition flags for SetNextWindowCollapsed() use. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + + ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack + ImRect ClipRect; // Current clipping rectangle. = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. + ImRect OuterRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerMainRect, InnerClipRect; + ImRect ContentsRegionRect; // FIXME: This is currently confusing/misleading. Maximum visible content position ~~ Pos + (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis + int LastFrameActive; // Last frame number the window was Active. + float ItemWidthDefault; + ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // User scale multiplier per-window + int SettingsIdx; // Index into SettingsWindow[] (indices are always valid as we only grow the array from the back) + + ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) + ImDrawList DrawListInst; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. + ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. + ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) + ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1) + ImRect NavRectRel[2]; // Reference rectangle, in window relative space + + // Navigation / Focus + // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext + int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() + int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) + int FocusIdxAllRequestCurrent; // Item being requested for focus + int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus + int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) + int FocusIdxTabRequestNext; // " + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDNoKeepAlive(const void* ptr); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; + ImRect LastItemDisplayRect; + + ImGuiItemHoveredDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } +}; + +//----------------------------------------------------------------------------- +// Internal API +// No guarantee of forward compatibility here. +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void FocusPreviousWindowIgnoringOne(ImGuiWindow* ignore_window); + IMGUI_API void BringWindowToFocusFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayBack(ImGuiWindow* window); + IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); + IMGUI_API ImVec2 CalcWindowExpectedSize(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); + IMGUI_API void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); + IMGUI_API void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); + IMGUI_API float GetWindowScrollMaxX(ImGuiWindow* window); + IMGUI_API float GetWindowScrollMaxY(ImGuiWindow* window); + IMGUI_API ImRect GetWindowAllowedExtentRect(ImGuiWindow* window); + IMGUI_API void SetCurrentFont(ImFont* font); + inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + + // Init + IMGUI_API void Initialize(ImGuiContext* context); + IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + + // NewFrame + IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); + IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void UpdateMouseMovingWindow(); + + // Settings + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); + IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + + // Basic Accessors + inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemId; } + inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } + inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API ImGuiID GetHoveredID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API void KeepAliveID(ImGuiID id); + IMGUI_API void MarkItemEdited(ImGuiID id); + + // Basic Helpers for widget code + IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested + IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + + // Popups, Modals, Tooltips + IMGUI_API void OpenPopupEx(ImGuiID id); + IMGUI_API void ClosePopup(ImGuiID id); + IMGUI_API void ClosePopupToLevel(int remaining); + IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window); + IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); + IMGUI_API ImGuiWindow* GetFrontMostPopupModal(); + IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); + IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default); + + // Navigation + IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); + IMGUI_API bool NavMoveRequestButNoResultYet(); + IMGUI_API void NavMoveRequestCancel(); + IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags); + IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); + IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); + IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. + IMGUI_API void SetNavID(ImGuiID id, int nav_layer); + IMGUI_API void SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel); + + // Inputs + inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { const int key_index = GImGui->IO.KeyMap[key]; return (key_index >= 0) ? IsKeyPressed(key_index, repeat) : false; } + inline bool IsNavInputDown(ImGuiNavInput n) { return GImGui->IO.NavInputs[n] > 0.0f; } + inline bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) { return GetNavInputAmount(n, mode) > 0.0f; } + inline bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) { return (GetNavInputAmount(n1, mode) + GetNavInputAmount(n2, mode)) > 0.0f; } + + // Drag and Drop + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // New Columns API (FIXME-WIP) + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index = -1); + + // Render helpers + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally) + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderArrow(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImVec2 pos); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); + + // Render helpers (those functions don't access any ImGui state!) + IMGUI_API void RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor = ImGuiMouseCursor_Arrow); + IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + + // Widgets + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); + IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); + IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags); + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. + + // Widgets low-level behaviors + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power); + IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb); + IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f); + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging + IMGUI_API void TreePushRawID(ImGuiID id); + + // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. + // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). + // e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " + template IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, const T v_min, const T v_max, const char* format, float power); + template IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, const T v_min, const T v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb); + template IMGUI_API float SliderCalcRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, float power, float linear_zero_pos); + template IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); + + // InputText + IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format); + + // Color + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); + + // Plot + IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); + + // Shade functions (write over already created vertices) + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + +} // namespace ImGui + +// ImFontAtlas internals +IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp new file mode 100644 index 000000000..7bf5938bd --- /dev/null +++ b/src/imgui/imgui_widgets.cpp @@ -0,0 +1,5732 @@ +// dear imgui, v1.66 WIP +// (widgets code) + +/* + +Index of this file: + +// [SECTION] Forward Declarations +// [SECTION] Widgets: Text, etc. +// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) +// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) +// [SECTION] Widgets: ComboBox +// [SECTION] Data Type and Data Formatting Helpers +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +// [SECTION] Widgets: InputText, InputTextMultiline +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +// [SECTION] Widgets: Selectable +// [SECTION] Widgets: ListBox +// [SECTION] Widgets: PlotLines, PlotHistogram +// [SECTION] Widgets: Value helpers +// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +#include // toupper, isprint +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang/GCC warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#if __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif +#endif + +//------------------------------------------------------------------------- +// Data +//------------------------------------------------------------------------- + +// Those MIN/MAX values are not define because we need to point to them +static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); +static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) +static const ImU32 IM_U32_MIN = 0; +static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) +#ifdef LLONG_MIN +static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); +static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); +#else +static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; +static const ImS64 IM_S64_MAX = 9223372036854775807LL; +#endif +static const ImU64 IM_U64_MIN = 0; +#ifdef ULLONG_MAX +static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); +#else +static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); +#endif + +//------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//------------------------------------------------------------------------- + +// Data Type helpers +static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format); +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2); +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format); + +// For InputTextEx() +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Text, etc. +//------------------------------------------------------------------------- +// - TextUnformatted() +// - Text() +// - TextV() +// - TextColored() +// - TextColoredV() +// - TextDisabled() +// - TextDisabledV() +// - TextWrapped() +// - TextWrappedV() +// - LabelText() +// - LabelTextV() +// - BulletText() +// - BulletTextV() +//------------------------------------------------------------------------- + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = wrap_pos_x >= 0.0f; + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. + const char* line = text; + const float line_height = GetTextLineHeight(); + const ImRect clip_rect = window->ClipRect; + ImVec2 text_size(0,0); + + if (text_pos.y <= clip_rect.Max.y) + { + ImVec2 pos = text_pos; + + // Lines to skip (can't skip when logging text) + if (!g.LogEnabled) + { + int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + if (IsClippedEx(line_rect, 0, false)) + break; + + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + const ImVec2 line_size = CalcTextSize(line, line_end, false); + text_size.x = ImMax(text_size.x, line_size.x); + RenderText(pos, line, line_end, false); + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + + text_size.y += (pos - text_pos).y; + } + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(bb); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + // Account of baseline offset + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextUnformatted(g.TempBuffer, text_end); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set + if (need_wrap) PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_wrap) PopTextWrapPos(); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); + const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Main +//------------------------------------------------------------------------- +// - ButtonBehavior() [Internal] +// - Button() +// - SmallButton() +// - InvisibleButton() +// - ArrowButton() +// - CloseButton() [Internal] +// - CollapseButton() [Internal] +// - Scrollbar() [Internal] +// - Image() +// - ImageButton() +// - Checkbox() +// - CheckboxFlags() +// - RadioButton() +// - ProgressBar() +// - Bullet() +//------------------------------------------------------------------------- + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default behavior requires click+release on same spot + if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) + flags |= ImGuiButtonFlags_PressedOnClickRelease; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Drag source doesn't report as hovered + if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) + hovered = false; + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy + { + pressed = true; + FocusWindow(window); + } + } + + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat + // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds + // PressedOnClick | | .. + // PressedOnRelease | | .. (NOT on release) + // PressedOnDoubleClick | | .. + // FIXME-NAV: We don't honor those different behaviors. + if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) + { + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + hovered = true; + + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); + if (nav_activated_by_code || nav_activated_by_inputs) + pressed = true; + if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + g.NavActivateId = id; // This is so SetActiveId assign a Nav source + SetActiveID(id, window); + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + } + + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + if (g.IO.MouseDown[0]) + { + held = true; + } + else + { + if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + if (!g.DragDropActive) + pressed = true; + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + if (g.NavActivateDownId != id) + ClearActiveID(); + } + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + if (pressed) + MarkItemEdited(id); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. + IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + return pressed; +} + +bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = GetFrameHeight(); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding); + RenderArrow(bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), dir); + + return pressed; +} + +bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) +{ + float sz = GetFrameHeight(); + return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0); +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); + bool is_clipped = !ItemAdd(bb, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + ImVec2 center = bb.GetCenter(); + if (hovered) + window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9); + + float cross_extent = (radius * 0.7071f) - 1.0f; + ImU32 cross_col = GetColorU32(ImGuiCol_Text); + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f); + + return pressed; +} + +bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); + ItemAdd(bb, id); + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + + ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + if (hovered || held) + window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9); + RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + + // Switch to moving the window after mouse is moved beyond the initial drag threshold + if (IsItemActive() && IsMouseDragging()) + StartMouseMovingWindow(window); + + return pressed; +} + +// Vertical/Horizontal scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +void ImGui::Scrollbar(ImGuiLayoutType direction) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); + + // Render background + bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); + float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; + const ImRect window_rect = window->Rect(); + const float border_size = window->WindowBorderSize; + ImRect bb = horizontal + ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); + if (!horizontal) + bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); + if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) + return; + + int window_rounding_corners; + if (horizontal) + window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + else + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); + bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); + float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; + float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; + float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + const bool previously_held = (g.ActiveId == id); + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); + float scroll_ratio = ImSaturate(scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + if (held && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; + float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (!previously_held) + { + // On initial click calculate the distance between mouse and the center of the grab + if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) + { + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + else + { + seek_absolute = true; + *click_delta_to_grab_center_v = 0.0f; + } + } + + // Apply scroll + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); + scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + if (horizontal) + window->Scroll.x = scroll_v; + else + window->Scroll.y = scroll_v; + + // Update values for rendering + scroll_ratio = ImSaturate(scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + + // Render + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + ImRect grab_rect; + if (horizontal) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2, 2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); + } + + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + { + *v = !(*v); + MarkItemEdited(id); + } + + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + if (*v) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + bool v = ((*flags & flags_value) == flags_value); + bool pressed = Checkbox(label, &v); + if (pressed) + { + if (v) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + + return pressed; +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb.Add(text_bb); + } + + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = (float)(int)center.x + 0.5f; + center.y = (float)(int)center.y + 0.5f; + const float radius = check_bb.GetHeight() * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + MarkItemEdited(id); + + RenderNavHighlight(total_bb, id); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + *v = v_button; + return pressed; +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x*2); + return; + } + + // Render and stay on same line + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + SameLine(0, style.FramePadding.x*2); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Low-level Layout helpers +//------------------------------------------------------------------------- +// - Spacing() +// - Dummy() +// - NewLine() +// - AlignTextToFramePadding() +// - Separator() +// - VerticalSeparator() [Internal] +// - SplitterBehavior() [Internal] +//------------------------------------------------------------------------- + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0,0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + ItemAdd(bb, 0); +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrentLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0,0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Horizontal/vertical separating line +void ImGui::Separator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Those flags should eventually be overridable by the user + ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator + if (window->DC.ColumnsSet) + PopClipRect(); + + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + if (!window->DC.GroupStack.empty()) + x1 += window->DC.Indent.x; + + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); + ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. + if (!ItemAdd(bb, 0)) + { + if (window->DC.ColumnsSet) + PushColumnClipRect(); + return; + } + + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); + + if (g.LogEnabled) + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); + + if (window->DC.ColumnsSet) + { + PushColumnClipRect(); + window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; + } +} + +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + +// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. +bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; + window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + window->DC.ItemFlags = item_flags_backup; + if (!item_add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); + float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); + if (mouse_delta < -size_1_maximum_delta) + mouse_delta = -size_1_maximum_delta; + if (mouse_delta > size_2_maximum_delta) + mouse_delta = size_2_maximum_delta; + + // Apply resize + if (mouse_delta != 0.0f) + { + if (mouse_delta < 0.0f) + IM_ASSERT(*size1 + mouse_delta >= min_size1); + if (mouse_delta > 0.0f) + IM_ASSERT(*size2 - mouse_delta >= min_size2); + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + MarkItemEdited(id); + } + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); + + return held; +} + + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Combo Box +//------------------------------------------------------------------------- +// - BeginCombo() +// - EndCombo() +// - Combo() +//------------------------------------------------------------------------- + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; + g.NextWindowData.SizeConstraintCond = 0; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth(); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); + + const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); + const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + if (!(flags & ImGuiComboFlags_NoPreview)) + window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left); + if (!(flags & ImGuiComboFlags_NoArrowButton)) + { + window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right); + RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); + } + RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); + if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((pressed || g.NavActivateId == id) && !popup_open) + { + if (window->DC.NavLayerCurrent == 0) + window->NavLastIds[0] = id; + OpenPopupEx(id); + popup_open = true; + } + + if (!popup_open) + return false; + + if (backup_next_window_size_constraint) + { + g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + + // Peak into expected window size so we can position it + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + ImVec2 size_expected = CalcWindowExpectedSize(popup_window); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImRect r_outer = GetWindowAllowedExtentRect(popup_window); + ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + // Horizontally align ourselves with the framed text + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); + bool ret = Begin(name, NULL, window_flags); + PopStyleVar(); + if (!ret) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + return true; +} + +void ImGui::EndCombo() +{ + EndPopup(); +} + +// Getter for the old Combo() API: const char*[] +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +// Getter for the old Combo() API: "item1\0item2\0item3\0" +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + // Call the getter to obtain the preview string which is a parameter to BeginCombo() + const char* preview_value = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_value); + + // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. + if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + + if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + return value_changed; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Data Type and Data Formatting Helpers [Internal] +//------------------------------------------------------------------------- +// - PatchFormatStringFloatToInt() +// - DataTypeFormatString() +// - DataTypeApplyOp() +// - DataTypeApplyOpFromText() +// - GetMinimumStepAtDecimalPrecision +// - RoundScalarWithFormat<>() +//------------------------------------------------------------------------- + +struct ImGuiDataTypeInfo +{ + size_t Size; + const char* PrintFmt; // Unused + const char* ScanFmt; +}; + +static const ImGuiDataTypeInfo GDataTypeInfo[] = +{ + { sizeof(int), "%d", "%d" }, + { sizeof(unsigned int), "%u", "%u" }, +#ifdef _MSC_VER + { sizeof(ImS64), "%I64d","%I64d" }, + { sizeof(ImU64), "%I64u","%I64u" }, +#else + { sizeof(ImS64), "%lld", "%lld" }, + { sizeof(ImU64), "%llu", "%llu" }, +#endif + { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg + { sizeof(double), "%f", "%lf" }, +}; +IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); + +// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f". +// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls. +// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! +static const char* PatchFormatStringFloatToInt(const char* fmt) +{ + if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case. + return "%d"; + const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) + const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user). + if (fmt_end > fmt_start && fmt_end[-1] == 'f') + { +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (fmt_start == fmt && fmt_end[0] == 0) + return "%d"; + ImGuiContext& g = *GImGui; + ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. + return g.TempBuffer; +#else + IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d" +#endif + } + return fmt; +} + +static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format) +{ + if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument + return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr); + if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument + return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr); + if (data_type == ImGuiDataType_Float) + return ImFormatString(buf, buf_size, format, *(const float*)data_ptr); + if (data_type == ImGuiDataType_Double) + return ImFormatString(buf, buf_size, format, *(const double*)data_ptr); + IM_ASSERT(0); + return 0; +} + +// FIXME: Adding support for clamping on boundaries of the data type would be nice. +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2) +{ + IM_ASSERT(op == '+' || op == '-'); + switch (data_type) + { + case ImGuiDataType_S32: + if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2; + else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2; + return; + case ImGuiDataType_U32: + if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2; + else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2; + return; + case ImGuiDataType_S64: + if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2; + else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2; + return; + case ImGuiDataType_U64: + if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2; + else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2; + return; + case ImGuiDataType_Float: + if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2; + else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2; + return; + case ImGuiDataType_Double: + if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2; + else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2; + return; + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); +} + +// User can input math operators (e.g. +100) to edit a numerical values. +// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format) +{ + while (ImCharIsBlankA(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsBlankA(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. + IM_ASSERT(data_type < ImGuiDataType_COUNT); + int data_backup[2]; + IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup)); + memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size); + + if (format == NULL) + format = GDataTypeInfo[data_type].ScanFmt; + + int arg1i = 0; + if (data_type == ImGuiDataType_S32) + { + int* v = (int*)data_ptr; + int arg0i = *v; + float arg1f = 0.0f; + if (op && sscanf(initial_value_buf, format, &arg0i) < 1) + return false; + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide + else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant + } + else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) + { + // Assign constant + // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future. + sscanf(buf, format, data_ptr); + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + format = "%f"; + float* v = (float*)data_ptr; + float arg0f = *v, arg1f = 0.0f; + if (op && sscanf(initial_value_buf, format, &arg0f) < 1) + return false; + if (sscanf(buf, format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + } + else if (data_type == ImGuiDataType_Double) + { + format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis + double* v = (double*)data_ptr; + double arg0f = *v, arg1f = 0.0; + if (op && sscanf(initial_value_buf, format, &arg0f) < 1) + return false; + if (sscanf(buf, format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + } + return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + if (decimal_precision < 0) + return FLT_MIN; + return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); +} + +template +static const char* ImAtoi(const char* src, TYPE* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + TYPE v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +template +TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) +{ + const char* fmt_start = ImParseFormatFindStart(format); + if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string + return v; + char v_str[64]; + ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); + const char* p = v_str; + while (*p == ' ') + p++; + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + v = (TYPE)ImAtof(p); + else + ImAtoi(p, (SIGNEDTYPE*)&v); + return v; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +//------------------------------------------------------------------------- +// - DragBehaviorT<>() [Internal] +// - DragBehavior() [Internal] +// - DragScalar() +// - DragScalarN() +// - DragFloat() +// - DragFloat2() +// - DragFloat3() +// - DragFloat4() +// - DragFloatRange2() +// - DragInt() +// - DragInt2() +// - DragInt3() +// - DragInt4() +// - DragIntRange2() +//------------------------------------------------------------------------- + +// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) +template +bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power) +{ + ImGuiContext& g = *GImGui; + const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + const bool has_min_max = (v_min != v_max); + + // Default tweak speed + if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX)) + v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); + + // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f) + { + adjust_delta = g.IO.MouseDelta.x; + if (g.IO.KeyAlt) + adjust_delta *= 1.0f / 100.0f; + if (g.IO.KeyShift) + adjust_delta *= 10.0f; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; + adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f).x; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + // Clear current value on activation + // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. + bool is_just_activated = g.ActiveIdIsJustActivated; + bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); + if (is_just_activated || is_already_past_limits_and_pushing_outward) + { + g.DragCurrentAccum = 0.0f; + g.DragCurrentAccumDirty = false; + } + else if (adjust_delta != 0.0f) + { + g.DragCurrentAccum += adjust_delta; + g.DragCurrentAccumDirty = true; + } + + if (!g.DragCurrentAccumDirty) + return false; + + TYPE v_cur = *v; + FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; + + const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX)); + if (is_power) + { + // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range + FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); + FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min)); + v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min); + v_old_ref_for_accum_remainder = v_old_norm_curved; + } + else + { + v_cur += (TYPE)g.DragCurrentAccum; + } + + // Round to user desired precision based on format string + v_cur = RoundScalarWithFormatT(format, data_type, v_cur); + + // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. + g.DragCurrentAccumDirty = false; + if (is_power) + { + FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); + g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder); + } + else + { + g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); + } + + // Lose zero sign for float/double + if (v_cur == (TYPE)-0) + v_cur = (TYPE)0; + + // Clamp values (+ handle overflow/wrap-around for integer types) + if (*v != v_cur && has_min_max) + { + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal)) + v_cur = v_min; + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal)) + v_cur = v_max; + } + + // Apply result + if (*v == v_cur) + return false; + *v = v_cur; + return true; +} + +bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId != id) + return false; + + switch (data_type) + { + case ImGuiDataType_S32: return DragBehaviorT(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power); + case ImGuiDataType_U32: return DragBehaviorT(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power); + case ImGuiDataType_S64: return DragBehaviorT(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power); + case ImGuiDataType_U64: return DragBehaviorT(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power); + case ImGuiDataType_Float: return DragBehaviorT(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power); + case ImGuiDataType_Double: return DragBehaviorT(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (power != 1.0f) + IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + // Default format string when passing NULL + // Patch old "%.0f" format string to use "%d", read function comments for more details. + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + if (format == NULL) + format = GDataTypeInfo[data_type].PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Drag turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); + + // Actual drag behavior + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power); + if (value_changed) + MarkItemEdited(id); + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + return value_changed; +} + +bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + v = (void*)((char*)v + type_size); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) +{ + return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); +} + +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format) +{ + return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format); +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format); +} + +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +//------------------------------------------------------------------------- +// - SliderBehaviorT<>() [Internal] +// - SliderBehavior() [Internal] +// - SliderScalar() +// - SliderScalarN() +// - SliderFloat() +// - SliderFloat2() +// - SliderFloat3() +// - SliderFloat4() +// - SliderAngle() +// - SliderInt() +// - SliderInt2() +// - SliderInt3() +// - SliderInt4() +// - VSliderScalar() +// - VSliderFloat() +// - VSliderInt() +//------------------------------------------------------------------------- + +template +float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos) +{ + if (v_min == v_max) + return 0.0f; + + const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); + const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_power) + { + if (v_clamped < 0.0f) + { + const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min)); + return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos; + } + else + { + const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min))); + return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos); + } + } + + // Linear slider + return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min)); +} + +// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc. +template +bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; + const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + const bool is_power = (power != 1.0f) && is_decimal; + + const float grab_padding = 2.0f; + const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f); + float grab_sz = style.GrabMinSize; + SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows + grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit + grab_sz = ImMin(grab_sz, slider_sz); + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f; + const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f; + + // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f + float linear_zero_pos; // 0.0->1.0f + if (is_power && v_min * v_max < 0.0f) + { + // Different sign + const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power); + const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power); + linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0)); + } + else + { + // Same sign + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (!is_horizontal) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); + float delta = is_horizontal ? delta2.x : -delta2.y; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (delta != 0.0f) + { + clicked_t = SliderCalcRatioFromValueT(data_type, *v, v_min, v_max, power, linear_zero_pos); + const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; + if ((decimal_precision > 0) || is_power) + { + delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta /= 10.0f; + } + else + { + if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps + else + delta /= 100.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= 10.0f; + set_new_value = true; + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + set_new_value = false; + else + clicked_t = ImSaturate(clicked_t + delta); + } + } + + if (set_new_value) + { + TYPE v_new; + if (is_power) + { + // Account for power curve scale on both sides of the zero + if (clicked_t < linear_zero_pos) + { + // Negative: rescale to the negative range before powering + float a = 1.0f - (clicked_t / linear_zero_pos); + a = ImPow(a, power); + v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a); + } + else + { + // Positive: rescale to the positive range before powering + float a; + if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = ImPow(a, power); + v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a); + } + } + else + { + // Linear slider + if (is_decimal) + { + v_new = ImLerp(v_min, v_max, clicked_t); + } + else + { + // For integer values we want the clicking position to match the grab box so we round above + // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. + FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t; + TYPE v_new_off_floor = (TYPE)(v_new_off_f); + TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5); + if (!is_decimal && v_new_off_floor < v_new_off_round) + v_new = v_min + v_new_off_round; + else + v_new = v_min + v_new_off_floor; + } + } + + // Round to user desired precision based on format string + v_new = RoundScalarWithFormatT(format, data_type, v_new); + + // Apply result + if (*v != v_new) + { + *v = v_new; + value_changed = true; + } + } + } + + // Output grab position so it can be displayed by the caller + float grab_t = SliderCalcRatioFromValueT(data_type, *v, v_min, v_max, power, linear_zero_pos); + if (!is_horizontal) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + if (is_horizontal) + *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding); + else + *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f); + + return value_changed; +} + +// For 32-bits and larger types, slider bounds are limited to half the natural type range. +// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. +// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. +bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + switch (data_type) + { + case ImGuiDataType_S32: + IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2); + return SliderBehaviorT(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_U32: + IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2); + return SliderBehaviorT(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_S64: + IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2); + return SliderBehaviorT(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_U64: + IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2); + return SliderBehaviorT(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_Float: + IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f); + return SliderBehaviorT(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_Double: + IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f); + return SliderBehaviorT(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id, &frame_bb)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + + // Default format string when passing NULL + // Patch old "%.0f" format string to use "%d", read function comments for more details. + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + if (format == NULL) + format = GDataTypeInfo[data_type].PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + const bool hovered = ItemHoverable(frame_bb, id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); + + ItemSize(total_bb, style.FramePadding.y); + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + v = (void*)((char*)v + type_size); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + return value_changed; +} + +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) +{ + return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format) +{ + if (format == NULL) + format = "%.0f deg"; + float v_deg = (*v_rad) * 360.0f / (2*IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, 1.0f); + *v_rad = v_deg * (2*IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format) +{ + return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format); +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format); +} + +bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + + // Default format string when passing NULL + // Patch old "%.0f" format string to use "%d", read function comments for more details. + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + if (format == NULL) + format = GDataTypeInfo[data_type].PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) + format = PatchFormatStringFloatToInt(format); + + const bool hovered = ItemHoverable(frame_bb, id); + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power) +{ + return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power); +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format) +{ + return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +//------------------------------------------------------------------------- +// - ImParseFormatFindStart() [Internal] +// - ImParseFormatFindEnd() [Internal] +// - ImParseFormatTrimDecorations() [Internal] +// - ImParseFormatPrecision() [Internal] +// - InputScalarAsWidgetReplacement() [Internal] +// - InputScalar() +// - InputScalarN() +// - InputFloat() +// - InputFloat2() +// - InputFloat3() +// - InputFloat4() +// - InputInt() +// - InputInt2() +// - InputInt3() +// - InputInt4() +// - InputDouble() +//------------------------------------------------------------------------- + +// We don't use strchr() because our strings are usually very short and often start with '%' +const char* ImParseFormatFindStart(const char* fmt) +{ + while (char c = fmt[0]) + { + if (c == '%' && fmt[1] != '%') + return fmt; + else if (c == '%') + fmt++; + fmt++; + } + return fmt; +} + +const char* ImParseFormatFindEnd(const char* fmt) +{ + // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. + if (fmt[0] != '%') + return fmt; + const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); + const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); + for (char c; (c = *fmt) != 0; fmt++) + { + if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) + return fmt + 1; + if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) + return fmt + 1; + } + return fmt; +} + +// Extract the format out of a format string with leading or trailing decorations +// fmt = "blah blah" -> return fmt +// fmt = "%.3f" -> return fmt +// fmt = "hello %.3f" -> return fmt + 6 +// fmt = "%.3f hello" -> return buf written with "%.3f" +const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size) +{ + const char* fmt_start = ImParseFormatFindStart(fmt); + if (fmt_start[0] != '%') + return fmt; + const char* fmt_end = ImParseFormatFindEnd(fmt_start); + if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. + return fmt_start; + ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size)); + return buf; +} + +// Parse display precision back from the display format string +// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. +int ImParseFormatPrecision(const char* fmt, int default_precision) +{ + fmt = ImParseFormatFindStart(fmt); + if (fmt[0] != '%') + return default_precision; + fmt++; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + int precision = INT_MAX; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 99) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) + precision = -1; + return (precision == INT_MAX) ? default_precision : precision; +} + +// Create text input in place of a slider (when CTRL+Clicking on slider) +// FIXME: Logic is messy and confusing. +bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id + SetActiveID(g.ScalarAsInputTextId, window); + g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetHoveredID(0); + FocusableItemUnregister(window); + + char fmt_buf[32]; + char data_buf[32]; + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format); + ImStrTrimBlanks(data_buf); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal); + bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags); + if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget + { + IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID + g.ScalarAsInputTextId = g.ActiveId; + SetHoveredID(id); + } + if (value_changed) + return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL); + return false; +} + +// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument) +bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + if (format == NULL) + format = GDataTypeInfo[data_type].PrintFmt; + + char buf[64]; + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format); + + bool value_changed = false; + if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0) + extra_flags |= ImGuiInputTextFlags_CharsDecimal; + extra_flags |= ImGuiInputTextFlags_AutoSelectAll; + + if (step != NULL) + { + const float button_size = GetFrameHeight(); + + BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() + PushID(label); + PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); + if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); + PopItemWidth(); + + // Step buttons + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, FindRenderedTextEnd(label)); + + PopID(); + EndGroup(); + } + else + { + if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags)) + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); + } + + return value_changed; +} + +bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + v = (void*)((char*)v + type_size); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags) +{ + extra_flags |= ImGuiInputTextFlags_CharsScientific; + return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags); +} + +bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags); +} + +// Prefer using "const char* format" directly, which is more flexible and consistent with other API. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char format[16] = "%f"; + if (decimal_precision >= 0) + ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); + return InputFloat(label, v, step, step_fast, format, extra_flags); +} + +bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char format[16] = "%f"; + if (decimal_precision >= 0) + ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); + return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char format[16] = "%f"; + if (decimal_precision >= 0) + ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); + return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char format[16] = "%f"; + if (decimal_precision >= 0) + ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); + return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags); +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags); +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags); +} + +bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags) +{ + extra_flags |= ImGuiInputTextFlags_CharsScientific; + return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputText, InputTextMultiline +//------------------------------------------------------------------------- +// - InputText() +// - InputTextMultiline() +// - InputTextEx() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((ImWchar)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImGuiStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +{ + const ImWchar* text = obj->TextW.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +{ + ImWchar* dst = obj->TextW.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text (FIXME-OPT: Use memmove) + const ImWchar* src = obj->TextW.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0; + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) + return false; + + // Grow internal buffer if needed + if (new_text_len + text_len + 1 > obj->TextW.Size) + { + if (!is_resizable) + return false; + IM_ASSERT(text_len < obj->TextW.Size); + obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); + } + + ImWchar* text = obj->TextW.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->TextW[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_SHIFT 0x20000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "imstb_textedit.h" + +} + +void ImGuiInputTextState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &StbState, key); + CursorFollow = true; + CursorAnimReset(); +} + +ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() +{ + memset(this, 0, sizeof(*this)); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos + bytes_count >= pos) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen >= BufSize) + { + if (!is_resizable) + return; + + // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!) + ImGuiContext& g = *GImGui; + ImGuiInputTextState* edit_state = &g.InputTextState; + IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); + IM_ASSERT(Buf == edit_state->TempBuffer.Data); + int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; + edit_state->TempBuffer.reserve(new_buf_size + 1); + Buf = edit_state->TempBuffer.Data; + BufSize = edit_state->BufCapacityA = new_buf_size; + } + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + unsigned int c = *p_char; + + if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. + return false; + + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)) + { + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + if (flags & ImGuiInputTextFlags_CharsScientific) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) + return false; + + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A'-'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsBlankW(c)) + return false; + } + + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiInputTextCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". +// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match +// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. +// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. +// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h +// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) +bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + const ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + if (is_resizable) + IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! + + if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); + + ImGuiWindow* draw_window = window; + if (is_multiline) + { + ItemAdd(total_bb, id, &frame_bb); + if (!BeginChildFrame(id, frame_bb.GetSize())) + { + EndChildFrame(); + EndGroup(); + return false; + } + draw_window = GetCurrentWindow(); + draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight + size.x -= draw_window->ScrollbarSizes.x; + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // Password pushes a temporary font with only a fallback glyph + if (is_password) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->DisplayOffset = g.Font->DisplayOffset; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // NB: we are only allowed to access 'edit_state' if we are the active widget. + ImGuiInputTextState& edit_state = g.InputTextState; + + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); + const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); + + bool clear_active_id = false; + + bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); + if (focus_requested || user_clicked || user_scrolled || user_nav_input_start) + { + if (g.ActiveId != id) + { + // Start edition + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int prev_len_w = edit_state.CurLenW; + const int init_buf_len = (int)strlen(buf); + edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + edit_state.CursorAnimReset(); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + edit_state.CursorClamp(); + } + else + { + edit_state.ID = id; + edit_state.ScrollX = 0.0f; + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + edit_state.StbState.insert_mode = true; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); + } + else if (io.MouseClicked[0]) + { + // Release focus when we click outside + clear_active_id = true; + } + + bool value_changed = false; + bool enter_pressed = false; + int backup_current_text_length = 0; + + if (g.ActiveId == id) + { + if (!is_editable && !g.ActiveIdIsJustActivated) + { + // When read-only we always use the live data passed to the function + edit_state.TextW.resize(buf_size+1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); + edit_state.CursorClamp(); + } + + backup_current_text_length = edit_state.CurLenA; + edit_state.BufCapacityA = buf_size; + edit_state.UserFlags = flags; + edit_state.UserCallback = callback; + edit_state.UserCallbackData = callback_user_data; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + + const bool is_osx = io.ConfigMacOSXBehaviors; + if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) + { + edit_state.SelectAll(); + edit_state.SelectedAllMouseLock = true; + } + else if (hovered && is_osx && io.MouseDoubleClicked[0]) + { + // Double-click select a word only, OS X style (by simulating keystrokes) + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + { + if (hovered) + { + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + edit_state.CursorFollow = true; + } + if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) + edit_state.SelectedAllMouseLock = false; + + if (io.InputCharacters[0]) + { + // Process text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. + bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); + if (!ignore_inputs && is_editable && !user_nav_input_start) + for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) + { + // Insert character if they pass filtering + unsigned int c = (unsigned int)io.InputCharacters[n]; + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + edit_state.OnKeyPressed((int)c); + } + + // Consume characters + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + } + } + + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_osx = io.ConfigMacOSXBehaviors; + const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl + const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt; + const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; + const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; + + const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; + const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable); + const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable; + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + { + if (!edit_state.HasSelection()) + { + if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + } + edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (is_editable) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + edit_state.OnKeyPressed((int)c); + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) + { + clear_active_id = cancel_edit = true; + } + else if (is_undo || is_redo) + { + edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + edit_state.ClearSelection(); + } + else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A)) + { + edit_state.SelectAll(); + edit_state.CursorFollow = true; + } + else if (is_cut || is_copy) + { + // Cut, Copy + if (io.SetClipboardTextFn) + { + const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + edit_state.TempBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie); + SetClipboardText(edit_state.TempBuffer.Data); + } + if (is_cut) + { + if (!edit_state.HasSelection()) + edit_state.SelectAll(); + edit_state.CursorFollow = true; + stb_textedit_cut(&edit_state, &edit_state.StbState); + } + } + else if (is_paste) + { + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)MemAlloc((clipboard_len+1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } + MemFree(clipboard_filtered); + } + } + } + + if (g.ActiveId == id) + { + const char* apply_new_text = NULL; + int apply_new_text_length = 0; + if (cancel_edit) + { + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. + if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0) + { + apply_new_text = edit_state.InitialText.Data; + apply_new_text_length = edit_state.InitialText.Size - 1; + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (is_editable) + { + edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1); + ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + event_flag = ImGuiInputTextFlags_CallbackAlways; + + if (event_flag) + { + ImGuiInputTextCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = callback_user_data; + + callback_data.EventKey = event_key; + callback_data.Buf = edit_state.TempBuffer.Data; + callback_data.BufTextLen = edit_state.CurLenA; + callback_data.BufSize = edit_state.BufCapacityA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = edit_state.TextW.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (callback_data.BufDirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + if (callback_data.BufTextLen > backup_current_text_length && is_resizable) + edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL); + edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + edit_state.CursorAnimReset(); + } + } + } + + // Will copy result string if modified + if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0) + { + apply_new_text = edit_state.TempBuffer.Data; + apply_new_text_length = edit_state.CurLenA; + } + } + + // Copy result to user buffer + if (apply_new_text) + { + IM_ASSERT(apply_new_text_length >= 0); + if (backup_current_text_length != apply_new_text_length && is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + IM_ASSERT(apply_new_text_length <= buf_size); + } + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size)); + value_changed = true; + } + + // Clear temporary user storage + edit_state.UserFlags = 0; + edit_state.UserCallback = NULL; + edit_state.UserCallbackData = NULL; + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render + // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL; + + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line + // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. + // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. + const int buf_display_max_length = 2 * 1024 * 1024; + + if (!is_multiline) + { + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + } + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.f, 0.f); + const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + if (g.ActiveId == id || is_currently_scrolling) + { + edit_state.CursorAnim += io.DeltaTime; + + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = edit_state.TextW.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. + const ImWchar* searches_input_ptr[2]; + searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[1] = NULL; + int searches_remaining = 1; + int searches_result_line_number[2] = { -1, -999 }; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_result_line_number[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bits + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; + if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_number[0] * g.FontSize; + if (searches_result_line_number[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_number[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } + + // Scroll + if (edit_state.CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + } + else + { + edit_state.ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + float scroll_y = draw_window->Scroll.y; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->Scroll.y = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; + } + } + edit_state.CursorFollow = false; + const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + + // Draw selection + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bits + //p = p ? p + 1 : text_selected_end; + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.y += g.FontSize; + } + } + + const int buf_display_len = edit_state.CurLenA; + if (is_multiline || buf_display_len < buf_display_max_length) + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); + + // Draw blinking cursor + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (is_editable) + g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + } + else + { + // Render text only + const char* buf_end = NULL; + if (is_multiline) + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width + else + buf_end = buf_display + strlen(buf_display); + if (is_multiline || (buf_end - buf_display) < buf_display_max_length) + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + if (is_multiline) + { + Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + EndChildFrame(); + EndGroup(); + } + + if (is_password) + PopFont(); + + // Log as text + if (g.LogEnabled && !is_password) + LogRenderedText(&render_pos, buf_display, NULL); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if (value_changed) + MarkItemEdited(id); + + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +//------------------------------------------------------------------------- +// - ColorEdit3() +// - ColorEdit4() +// - ColorPicker3() +// - RenderColorRectWithAlphaCheckerboard() [Internal] +// - ColorPicker4() +// - ColorButton() +// - SetColorEditOptions() +// - ColorTooltip() [Internal] +// - ColorEditOptionsPopup() [Internal] +// - ColorPickerOptionsPopup() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_items_all = CalcItemWidth() - w_extra; + const char* label_display_end = FindRenderedTextEnd(label); + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + const ImGuiColorEditFlags flags_untouched = flags; + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__InputsMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + const char* fmt_table_int[3][4] = + { + { "%3d", "%3d", "%3d", "%3d" }, // Short display + { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA + { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA + }; + const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; + + PushItemWidth(w_item_one); + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + if (n + 1 == components) + PushItemWidth(w_item_last); + if (flags & ImGuiColorEditFlags_Float) + value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + else + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + PopItemWidth(); + PopItemWidth(); + } + else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); + PushItemWidth(w_items_all); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsBlankA(*p)) + p++; + i[0] = i[1] = i[2] = i[3] = 0; + if (alpha) + sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + PopItemWidth(); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + if (!(flags & ImGuiColorEditFlags_NoInputs)) + SameLine(0, style.ItemInnerSpacing.x); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextUnformatted(label, label_display_end); + Spacing(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemWidth(); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + + // Convert back + if (picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + if (value_changed) + { + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); + value_changed = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = true; + } + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + if (value_changed) + MarkItemEdited(window->DC.LastItemId); + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +// Helper for ColorPicker4() +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. +void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); + window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + int rounding_corners_flags_cell = 0; + if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } + rounding_corners_flags_cell &= rounding_corners_flags; + window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + } + } + } + else + { + window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + } +} + +// Helper for ColorPicker4() +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) +{ + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); +} + +// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H,S,V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) + { + // Interactive with Hue wheel + H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + + // R,G,B and H,S,V slider color editor + bool value_changed_fix_hue_wrap = false; + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) + if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB)) + { + // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. + // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) + value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); + value_changed = true; + } + if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); + if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit4 call), if any + if (value_changed_fix_hue_wrap) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); + + const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = ImCos(H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); + draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); + draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); + draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); + draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); + float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + EndGroup(); + + if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) + value_changed = false; + if (value_changed) + MarkItemEdited(window->DC.LastItemId); + + PopID(); + + return value_changed; +} + +// A little colored square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) + { + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + } + RenderNavHighlight(bb, id); + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + + // Drag and Drop Source + // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. + if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextUnformatted("Color"); + EndDragDropSource(); + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + if (pressed) + MarkItemEdited(id); + + return pressed; +} + +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__InputsMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + BeginTooltipEx(0, true); + + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextUnformatted(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + EndTooltip(); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) Separator(); + PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = GetCursorScreenPos(); + if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + SetCursorScreenPos(backup_pos); + ImVec4 dummy_ref_col; + memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); + ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); + PopID(); + } + PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) Separator(); + CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + EndPopup(); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +//------------------------------------------------------------------------- +// - TreeNode() +// - TreeNodeV() +// - TreeNodeEx() +// - TreeNodeExV() +// - TreeNodeBehavior() [Internal] +// - TreePush() +// - TreePop() +// - TreeAdvanceToLabelPos() +// - GetTreeNodeToLabelSpacing() +// - SetNextTreeNodeOpen() +// - CollapsingHeader() +//------------------------------------------------------------------------- + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextTreeNodeOpenCond != 0) + { + if (g.NextTreeNodeOpenCond & ImGuiCond_Always) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.NextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + g.NextTreeNodeOpenCond = 0; + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); + ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); + if (display_frame) + { + // Framed header expand a little outside the default padding + frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; + frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; + } + + const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser + ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) + const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y); + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + + // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + window->DC.LastItemDisplayRect = frame_bb; + + if (!item_add) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; + } + + // Flags that affects opening behavior: + // - 0(default) ..................... single-click anywhere to open + // - OpenOnDoubleClick .............. double-click anywhere to open + // - OpenOnArrow .................... single-click on arrow to open + // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open + ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); + + bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + if (!(flags & ImGuiTreeNodeFlags_Leaf)) + { + bool toggled = false; + if (pressed) + { + toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id); + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover); + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + toggled |= g.IO.MouseDoubleClicked[0]; + if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = false; + } + + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y); + if (display_frame) + { + // Framed type + RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + if (g.LogEnabled) + { + // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. + const char log_prefix[] = "\n##"; + const char log_suffix[] = "##"; + LogRenderedText(&text_pos, log_prefix, log_prefix+3); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); + } + else + { + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } + } + else + { + // Unframed typed for tree nodes + if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) + { + RenderFrame(frame_bb.Min, frame_bb.Max, col, false); + RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); + } + + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); + else if (!(flags & ImGuiTreeNodeFlags_Leaf)) + RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogRenderedText(&text_pos, ">"); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushRawID(ImGuiID id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth))) + { + SetNavID(window->IDStack.back(), g.NavLayer); + NavMoveRequestCancel(); + } + window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1; + + IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. + PopID(); +} + +void ImGui::TreeAdvanceToLabelPos() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextTreeNodeOpenVal = is_open; + g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label); +} + +bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_open && !*p_open) + return false; + + ImGuiID id = window->GetID(label); + bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); + if (p_open) + { + // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + ImGuiContext& g = *GImGui; + ImGuiItemHoveredDataBackup last_item_backup; + float button_radius = g.FontSize * 0.5f; + ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y); + if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), button_center, button_radius)) + *p_open = false; + last_item_backup.Restore(); + } + + return is_open; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Selectable +//------------------------------------------------------------------------- +// - Selectable() +//------------------------------------------------------------------------- + +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. + PopClipRect(); + + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb_inner(pos, pos + size); + ItemSize(bb_inner); + + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding; + float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; + float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); + ImRect bb(pos, pos + size_draw); + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) + bb.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += spacing_R; + bb.Max.y += spacing_D; + if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id)) + { + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + PushColumnClipRect(); + return false; + } + + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick; + if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; + if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets) + if (pressed || hovered) + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + { + g.NavDisableHighlight = true; + SetNavID(id, window->DC.NavLayerCurrent); + } + if (pressed) + MarkItemEdited(id); + + // Render + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + { + PushColumnClipRect(); + bb.Max.x -= (GetContentRegionMax().x - max_x); + } + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ListBox +//------------------------------------------------------------------------- +// - ListBox() +// - ListBoxHeader() +// - ListBoxFooter() +//------------------------------------------------------------------------- + +// FIXME: Rename to BeginListBox() +// Helper to calculate the size of a listbox and display a label on the right. +// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" +bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = GetStyle(); + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. + + BeginGroup(); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +// FIXME: Rename to BeginListBox() +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; + return ListBoxHeader(label, size); +} + +// FIXME: Rename to EndListBox() +void ImGui::ListBoxFooter() +{ + ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; + const ImRect bb = parent_window->DC.LastItemRect; + const ImGuiStyle& style = GetStyle(); + + EndChildFrame(); + + // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) + // We call SameLine() to restore DC.CurrentLine* data + SameLine(); + parent_window->DC.CursorPos = bb.Min; + ItemSize(bb, style.FramePadding.y); + EndGroup(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + if (!ListBoxHeader(label, items_count, height_in_items)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + ImGuiContext& g = *GImGui; + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + ListBoxFooter(); + if (value_changed) + MarkItemEdited(g.CurrentWindow->DC.LastItemId); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: PlotLines, PlotHistogram +//------------------------------------------------------------------------- +// - PlotEx() [Internal] +// - PlotLines() +// - PlotHistogram() +//------------------------------------------------------------------------- + +void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (graph_size.x == 0.0f) + graph_size.x = CalcItemWidth(); + if (graph_size.y == 0.0f) + graph_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return; + const bool hovered = ItemHoverable(inner_bb, 0); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (values_count > 0) + { + int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + int v_hovered = -1; + if (hovered) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + v_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Value helpers +// Those is not very useful, legacy API. +//------------------------------------------------------------------------- +// - Value() +//------------------------------------------------------------------------- + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//------------------------------------------------------------------------- +// [SECTION] MenuItem, BeginMenu, EndMenu, etc. +//------------------------------------------------------------------------- +// - ImGuiMenuColumns [Internal] +// - BeginMainMenuBar() +// - EndMainMenuBar() +// - BeginMenuBar() +// - EndMenuBar() +// - BeginMenu() +// - EndMenu() +// - MenuItem() +//------------------------------------------------------------------------- + +// Helpers for internal use +ImGuiMenuColumns::ImGuiMenuColumns() +{ + Count = 0; + Spacing = Width = NextWidth = 0.0f; + memset(Pos, 0, sizeof(Pos)); + memset(NextWidths, 0, sizeof(NextWidths)); +} + +void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiMenuColumns::CalcExtraSpace(float avail_w) +{ + return ImMax(0.0f, avail_w - Width); +} + +// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + SetNextWindowPos(ImVec2(0.0f, 0.0f)); + SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); + PopStyleVar(2); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + if (!is_open) + { + End(); + return false; + } + return true; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) + FocusPreviousWindowIgnoringOne(g.NavWindow); + + End(); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Backup position on layer 0 + PushID("##menubar"); + + // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); + clip_rect.ClipWith(window->OuterRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.NavLayerCurrent++; + window->DC.NavLayerCurrentMask <<= 1; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) + IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check + FocusWindow(window); + SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]); + g.NavLayer = 1; + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + } + } + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. + window->DC.GroupStack.back().AdvanceCursor = false; + EndGroup(); // Restore position on layer 0 + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.NavLayerCurrent--; + window->DC.NavLayerCurrentMask >>= 1; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + bool pressed; + bool menu_is_open = IsPopupOpen(id); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup). + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); + if (!enabled) PopStyleColor(); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false, want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) + { + if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug + } + } + + want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); + want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); + + if (g.NavActivateId == id) + { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id)) + ClosePopupToLevel(g.CurrentPopupStack.Size); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + flags |= ImGuiWindowFlags_ChildWindow; + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request _within our child menu_ failed, close the menu. + // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. + // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} diff --git a/src/imgui/imstb_rectpack.h b/src/imgui/imstb_rectpack.h new file mode 100644 index 000000000..2b07dcc82 --- /dev/null +++ b/src/imgui/imstb_rectpack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/imgui/imstb_textedit.h b/src/imgui/imstb_textedit.h new file mode 100644 index 000000000..9e12469be --- /dev/null +++ b/src/imgui/imstb_textedit.h @@ -0,0 +1,1409 @@ +// [ImGui] this is a slightly modified version of stb_textedit.h 1.12. Those changes would need to be pushed into nothings/stb +// [ImGui] - 2018-06: fixed undo/redo after pasting large amount of text (over 32 kb). Redo will still fail when undo buffers are exhausted, but text won't be corrupted (see nothings/stb issue #620) +// [ImGui] - 2018-06: fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) +// [ImGui] - fixed some minor warnings + +// stb_textedit.h - v1.12 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// See end of file for license information. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash +// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield +// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// Dan Thompson +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Todo: +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to +// anything other type you wante before including. +// +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + int undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = 0; + + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + if (state->select_start == state->select_end) + state->select_start = state->cursor; + + p = stb_text_locate_coord(str, x, y); + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + i = 0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicity clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +{ + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +#ifndef STB_TEXTEDIT_KEYTYPE +#define STB_TEXTEDIT_KEYTYPE int +#endif + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicity clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str,state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // now find character position down a row + if (find.length) { + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + int start = find.first_char + find.length; + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // can only go up if there's a previous row + if (find.prev_first != find.first_char) { + // now find character position up a row + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + +// @TODO: +// STB_TEXTEDIT_K_PGUP - move cursor up a page +// STB_TEXTEDIT_K_PGDOWN - move cursor down a page + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point -= n; + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // move the remaining redo character data to the end of the buffer + state->redo_char_point += n; + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + // adjust the position of all the other records to account for above memmove + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage += n; + } + // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + // now move redo_point to point to the new one + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point += insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif//STB_TEXTEDIT_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/imgui/imstb_truetype.h b/src/imgui/imstb_truetype.h new file mode 100644 index 000000000..f65deb503 --- /dev/null +++ b/src/imgui/imstb_truetype.h @@ -0,0 +1,4854 @@ +// stb_truetype.h - v1.19 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index f81355012..1faf542dd 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.0) project(Libnest2D) @@ -11,125 +11,112 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED) # Add our own cmake module path. -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/) +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/) option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded and the provided unit tests will be included in the build." OFF) option(LIBNEST2D_BUILD_EXAMPLES "If enabled, examples will be built." OFF) -set(LIBNEST2D_GEOMETRIES_BACKEND "clipper" CACHE STRING - "Build libnest2d with geometry classes implemented by the chosen backend.") +option(LIBNEST2D_HEADER_ONLY "If enabled static library will not be built." ON) -set(LIBNEST2D_OPTIMIZER_BACKEND "nlopt" CACHE STRING - "Build libnest2d with optimization features implemented by the chosen backend.") +set(GEOMETRY_BACKENDS clipper boost eigen) +set(LIBNEST2D_GEOMETRIES clipper CACHE STRING "Geometry backend") +set_property(CACHE LIBNEST2D_GEOMETRIES PROPERTY STRINGS ${GEOMETRY_BACKENDS}) +list(FIND GEOMETRY_BACKENDS ${LIBNEST2D_GEOMETRIES} GEOMETRY_TYPE) +if(${GEOMETRY_TYPE} EQUAL -1) + message(FATAL_ERROR "Option ${LIBNEST2D_GEOMETRIES} not supported, valid entries are ${GEOMETRY_BACKENDS}") +endif() + +set(OPTIMIZERS nlopt optimlib) +set(LIBNEST2D_OPTIMIZER nlopt CACHE STRING "Optimization backend") +set_property(CACHE LIBNEST2D_OPTIMIZER PROPERTY STRINGS ${OPTIMIZERS}) +list(FIND OPTIMIZERS ${LIBNEST2D_OPTIMIZER} OPTIMIZER_TYPE) +if(${OPTIMIZER_TYPE} EQUAL -1) + message(FATAL_ERROR "Option ${LIBNEST2D_OPTIMIZER} not supported, valid entries are ${OPTIMIZERS}") +endif() + +add_library(libnest2d INTERFACE) + +set(SRC_DIR ${PROJECT_SOURCE_DIR}/include) set(LIBNEST2D_SRCFILES - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/libnest2d.hpp # Templates only - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d.h # Exports ready made types using template arguments - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits_nfp.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/selections/selection_boilerplate.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/selections/filler.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/selections/firstfit.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/selections/djd_heuristic.hpp + ${SRC_DIR}/libnest2d/libnest2d.hpp # Templates only + ${SRC_DIR}/libnest2d/geometry_traits.hpp + ${SRC_DIR}/libnest2d/geometry_traits_nfp.hpp + ${SRC_DIR}/libnest2d/common.hpp + ${SRC_DIR}/libnest2d/optimizer.hpp + ${SRC_DIR}/libnest2d/utils/metaloop.hpp + ${SRC_DIR}/libnest2d/utils/rotfinder.hpp + ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp + ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp + ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp + ${SRC_DIR}/libnest2d/selections/selection_boilerplate.hpp + ${SRC_DIR}/libnest2d/selections/filler.hpp + ${SRC_DIR}/libnest2d/selections/firstfit.hpp + ${SRC_DIR}/libnest2d/selections/djd_heuristic.hpp ) -set(LIBNEST2D_LIBRARIES "") - -set(LIBNEST2D_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}) - -if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper") - - # Clipper backend is not enough on its own, it still needs some functions - # from Boost geometry - if(NOT Boost_INCLUDE_DIRS_FOUND) - find_package(Boost 1.58 REQUIRED) - # TODO automatic download of boost geometry headers +set(TBB_STATIC ON) +find_package(TBB QUIET) +if(TBB_FOUND) + message(STATUS "Parallelization with Intel TBB") + target_include_directories(libnest2d INTERFACE ${TBB_INCLUDE_DIRS}) + target_compile_definitions(libnest2d INTERFACE ${TBB_DEFINITIONS} -DUSE_TBB) + if(MSVC) + # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. + target_compile_definitions(libnest2d INTERFACE -D__TBB_NO_IMPLICIT_LINKAGE) endif() + # The Intel TBB library will use the std::exception_ptr feature of C++11. + target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) - add_subdirectory(libnest2d/clipper_backend) + target_link_libraries(libnest2d INTERFACE tbb) +else() + find_package(OpenMP QUIET) - include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS}) - include_directories(${Boost_INCLUDE_DIRS}) - - list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp) - list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES}) - list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS} - ${Boost_INCLUDE_DIRS_FOUND}) + if(OpenMP_CXX_FOUND) + message(STATUS "Parallelization with OpenMP") + target_include_directories(libnest2d INTERFACE OpenMP::OpenMP_CXX) + target_link_libraries(libnest2d INTERFACE OpenMP::OpenMP_CXX) + else() + message("Parallelization with C++11 threads") + find_package(Threads REQUIRED) + target_link_libraries(libnest2d INTERFACE Threads::Threads) + endif() endif() -if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt") - find_package(NLopt 1.4) - if(NOT NLopt_FOUND) - message(STATUS "NLopt not found so downloading " - "and automatic build is performed...") - include(DownloadNLopt) - endif() - find_package(Threads REQUIRED) +add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) +add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) - list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/simplex.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp) - list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS}) - list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR}) -endif() +#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) +target_include_directories(libnest2d INTERFACE ${SRC_DIR}) -if(LIBNEST2D_UNITTESTS) - enable_testing() - add_subdirectory(tests) +if(NOT LIBNEST2D_HEADER_ONLY) + set(LIBNAME libnest2d_${LIBNEST2D_GEOMETRIES}_${LIBNEST2D_OPTIMIZER}) + add_library(${LIBNAME} ${PROJECT_SOURCE_DIR}/src/libnest2d.cpp) + target_link_libraries(${LIBNAME} PUBLIC libnest2d) + target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC) endif() if(LIBNEST2D_BUILD_EXAMPLES) - add_executable(example examples/main.cpp -# tools/libnfpglue.hpp -# tools/libnfpglue.cpp - tools/nfp_svgnest.hpp - tools/nfp_svgnest_glue.hpp - tools/svgtools.hpp - tests/printer_parts.cpp - tests/printer_parts.h - ${LIBNEST2D_SRCFILES} - ) - set(TBB_STATIC ON) - find_package(TBB QUIET) - if(TBB_FOUND) - message(STATUS "Parallelization with Intel TBB") - target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS}) - target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB) - if(MSVC) - # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. - target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE) - endif() - # The Intel TBB library will use the std::exception_ptr feature of C++11. - target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1) + add_executable(example examples/main.cpp + # tools/libnfpglue.hpp + # tools/libnfpglue.cpp + tools/nfp_svgnest.hpp + tools/nfp_svgnest_glue.hpp + tools/svgtools.hpp + tests/printer_parts.cpp + tests/printer_parts.h + ) - target_link_libraries(example ${TBB_LIBRARIES}) - else() - find_package(OpenMP QUIET) - if(OpenMP_CXX_FOUND) - message(STATUS "Parallelization with OpenMP") - target_include_directories(example PUBLIC OpenMP::OpenMP_CXX) - target_link_libraries(example OpenMP::OpenMP_CXX) - endif() - endif() - - target_link_libraries(example ${LIBNEST2D_LIBRARIES}) - target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS}) + if(NOT LIBNEST2D_HEADER_ONLY) + target_link_libraries(example ${LIBNAME}) + else() + target_link_libraries(example libnest2d) + endif() endif() -get_directory_property(hasParent PARENT_DIRECTORY) -if(hasParent) - set(LIBNEST2D_INCLUDES ${LIBNEST2D_HEADERS} PARENT_SCOPE) - set(LIBNEST2D_LIBRARIES ${LIBNEST2D_LIBRARIES} PARENT_SCOPE) +if(LIBNEST2D_UNITTESTS) + add_subdirectory(${PROJECT_SOURCE_DIR}/tests) endif() diff --git a/src/libnest2d/README.md b/src/libnest2d/README.md deleted file mode 100644 index 61a7ac7d0..000000000 --- a/src/libnest2d/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Introduction - -Libnest2D is a library and framework for the 2D bin packaging problem. -Inspired from the [SVGNest](svgnest.com) Javascript library the project is -built from scratch in C++11. The library is written with a policy that it should -be usable out of the box with a very simple interface but has to be customizable -to the very core as well. The algorithms are defined in a header only fashion -with templated geometry types. These geometries can have custom or already -existing implementation to avoid copying or having unnecessary dependencies. - -A default backend is provided if the user of the library just wants to use it -out of the box without additional integration. This backend is reasonably -fast and robust, being built on top of boost geometry and the -[polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of -this default backend implies the dependency on these packages but its header -only as well. - -This software is currently under construction and lacks a throughout -documentation and some essential algorithms as well. At this stage it works well -for rectangles and convex closed polygons without considering holes and -concavities. - -Holes and non-convex polygons will be usable in the near future as well. The -no fit polygon based placer module combined with the first fit selection -strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r) -application's arrangement feature. It uses local optimization techniques to find -the best placement of each new item based on some features of the arrangement. - -In the near future I would like to use machine learning to evaluate the -placements and (or) the order if items in which they are placed and see what -results can be obtained. This is a different approach than that of SVGnest which -uses genetic algorithms to find better and better selection orders. Maybe the -two approaches can be combined as well. - -# References -- [SVGNest](https://github.com/Jack000/SVGnest) -- [An effective heuristic for the two-dimensional irregular -bin packing problem](http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf) -- [Complete and robust no-fit polygon generation for the irregular stock cutting problem](https://www.sciencedirect.com/science/article/abs/pii/S0377221706001639) -- [Applying Meta-Heuristic Algorithms to the Nesting -Problem Utilising the No Fit Polygon](http://www.graham-kendall.com/papers/k2001.pdf) -- [A comprehensive and robust procedure for obtaining the nofit polygon -using Minkowski sums](https://www.sciencedirect.com/science/article/pii/S0305054806000669) \ No newline at end of file diff --git a/src/libnest2d/cmake_modules/DownloadNLopt.cmake b/src/libnest2d/cmake_modules/DownloadNLopt.cmake index 0f5392596..62b2b4c1a 100644 --- a/src/libnest2d/cmake_modules/DownloadNLopt.cmake +++ b/src/libnest2d/cmake_modules/DownloadNLopt.cmake @@ -6,11 +6,14 @@ else() set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") endif() +set(URL_NLOPT "https://github.com/stevengj/nlopt.git" + CACHE STRING "Location of the nlopt git repository") + # set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt) include(DownloadProject) download_project( PROJ nlopt - GIT_REPOSITORY https://github.com/stevengj/nlopt.git - GIT_TAG 1fcbcbf2fe8e34234e016cc43a6c41d3e8453e1f #master #nlopt-2.4.2 + GIT_REPOSITORY ${URL_NLOPT} + GIT_TAG v2.5.0 # CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR} ${UPDATE_DISCONNECTED_IF_AVAILABLE} ) diff --git a/src/libnest2d/cmake_modules/FindClipper.cmake b/src/libnest2d/cmake_modules/FindClipper.cmake index f6b973440..01b6b99d5 100644 --- a/src/libnest2d/cmake_modules/FindClipper.cmake +++ b/src/libnest2d/cmake_modules/FindClipper.cmake @@ -47,4 +47,12 @@ FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper MARK_AS_ADVANCED( CLIPPER_INCLUDE_DIRS - CLIPPER_LIBRARIES) \ No newline at end of file + CLIPPER_LIBRARIES) + +if(CLIPPER_FOUND) + add_library(Clipper::Clipper INTERFACE IMPORTED) + set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_LINK_LIBRARIES ${CLIPPER_LIBRARIES}) + set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CLIPPER_INCLUDE_DIRS}) + #target_link_libraries(Clipper::Clipper INTERFACE ${CLIPPER_LIBRARIES}) + #target_include_directories(Clipper::Clipper INTERFACE ${CLIPPER_INCLUDE_DIRS}) +endif() diff --git a/src/libnest2d/cmake_modules/FindNLopt.cmake b/src/libnest2d/cmake_modules/FindNLopt.cmake index 4b93be7b6..2f813b6aa 100644 --- a/src/libnest2d/cmake_modules/FindNLopt.cmake +++ b/src/libnest2d/cmake_modules/FindNLopt.cmake @@ -114,6 +114,13 @@ if(NLopt_FOUND) message(STATUS "Found NLopt in '${NLopt_DIR}'.") message(STATUS "Using NLopt include directory '${NLopt_INCLUDE_DIR}'.") message(STATUS "Using NLopt library '${NLopt_LIBS}'.") + add_library(Nlopt::Nlopt INTERFACE IMPORTED) + set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS}) + set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR}) + set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}") + # target_link_libraries(Nlopt::Nlopt INTERFACE ${NLopt_LIBS}) + # target_include_directories(Nlopt::Nlopt INTERFACE ${NLopt_INCLUDE_DIR}) + # target_compile_definitions(Nlopt::Nlopt INTERFACE ${NLopt_DEFINITIONS}) else() if(NLopt_FIND_REQUIRED) message(FATAL_ERROR "Unable to find requested NLopt installation:${NLopt_ERROR_REASON}") @@ -122,4 +129,4 @@ else() message(STATUS "NLopt was not found:${NLopt_ERROR_REASON}") endif() endif() -endif() \ No newline at end of file +endif() diff --git a/src/libnest2d/cmake_modules/FindTBB.cmake b/src/libnest2d/cmake_modules/FindTBB.cmake deleted file mode 100644 index 8b498d3ab..000000000 --- a/src/libnest2d/cmake_modules/FindTBB.cmake +++ /dev/null @@ -1,322 +0,0 @@ -# The MIT License (MIT) -# -# Copyright (c) 2015 Justus Calvin -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# -# FindTBB -# ------- -# -# Find TBB include directories and libraries. -# -# Usage: -# -# find_package(TBB [major[.minor]] [EXACT] -# [QUIET] [REQUIRED] -# [[COMPONENTS] [components...]] -# [OPTIONAL_COMPONENTS components...]) -# -# where the allowed components are tbbmalloc and tbb_preview. Users may modify -# the behavior of this module with the following variables: -# -# * TBB_ROOT_DIR - The base directory the of TBB installation. -# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. -# * TBB_LIBRARY - The directory that contains the TBB library files. -# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. -# These libraries, if specified, override the -# corresponding library search results, where -# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, -# tbb_preview, or tbb_preview_debug. -# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will -# be used instead of the release version. -# * TBB_STATIC - Static linking of libraries with a _static suffix. -# For example, on Windows a tbb_static.lib will be searched for -# instead of tbb.lib. -# -# Users may modify the behavior of this module with the following environment -# variables: -# -# * TBB_INSTALL_DIR -# * TBBROOT -# * LIBRARY_PATH -# -# This module will set the following variables: -# -# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or -# don’t want to use TBB. -# * TBB__FOUND - If False, optional part of TBB sytem is -# not available. -# * TBB_VERSION - The full version string -# * TBB_VERSION_MAJOR - The major version -# * TBB_VERSION_MINOR - The minor version -# * TBB_INTERFACE_VERSION - The interface version number defined in -# tbb/tbb_stddef.h. -# * TBB__LIBRARY_RELEASE - The path of the TBB release version of -# , where may be tbb, tbb_debug, -# tbbmalloc, tbbmalloc_debug, tbb_preview, or -# tbb_preview_debug. -# * TBB__LIBRARY_DEGUG - The path of the TBB release version of -# , where may be tbb, tbb_debug, -# tbbmalloc, tbbmalloc_debug, tbb_preview, or -# tbb_preview_debug. -# -# The following varibles should be used to build and link with TBB: -# -# * TBB_INCLUDE_DIRS - The include directory for TBB. -# * TBB_LIBRARIES - The libraries to link against to use TBB. -# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. -# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. -# * TBB_DEFINITIONS - Definitions to use when compiling code that uses -# TBB. -# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that -# uses TBB. -# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that -# uses TBB. -# -# This module will also create the "tbb" target that may be used when building -# executables and libraries. - -include(FindPackageHandleStandardArgs) - -if(NOT TBB_FOUND) - - ################################## - # Check the build type - ################################## - - if(NOT DEFINED TBB_USE_DEBUG_BUILD) - if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") - set(TBB_BUILD_TYPE DEBUG) - else() - set(TBB_BUILD_TYPE RELEASE) - endif() - elseif(TBB_USE_DEBUG_BUILD) - set(TBB_BUILD_TYPE DEBUG) - else() - set(TBB_BUILD_TYPE RELEASE) - endif() - - ################################## - # Set the TBB search directories - ################################## - - # Define search paths based on user input and environment variables - set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) - - # Define the search directories based on the current platform - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" - "C:/Program Files (x86)/Intel/TBB") - - # Set the target architecture - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(TBB_ARCHITECTURE "intel64") - else() - set(TBB_ARCHITECTURE "ia32") - endif() - - # Set the TBB search library path search suffix based on the version of VC - if(WINDOWS_STORE) - set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") - elseif(MSVC14) - set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") - elseif(MSVC12) - set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") - elseif(MSVC11) - set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") - elseif(MSVC10) - set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") - endif() - - # Add the library path search suffix for the VC independent version of TBB - list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") - - elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # OS X - set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") - - # TODO: Check to see which C++ library is being used by the compiler. - if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) - # The default C++ library on OS X 10.9 and later is libc++ - set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") - else() - set(TBB_LIB_PATH_SUFFIX "lib") - endif() - elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - # Linux - set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") - - # TODO: Check compiler version to see the suffix should be /gcc4.1 or - # /gcc4.1. For now, assume that the compiler is more recent than - # gcc 4.4.x or later. - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") - set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") - endif() - endif() - - ################################## - # Find the TBB include dir - ################################## - - find_path(TBB_INCLUDE_DIRS tbb/tbb.h - HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} - PATHS ${TBB_DEFAULT_SEARCH_DIR} - PATH_SUFFIXES include) - - ################################## - # Set version strings - ################################## - - if(TBB_INCLUDE_DIRS) - file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) - string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" - TBB_VERSION_MAJOR "${_tbb_version_file}") - string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" - TBB_VERSION_MINOR "${_tbb_version_file}") - string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" - TBB_INTERFACE_VERSION "${_tbb_version_file}") - set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") - endif() - - ################################## - # Find TBB components - ################################## - - if(TBB_VERSION VERSION_LESS 4.3) - set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) - else() - set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) - endif() - - if(TBB_STATIC) - set(TBB_STATIC_SUFFIX "_static") - endif() - - # Find each component - foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) - if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") - - # Search for the libraries - find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} - HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} - PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH - PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) - - find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug - HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} - PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH - PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) - - if(TBB_${_comp}_LIBRARY_DEBUG) - list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") - endif() - if(TBB_${_comp}_LIBRARY_RELEASE) - list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") - endif() - if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) - set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") - endif() - - if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") - set(TBB_${_comp}_FOUND TRUE) - else() - set(TBB_${_comp}_FOUND FALSE) - endif() - - # Mark internal variables as advanced - mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) - mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) - mark_as_advanced(TBB_${_comp}_LIBRARY) - - endif() - endforeach() - - unset(TBB_STATIC_SUFFIX) - - ################################## - # Set compile flags and libraries - ################################## - - set(TBB_DEFINITIONS_RELEASE "") - set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") - - if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") - set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") - elseif(TBB_LIBRARIES_RELEASE) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") - set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") - elseif(TBB_LIBRARIES_DEBUG) - set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") - set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") - endif() - - find_package_handle_standard_args(TBB - REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES - HANDLE_COMPONENTS - VERSION_VAR TBB_VERSION) - - ################################## - # Create targets - ################################## - - if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) - add_library(tbb SHARED IMPORTED) - set_target_properties(tbb PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} - IMPORTED_LOCATION ${TBB_LIBRARIES}) - if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) - set_target_properties(tbb PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" - IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} - IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} - IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} - IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} - ) - elseif(TBB_LIBRARIES_RELEASE) - set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) - else() - set_target_properties(tbb PROPERTIES - INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" - IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} - ) - endif() - endif() - - mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) - - unset(TBB_ARCHITECTURE) - unset(TBB_BUILD_TYPE) - unset(TBB_LIB_PATH_SUFFIX) - unset(TBB_DEFAULT_SEARCH_DIR) - - if(TBB_DEBUG) - message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") - message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") - message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") - message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") - message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") - message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") - message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") - endif() - -endif() diff --git a/src/libnest2d/examples/main.cpp b/src/libnest2d/examples/main.cpp deleted file mode 100644 index ebc3fb15c..000000000 --- a/src/libnest2d/examples/main.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include -#include -#include -//#define DEBUG_EXPORT_NFP - -#include - -#include "tests/printer_parts.h" -#include "tools/benchmark.h" -#include "tools/svgtools.hpp" -#include "libnest2d/rotfinder.hpp" - -//#include "tools/libnfpglue.hpp" -//#include "tools/nfp_svgnest_glue.hpp" - - -using namespace libnest2d; -using ItemGroup = std::vector>; - -std::vector& _parts(std::vector& ret, const TestData& data) -{ - if(ret.empty()) { - ret.reserve(data.size()); - for(auto& inp : data) - ret.emplace_back(inp); - } - - return ret; -} - -std::vector& prusaParts() { - static std::vector ret; - return _parts(ret, PRINTER_PART_POLYGONS); -} - -std::vector& stegoParts() { - static std::vector ret; - return _parts(ret, STEGOSAUR_POLYGONS); -} - -std::vector& prusaExParts() { - static std::vector ret; - if(ret.empty()) { - ret.reserve(PRINTER_PART_POLYGONS_EX.size()); - for(auto& p : PRINTER_PART_POLYGONS_EX) { - ret.emplace_back(p.Contour, p.Holes); - } - } - return ret; -} - -void arrangeRectangles() { - using namespace libnest2d; - - const int SCALE = 1000000; - - std::vector rects(202, { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190019}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802209}, - {6691309, -11542349}, - {5877850, -12201069}, - {5000000, -12771149}, - {4067369, -13246350}, - {3090169, -13621459}, - {2079119, -13892379}, - {1045279, -14056119}, - {0, -14110899}, - {-1045279, -14056119}, - {-2079119, -13892379}, - {-3090169, -13621459}, - {-4067369, -13246350}, - {-5000000, -12771149}, - {-5877850, -12201069}, - {-6691309, -11542349}, - {-7431449, -10802209}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190019}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }); - - std::vector input; - input.insert(input.end(), prusaParts().begin(), prusaParts().end()); -// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); -// input.insert(input.end(), stegoParts().begin(), stegoParts().end()); -// input.insert(input.end(), rects.begin(), rects.end()); - - Box bin(250*SCALE, 210*SCALE); -// PolygonImpl bin = { -// { -// {25*SCALE, 0}, -// {0, 25*SCALE}, -// {0, 225*SCALE}, -// {25*SCALE, 250*SCALE}, -// {225*SCALE, 250*SCALE}, -// {250*SCALE, 225*SCALE}, -// {250*SCALE, 25*SCALE}, -// {225*SCALE, 0}, -// {25*SCALE, 0} -// }, -// {} -// }; - -// Circle bin({0, 0}, 125*SCALE); - - auto min_obj_distance = static_cast(6*SCALE); - - using Placer = placers::_NofitPolyPlacer; - using Packer = Nester; - - Packer arrange(bin, min_obj_distance); - - Packer::PlacementConfig pconf; - pconf.alignment = Placer::Config::Alignment::CENTER; - pconf.starting_point = Placer::Config::Alignment::CENTER; - pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 0.65f; - pconf.parallel = true; - - Packer::SelectionConfig sconf; -// sconf.allow_parallel = false; -// sconf.force_parallel = false; -// sconf.try_triplets = true; -// sconf.try_reverse_order = true; -// sconf.waste_increment = 0.01; - - arrange.configure(pconf, sconf); - - arrange.progressIndicator([&](unsigned r){ - std::cout << "Remaining items: " << r << std::endl; - }); - -// findMinimumBoundingBoxRotations(input.begin(), input.end()); - - Benchmark bench; - - bench.start(); - Packer::ResultType result; - - try { - result = arrange.execute(input.begin(), input.end()); - } catch(GeometryException& ge) { - std::cerr << "Geometry error: " << ge.what() << std::endl; - return ; - } catch(std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; - return ; - } - - bench.stop(); - - std::vector eff; - eff.reserve(result.size()); - - auto bin_area = sl::area(bin); - for(auto& r : result) { - double a = 0; - std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); - eff.emplace_back(a/bin_area); - }; - - std::cout << bench.getElapsedSec() << " bin count: " << result.size() - << std::endl; - - std::cout << "Bin efficiency: ("; - for(double e : eff) std::cout << e*100.0 << "% "; - std::cout << ") Average: " - << std::accumulate(eff.begin(), eff.end(), 0.0)*100.0/result.size() - << " %" << std::endl; - - std::cout << "Bin usage: ("; - size_t total = 0; - for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } - std::cout << ") Total: " << total << std::endl; - -// for(auto& it : input) { -// auto ret = sl::isValid(it.transformedShape()); -// std::cout << ret.second << std::endl; -// } - - if(total != input.size()) std::cout << "ERROR " << "could not pack " - << input.size() - total << " elements!" - << std::endl; - - using SVGWriter = svg::SVGWriter; - - SVGWriter::Config conf; - conf.mm_in_coord_units = SCALE; - SVGWriter svgw(conf); - svgw.setSize(Box(250*SCALE, 210*SCALE)); - svgw.writePackGroup(result); - svgw.save("out"); -} - -int main(void /*int argc, char **argv*/) { - arrangeRectangles(); - return EXIT_SUCCESS; -} diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h new file mode 100644 index 000000000..4ad752421 --- /dev/null +++ b/src/libnest2d/include/libnest2d.h @@ -0,0 +1,175 @@ +#ifndef LIBNEST2D_H +#define LIBNEST2D_H + +// The type of backend should be set conditionally by the cmake configuriation +// for now we set it statically to clipper backend +#ifdef LIBNEST2D_BACKEND_CLIPPER +#include +#endif + +#ifdef LIBNEST2D_OPTIMIZER_NLOPT +// We include the stock optimizers for local and global optimization +#include // Local subplex for NfpPlacer +#include // Genetic for min. bounding box +#endif + +#include +#include +#include +#include +#include +#include + +namespace libnest2d { + +using Point = PointImpl; +using Coord = TCoord; +using Box = _Box; +using Segment = _Segment; +using Circle = _Circle; + +using Item = _Item; +using Rectangle = _Rectangle; + +using PackGroup = _PackGroup; +using IndexedPackGroup = _IndexedPackGroup; + +using FillerSelection = selections::_FillerSelection; +using FirstFitSelection = selections::_FirstFitSelection; +using DJDHeuristic = selections::_DJDHeuristic; + +template // Generic placer for arbitrary bin types +using _NfpPlacer = placers::_NofitPolyPlacer; + +// NfpPlacer is with Box bin +using NfpPlacer = _NfpPlacer; + +// This supports only box shaped bins +using BottomLeftPlacer = placers::_BottomLeftPlacer; + +template::iterator> +PackGroup nest(Iterator from, Iterator to, + const typename Placer::BinType& bin, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + Nester nester(bin, dist, pconf, sconf); + return nester.execute(from, to); +} + +template> +PackGroup nest(Container&& cont, + const typename Placer::BinType& bin, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + return nest(cont.begin(), cont.end(), + bin, dist, pconf, sconf); +} + +template::iterator> +PackGroup nest(Iterator from, Iterator to, + const typename Placer::BinType& bin, + ProgressFunction prg, + StopCondition scond = []() { return false; }, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + Nester nester(bin, dist, pconf, sconf); + if(prg) nester.progressIndicator(prg); + if(scond) nester.stopCondition(scond); + return nester.execute(from, to); +} + +template> +PackGroup nest(Container&& cont, + const typename Placer::BinType& bin, + ProgressFunction prg, + StopCondition scond = []() { return false; }, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + return nest(cont.begin(), cont.end(), + bin, prg, scond, dist, pconf, sconf); +} + +#ifdef LIBNEST2D_STATIC +extern template +PackGroup nest&>( + std::vector& cont, + const Box& bin, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +extern template +PackGroup nest&>( + std::vector& cont, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +extern template +PackGroup nest>( + std::vector&& cont, + const Box& bin, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +extern template +PackGroup nest>( + std::vector&& cont, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +extern template +PackGroup nest::iterator>( + std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +extern template +PackGroup nest::iterator>( + std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist, + const NfpPlacer::Config& pcfg, + const FirstFitSelection::Config& scfg +); + +#endif + +} + +#endif // LIBNEST2D_H diff --git a/src/libnest2d/libnest2d/clipper_backend/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt similarity index 55% rename from src/libnest2d/libnest2d/clipper_backend/CMakeLists.txt rename to src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index b6f2de439..aa53f957e 100644 --- a/src/libnest2d/libnest2d/clipper_backend/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -5,6 +5,10 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a if(NOT CLIPPER_FOUND) find_package(Subversion QUIET) if(Subversion_FOUND) + + set(URL_CLIPPER "https://svn.code.sf.net/p/polyclipping/code/trunk/cpp" + CACHE STRING "Clipper source code repository location.") + message(STATUS "Clipper not found so it will be downloaded.") # Silently download and build the library in the build dir @@ -16,7 +20,7 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a include(DownloadProject) download_project( PROJ clipper_library - SVN_REPOSITORY https://svn.code.sf.net/p/polyclipping/code/trunk/cpp + SVN_REPOSITORY ${URL_CLIPPER} SVN_REVISION -r540 #SOURCE_SUBDIR cpp INSTALL_COMMAND "" @@ -29,20 +33,41 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a # ${clipper_library_BINARY_DIR} # ) - add_library(clipper_lib STATIC + add_library(ClipperBackend STATIC ${clipper_library_SOURCE_DIR}/clipper.cpp ${clipper_library_SOURCE_DIR}/clipper.hpp) - set(CLIPPER_INCLUDE_DIRS ${clipper_library_SOURCE_DIR} - PARENT_SCOPE) - - set(CLIPPER_LIBRARIES clipper_lib PARENT_SCOPE) - + target_include_directories(ClipperBackend INTERFACE + ${clipper_library_SOURCE_DIR}) else() message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") endif() + else() + add_library(ClipperBackend INTERFACE) + target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) endif() else() - set(CLIPPER_LIBRARIES clipper PARENT_SCOPE) + # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) + # set(CLIPPER_LIBRARIES clipper PARENT_SCOPE) + add_library(ClipperBackend INTERFACE) + target_link_libraries(ClipperBackend INTERFACE clipper) endif() + +# Clipper backend is not enough on its own, it still needs some functions +# from Boost geometry +if(NOT Boost_INCLUDE_DIRS_FOUND) + find_package(Boost 1.58 REQUIRED) + # TODO automatic download of boost geometry headers +endif() + +target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} ) +#target_sources(ClipperBackend INTERFACE +# ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp +# ${SRC_DIR}/libnest2d/utils/boost_alg.hpp ) + +target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) + +# And finally plug the ClipperBackend into libnest2d +target_link_libraries(libnest2d INTERFACE ClipperBackend) + diff --git a/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp similarity index 95% rename from src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp rename to src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 745fd2108..c05d08d0d 100644 --- a/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -7,8 +7,8 @@ #include #include -#include "../geometry_traits.hpp" -#include "../geometry_traits_nfp.hpp" +#include +#include #include @@ -99,6 +99,10 @@ template<> struct PointType { using Type = PointImpl; }; +template<> struct PointType { + using Type = PointImpl; +}; + template<> struct PointType { using Type = PointImpl; }; @@ -108,6 +112,7 @@ template<> struct CountourType { }; template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PathTag; }; template<> struct ShapeTag> { using Type = MultiPolygonTag; @@ -185,11 +190,6 @@ inline double area(const PolygonImpl& sh) { namespace shapelike { -template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) -{ - return sh.Contour.reserve(vertex_capacity); -} - // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline double area(const PolygonImpl& sh, const PolygonTag&) { @@ -327,13 +327,13 @@ template<> inline THolesContainer& holes(PolygonImpl& sh) } template<> -inline TContour& getHole(PolygonImpl& sh, unsigned long idx) +inline TContour& hole(PolygonImpl& sh, unsigned long idx) { return sh.Holes[idx]; } template<> -inline const TContour& getHole(const PolygonImpl& sh, +inline const TContour& hole(const PolygonImpl& sh, unsigned long idx) { return sh.Holes[idx]; @@ -344,13 +344,13 @@ template<> inline size_t holeCount(const PolygonImpl& sh) return sh.Holes.size(); } -template<> inline PathImpl& getContour(PolygonImpl& sh) +template<> inline PathImpl& contour(PolygonImpl& sh) { return sh.Contour; } template<> -inline const PathImpl& getContour(const PolygonImpl& sh) +inline const PathImpl& contour(const PolygonImpl& sh) { return sh.Contour; } @@ -455,6 +455,6 @@ merge(const std::vector& shapes) //#define DISABLE_BOOST_UNSERIALIZE // All other operators and algorithms are implemented with boost -#include "../boost_alg.hpp" +#include #endif // CLIPPER_BACKEND_HPP diff --git a/src/libnest2d/libnest2d/common.hpp b/src/libnest2d/include/libnest2d/common.hpp similarity index 100% rename from src/libnest2d/libnest2d/common.hpp rename to src/libnest2d/include/libnest2d/common.hpp diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp new file mode 100644 index 000000000..828044afe --- /dev/null +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -0,0 +1,965 @@ +#ifndef GEOMETRY_TRAITS_HPP +#define GEOMETRY_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.hpp" + +namespace libnest2d { + +/// Getting the coordinate data type for a geometry class. +template struct CoordType { using Type = long; }; + +/// TCoord as shorthand for typename `CoordType::Type`. +template +using TCoord = typename CoordType>::Type; + + +/// Getting the type of point structure used by a shape. +template struct PointType { using Type = typename Sh::PointType; }; + +/// TPoint as shorthand for `typename PointType::Type`. +template +using TPoint = typename PointType>::Type; + + +template struct CountourType { using Type = RawShape; }; + +template +using TContour = typename CountourType>::Type; + + +template +struct HolesContainer { using Type = std::vector>; }; + +template +using THolesContainer = typename HolesContainer>::Type; + + +template +struct LastPointIsFirst { static const bool Value = true; }; + +enum class Orientation { + CLOCKWISE, + COUNTER_CLOCKWISE +}; + +template +struct OrientationType { + + // Default Polygon orientation that the library expects + static const Orientation Value = Orientation::CLOCKWISE; +}; + +/** + * \brief A point pair base class for other point pairs (segment, box, ...). + * \tparam RawPoint The actual point type to use. + */ +template +struct PointPair { + RawPoint p1; + RawPoint p2; +}; + +struct PolygonTag {}; +struct PathTag {}; +struct MultiPolygonTag {}; +struct BoxTag {}; +struct CircleTag {}; + +template struct ShapeTag { using Type = typename Shape::Tag; }; +template using Tag = typename ShapeTag>::Type; + +template struct MultiShape { using Type = std::vector; }; +template +using TMultiShape =typename MultiShape>::Type; + +/** + * \brief An abstraction of a box; + */ +template +class _Box: PointPair { + using PointPair::p1; + using PointPair::p2; +public: + + using Tag = BoxTag; + using PointType = RawPoint; + + inline _Box() = default; + inline _Box(const RawPoint& p, const RawPoint& pp): + PointPair({p, pp}) {} + + inline _Box(TCoord width, TCoord height): + _Box(RawPoint{0, 0}, RawPoint{width, height}) {} + + inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; } + inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; } + + inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; } + inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; } + + inline TCoord width() const BP2D_NOEXCEPT; + inline TCoord height() const BP2D_NOEXCEPT; + + inline RawPoint center() const BP2D_NOEXCEPT; + + inline double area() const BP2D_NOEXCEPT { + return double(width()*height()); + } +}; + +template +class _Circle { + RawPoint center_; + double radius_ = 0; +public: + + using Tag = CircleTag; + using PointType = RawPoint; + + _Circle() = default; + + _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} + + inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } + inline const void center(const RawPoint& c) { center_ = c; } + + inline double radius() const BP2D_NOEXCEPT { return radius_; } + inline void radius(double r) { radius_ = r; } + + inline double area() const BP2D_NOEXCEPT { + return 2.0*Pi*radius_*radius_; + } +}; + +/** + * \brief An abstraction of a directed line segment with two points. + */ +template +class _Segment: PointPair { + using PointPair::p1; + using PointPair::p2; + mutable Radians angletox_ = std::nan(""); +public: + + using PointType = RawPoint; + + inline _Segment() = default; + + inline _Segment(const RawPoint& p, const RawPoint& pp): + PointPair({p, pp}) {} + + /** + * @brief Get the first point. + * @return Returns the starting point. + */ + inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } + + /** + * @brief The end point. + * @return Returns the end point of the segment. + */ + inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } + + inline void first(const RawPoint& p) BP2D_NOEXCEPT + { + angletox_ = std::nan(""); p1 = p; + } + + inline void second(const RawPoint& p) BP2D_NOEXCEPT { + angletox_ = std::nan(""); p2 = p; + } + + /// Returns the angle measured to the X (horizontal) axis. + inline Radians angleToXaxis() const; + + /// The length of the segment in the measure of the coordinate system. + inline double length(); +}; + +// This struct serves almost as a namespace. The only difference is that is can +// used in friend declarations. +namespace pointlike { + +template +inline TCoord x(const RawPoint& p) +{ + return p.x(); +} + +template +inline TCoord y(const RawPoint& p) +{ + return p.y(); +} + +template +inline TCoord& x(RawPoint& p) +{ + return p.x(); +} + +template +inline TCoord& y(RawPoint& p) +{ + return p.y(); +} + +template +inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) +{ + static_assert(always_false::value, + "PointLike::distance(point, point) unimplemented!"); + return 0; +} + +template +inline double distance(const RawPoint& /*p1*/, + const _Segment& /*s*/) +{ + static_assert(always_false::value, + "PointLike::distance(point, segment) unimplemented!"); + return 0; +} + +template +inline std::pair, bool> horizontalDistance( + const RawPoint& p, const _Segment& s) +{ + using Unit = TCoord; + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + + TCoord ret; + + if( (y < y1 && y < y2) || (y > y1 && y > y2) ) + return {0, false}; + if ((y == y1 && y == y2) && (x > x1 && x > x2)) + ret = std::min( x-x1, x -x2); + else if( (y == y1 && y == y2) && (x < x1 && x < x2)) + ret = -std::min(x1 - x, x2 - x); + else if(std::abs(y - y1) <= std::numeric_limits::epsilon() && + std::abs(y - y2) <= std::numeric_limits::epsilon()) + ret = 0; + else + ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); + + return {ret, true}; +} + +template +inline std::pair, bool> verticalDistance( + const RawPoint& p, const _Segment& s) +{ + using Unit = TCoord; + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + + TCoord ret; + + if( (x < x1 && x < x2) || (x > x1 && x > x2) ) + return {0, false}; + if ((x == x1 && x == x2) && (y > y1 && y > y2)) + ret = std::min( y-y1, y -y2); + else if( (x == x1 && x == x2) && (y < y1 && y < y2)) + ret = -std::min(y1 - y, y2 - y); + else if(std::abs(x - x1) <= std::numeric_limits::epsilon() && + std::abs(x - x2) <= std::numeric_limits::epsilon()) + ret = 0; + else + ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); + + return {ret, true}; +} +} + +template +TCoord _Box::width() const BP2D_NOEXCEPT +{ + return pointlike::x(maxCorner()) - pointlike::x(minCorner()); +} + +template +TCoord _Box::height() const BP2D_NOEXCEPT +{ + return pointlike::y(maxCorner()) - pointlike::y(minCorner()); +} + +template +TCoord getX(const RawPoint& p) { return pointlike::x(p); } + +template +TCoord getY(const RawPoint& p) { return pointlike::y(p); } + +template +void setX(RawPoint& p, const TCoord& val) +{ + pointlike::x(p) = val; +} + +template +void setY(RawPoint& p, const TCoord& val) +{ + pointlike::y(p) = val; +} + +template +inline Radians _Segment::angleToXaxis() const +{ + if(std::isnan(static_cast(angletox_))) { + TCoord dx = getX(second()) - getX(first()); + TCoord dy = getY(second()) - getY(first()); + + double a = std::atan2(dy, dx); + auto s = std::signbit(a); + + if(s) a += Pi_2; + angletox_ = a; + } + return angletox_; +} + +template +inline double _Segment::length() +{ + return pointlike::distance(first(), second()); +} + +template +inline RawPoint _Box::center() const BP2D_NOEXCEPT { + auto& minc = minCorner(); + auto& maxc = maxCorner(); + + using Coord = TCoord; + + RawPoint ret = { // No rounding here, we dont know if these are int coords + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) + }; + + return ret; +} + +enum class Formats { + WKT, + SVG +}; + +// This struct serves as a namespace. The only difference is that it can be +// used in friend declarations and can be aliased at class scope. +namespace shapelike { + +template +using Shapes = TMultiShape; + +template +inline RawShape create(const TContour& contour, + const THolesContainer& holes) +{ + return RawShape(contour, holes); +} + +template +inline RawShape create(TContour&& contour, + THolesContainer&& holes) +{ + return RawShape(contour, holes); +} + +template +inline RawShape create(const TContour& contour) +{ + return create(contour, {}); +} + +template +inline RawShape create(TContour&& contour) +{ + return create(contour, {}); +} + +template +inline THolesContainer& holes(RawShape& /*sh*/) +{ + static THolesContainer empty; + return empty; +} + +template +inline const THolesContainer& holes(const RawShape& /*sh*/) +{ + static THolesContainer empty; + return empty; +} + +template +inline TContour& hole(RawShape& sh, unsigned long idx) +{ + return holes(sh)[idx]; +} + +template +inline const TContour& hole(const RawShape& sh, unsigned long idx) +{ + return holes(sh)[idx]; +} + +template +inline size_t holeCount(const RawShape& sh) +{ + return holes(sh).size(); +} + +template +inline TContour& contour(RawShape& sh) +{ + static_assert(always_false::value, + "shapelike::contour() unimplemented!"); + return sh; +} + +template +inline const TContour& contour(const RawShape& sh) +{ + static_assert(always_false::value, + "shapelike::contour() unimplemented!"); + return sh; +} + +// Optional, does nothing by default +template +inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) +{ + p.reserve(vertex_capacity); +} + +template +inline void addVertex(RawShape& sh, const PathTag&, Args...args) +{ + return sh.emplace_back(std::forward(args)...); +} + +template +inline void foreachVertex(RawShape& sh, Fn fn, const PathTag&) { + std::for_each(sh.begin(), sh.end(), fn); +} + +template +inline typename RawShape::iterator begin(RawShape& sh, const PathTag&) +{ + return sh.begin(); +} + +template +inline typename RawShape::iterator end(RawShape& sh, const PathTag&) +{ + return sh.end(); +} + +template +inline typename RawShape::const_iterator +cbegin(const RawShape& sh, const PathTag&) +{ + return sh.cbegin(); +} + +template +inline typename RawShape::const_iterator +cend(const RawShape& sh, const PathTag&) +{ + return sh.cend(); +} + +template +inline std::string toString(const RawShape& /*sh*/) +{ + return ""; +} + +template +inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) +{ + static_assert(always_false::value, + "shapelike::serialize() unimplemented!"); + return ""; +} + +template +inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) +{ + static_assert(always_false::value, + "shapelike::unserialize() unimplemented!"); +} + +template +inline double area(const RawShape& /*sh*/, const PolygonTag&) +{ + static_assert(always_false::value, + "shapelike::area() unimplemented!"); + return 0; +} + +template +inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) +{ + static_assert(always_false::value, + "shapelike::intersects() unimplemented!"); + return false; +} + +template +inline bool isInside(const TPoint& /*point*/, + const RawShape& /*shape*/) +{ + static_assert(always_false::value, + "shapelike::isInside(point, shape) unimplemented!"); + return false; +} + +template +inline bool isInside(const RawShape& /*shape*/, + const RawShape& /*shape*/) +{ + static_assert(always_false::value, + "shapelike::isInside(shape, shape) unimplemented!"); + return false; +} + +template +inline bool touches( const RawShape& /*shape*/, + const RawShape& /*shape*/) +{ + static_assert(always_false::value, + "shapelike::touches(shape, shape) unimplemented!"); + return false; +} + +template +inline bool touches( const TPoint& /*point*/, + const RawShape& /*shape*/) +{ + static_assert(always_false::value, + "shapelike::touches(point, shape) unimplemented!"); + return false; +} + +template +inline _Box> boundingBox(const RawShape& /*sh*/, + const PolygonTag&) +{ + static_assert(always_false::value, + "shapelike::boundingBox(shape) unimplemented!"); +} + +template +inline _Box> +boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) +{ + static_assert(always_false::value, + "shapelike::boundingBox(shapes) unimplemented!"); +} + +template +inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) +{ + static_assert(always_false::value, + "shapelike::convexHull(shape) unimplemented!"); + return RawShape(); +} + +template +inline typename RawShapes::value_type +convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) +{ + static_assert(always_false::value, + "shapelike::convexHull(shapes) unimplemented!"); + return typename RawShapes::value_type(); +} + +template +inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) +{ + static_assert(always_false::value, + "shapelike::rotate() unimplemented!"); +} + +template +inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) +{ + static_assert(always_false::value, + "shapelike::translate() unimplemented!"); +} + +template +inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) +{ + dout() << "The current geometry backend does not support offsetting!\n"; +} + +template +inline std::pair isValid(const RawShape& /*sh*/) +{ + return {false, "shapelike::isValid() unimplemented!"}; +} + +template inline bool isConvex(const RawPath& sh, const PathTag&) +{ + using Vertex = TPoint; + auto first = begin(sh); + auto middle = std::next(first); + auto last = std::next(middle); + using CVrRef = const Vertex&; + + auto zcrossproduct = [](CVrRef k, CVrRef k1, CVrRef k2) { + auto dx1 = getX(k1) - getX(k); + auto dy1 = getY(k1) - getY(k); + auto dx2 = getX(k2) - getX(k1); + auto dy2 = getY(k2) - getY(k1); + return dx1*dy2 - dy1*dx2; + }; + + auto firstprod = zcrossproduct( *(std::prev(std::prev(end(sh)))), + *first, + *middle ); + + bool ret = true; + bool frsign = firstprod > 0; + while(last != end(sh)) { + auto &k = *first, &k1 = *middle, &k2 = *last; + auto zc = zcrossproduct(k, k1, k2); + ret &= frsign == (zc > 0); + ++first; ++middle; ++last; + } + + return ret; +} + +// ***************************************************************************** +// No need to implement these +// ***************************************************************************** + +template +inline typename TContour::iterator +begin(RawShape& sh, const PolygonTag& t) +{ + return begin(contour(sh), PathTag()); +} + +template // Tag dispatcher +inline auto begin(RawShape& sh) -> decltype(begin(sh, Tag())) +{ + return begin(sh, Tag()); +} + +template +inline typename TContour::const_iterator +cbegin(const RawShape& sh, const PolygonTag&) +{ + return cbegin(contour(sh), PathTag()); +} + +template // Tag dispatcher +inline auto cbegin(const RawShape& sh) -> decltype(cbegin(sh, Tag())) +{ + return cbegin(sh, Tag()); +} + +template +inline typename TContour::iterator +end(RawShape& sh, const PolygonTag&) +{ + return end(contour(sh), PathTag()); +} + +template // Tag dispatcher +inline auto end(RawShape& sh) -> decltype(begin(sh, Tag())) +{ + return end(sh, Tag()); +} + +template +inline typename TContour::const_iterator +cend(const RawShape& sh, const PolygonTag&) +{ + return cend(contour(sh), PathTag()); +} + +template // Tag dispatcher +inline auto cend(const RawShape& sh) -> decltype(cend(sh, Tag())) +{ + return cend(sh, Tag()); +} + +template std::reverse_iterator _backward(It iter) { + return std::reverse_iterator(iter); +} + +template auto rbegin(P& p) -> decltype(_backward(end(p))) +{ + return _backward(end(p)); +} + +template auto rcbegin(const P& p) -> decltype(_backward(end(p))) +{ + return _backward(end(p)); +} + +template auto rend(P& p) -> decltype(_backward(begin(p))) +{ + return _backward(begin(p)); +} + +template auto rcend(const P& p) -> decltype(_backward(cbegin(p))) +{ + return _backward(cbegin(p)); +} + +template TPoint

front(const P& p) { return *shapelike::cbegin(p); } +template TPoint

back (const P& p) { + return *backward(shapelike::cend(p)); +} + +// Optional, does nothing by default +template +inline void reserve(RawShape& sh, size_t vertex_capacity, const PolygonTag&) +{ + reserve(contour(sh), vertex_capacity, PathTag()); +} + +template // Tag dispatcher +inline void reserve(T& sh, size_t vertex_capacity) { + reserve(sh, vertex_capacity, Tag()); +} + +template +inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) +{ + return addVertex(contour(sh), PathTag(), std::forward(args)...); +} + +template // Tag dispatcher +inline void addVertex(RawShape& sh, Args...args) +{ + return addVertex(sh, Tag(), std::forward(args)...); +} + +template +inline Box boundingBox(const Box& box, const BoxTag& ) +{ + return box; +} + +template +inline _Box boundingBox( + const Circle& circ, const CircleTag&) +{ + using Point = typename Circle::PointType; + using Coord = TCoord; + Point pmin = { + static_cast(getX(circ.center()) - circ.radius()), + static_cast(getY(circ.center()) - circ.radius()) }; + + Point pmax = { + static_cast(getX(circ.center()) + circ.radius()), + static_cast(getY(circ.center()) + circ.radius()) }; + + return {pmin, pmax}; +} + +template // Dispatch function +inline _Box> boundingBox(const S& sh) +{ + return boundingBox(sh, Tag() ); +} + +template +inline double area(const Box& box, const BoxTag& ) +{ + return box.area(); +} + +template +inline double area(const Circle& circ, const CircleTag& ) +{ + return circ.area(); +} + +template // Dispatching function +inline double area(const RawShape& sh) +{ + return area(sh, Tag()); +} + +template +inline double area(const RawShapes& shapes, const MultiPolygonTag&) +{ + using RawShape = typename RawShapes::value_type; + return std::accumulate(shapes.begin(), shapes.end(), 0.0, + [](double a, const RawShape& b) { + return a += area(b); + }); +} + +template +inline auto convexHull(const RawShape& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce +{ + return convexHull(sh, Tag()); +} + +template +inline bool isInside(const TPoint& point, + const _Circle>& circ) +{ + return pointlike::distance(point, circ.center()) < circ.radius(); +} + +template +inline bool isInside(const TPoint& point, + const _Box>& box) +{ + auto px = getX(point); + auto py = getY(point); + auto minx = getX(box.minCorner()); + auto miny = getY(box.minCorner()); + auto maxx = getX(box.maxCorner()); + auto maxy = getY(box.maxCorner()); + + return px > minx && px < maxx && py > miny && py < maxy; +} + +template +inline bool isInside(const RawShape& sh, + const _Circle>& circ) +{ + return std::all_of(cbegin(sh), cend(sh), + [&circ](const TPoint& p){ + return isInside(p, circ); + }); +} + +template +inline bool isInside(const _Box>& box, + const _Circle>& circ) +{ + return isInside(box.minCorner(), circ) && + isInside(box.maxCorner(), circ); +} + +template +inline bool isInside(const _Box>& ibb, + const _Box>& box) +{ + auto iminX = getX(ibb.minCorner()); + auto imaxX = getX(ibb.maxCorner()); + auto iminY = getY(ibb.minCorner()); + auto imaxY = getY(ibb.maxCorner()); + + auto minX = getX(box.minCorner()); + auto maxX = getX(box.maxCorner()); + auto minY = getY(box.minCorner()); + auto maxY = getY(box.maxCorner()); + + return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; +} + +template // Potential O(1) implementation may exist +inline TPoint& vertex(RawShape& sh, unsigned long idx, + const PolygonTag&) +{ + return *(shapelike::begin(contour(sh)) + idx); +} + +template // Potential O(1) implementation may exist +inline TPoint& vertex(RawShape& sh, unsigned long idx, + const PathTag&) +{ + return *(shapelike::begin(sh) + idx); +} + +template // Potential O(1) implementation may exist +inline TPoint& vertex(RawShape& sh, unsigned long idx) +{ + return vertex(sh, idx, Tag()); +} + +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const RawShape& sh, + unsigned long idx, + const PolygonTag&) +{ + return *(shapelike::cbegin(contour(sh)) + idx); +} + +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const RawShape& sh, + unsigned long idx, + const PathTag&) +{ + return *(shapelike::cbegin(sh) + idx); +} + + +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const RawShape& sh, + unsigned long idx) +{ + return vertex(sh, idx, Tag()); +} + +template +inline size_t contourVertexCount(const RawShape& sh) +{ + return shapelike::cend(sh) - shapelike::cbegin(sh); +} + +template +inline void foreachVertex(RawShape& sh, Fn fn, const PolygonTag&) { + foreachVertex(contour(sh), fn, PathTag()); + for(auto& h : holes(sh)) foreachVertex(h, fn, PathTag()); +} + +template +inline void foreachVertex(RawShape& sh, Fn fn) { + foreachVertex(sh, fn, Tag()); +} + +template inline bool isConvex(const Poly& sh, const PolygonTag&) +{ + bool convex = true; + convex &= isConvex(contour(sh), PathTag()); + convex &= holeCount(sh) == 0; + return convex; +} + +template inline bool isConvex(const RawShape& sh) // dispatch +{ + return isConvex(sh, Tag()); +} + +} + +#define DECLARE_MAIN_TYPES(T) \ + using Polygon = T; \ + using Point = TPoint; \ + using Coord = TCoord; \ + using Contour = TContour; \ + using Box = _Box; \ + using Circle = _Circle; \ + using Segment = _Segment; \ + using Polygons = TMultiShape + +} + +#endif // GEOMETRY_TRAITS_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp new file mode 100644 index 000000000..b57b8dc53 --- /dev/null +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -0,0 +1,282 @@ +#ifndef GEOMETRIES_NOFITPOLYGON_HPP +#define GEOMETRIES_NOFITPOLYGON_HPP + +#include "geometry_traits.hpp" +#include +#include +#include +#include + +namespace libnest2d { + +namespace __nfp { +// Do not specialize this... +template +inline bool _vsort(const TPoint& v1, const TPoint& v2) +{ + using Coord = TCoord>; + Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return x1 < x2; + + return diff < 0; +} + +template> +inline void buildPolygon(const EdgeList& edgelist, + RawShape& rpoly, + Vertex& top_nfp) +{ + namespace sl = shapelike; + + auto& rsh = sl::contour(rpoly); + + sl::reserve(rsh, 2*edgelist.size()); + + // Add the two vertices from the first edge into the final polygon. + sl::addVertex(rsh, edgelist.front().first()); + sl::addVertex(rsh, edgelist.front().second()); + + // Sorting function for the nfp reference vertex search + auto& cmp = _vsort; + + // the reference (rightmost top) vertex so far + top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); + + auto tmp = std::next(sl::begin(rsh)); + + // Construct final nfp by placing each edge to the end of the previous + for(auto eit = std::next(edgelist.begin()); + eit != edgelist.end(); + ++eit) + { + auto d = *tmp - eit->first(); + Vertex p = eit->second() + d; + + sl::addVertex(rsh, p); + + // Set the new reference vertex + if(cmp(top_nfp, p)) top_nfp = p; + + tmp = std::next(tmp); + } + +} + +template +void advance(Iterator& it, Container& cont, bool direction) +{ + int dir = direction ? 1 : -1; + if(dir < 0 && it == cont.begin()) it = std::prev(cont.end()); + else it += dir; + if(dir > 0 && it == cont.end()) it = cont.begin(); +} + +} + +/// A collection of static methods for handling the no fit polygon creation. +namespace nfp { + +const double BP2D_CONSTEXPR TwoPi = 2*Pi; + +/// The complexity level of a polygon that an NFP implementation can handle. +enum class NfpLevel: unsigned { + CONVEX_ONLY, + ONE_CONVEX, + BOTH_CONCAVE, + ONE_CONVEX_WITH_HOLES, + BOTH_CONCAVE_WITH_HOLES +}; + +template +using NfpResult = std::pair>; + +template struct MaxNfpLevel { + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + + +// Shorthand for a pile of polygons +template +using Shapes = TMultiShape; + +/** + * Merge a bunch of polygons with the specified additional polygon. + * + * \tparam RawShape the Polygon data type. + * \param shc The pile of polygons that will be unified with sh. + * \param sh A single polygon to unify with shc. + * + * \return A set of polygons that is the union of the input polygons. Note that + * mostly it will be a set containing only one big polygon but if the input + * polygons are disjunct than the resulting set will contain more polygons. + */ +template +inline RawShapes merge(const RawShapes& /*shc*/) +{ + static_assert(always_false::value, + "Nfp::merge(shapes, shape) unimplemented!"); +} + +/** + * Merge a bunch of polygons with the specified additional polygon. + * + * \tparam RawShape the Polygon data type. + * \param shc The pile of polygons that will be unified with sh. + * \param sh A single polygon to unify with shc. + * + * \return A set of polygons that is the union of the input polygons. Note that + * mostly it will be a set containing only one big polygon but if the input + * polygons are disjunct than the resulting set will contain more polygons. + */ +template +inline TMultiShape merge(const TMultiShape& shc, + const RawShape& sh) +{ + auto m = nfp::merge(shc); + m.emplace_back(sh); + return nfp::merge(m); +} + +/** + * Get the vertex of the polygon that is at the lowest values (bottom) in the Y + * axis and if there are more than one vertices on the same Y coordinate than + * the result will be the leftmost (with the highest X coordinate). + */ +template +inline TPoint leftmostDownVertex(const RawShape& sh) +{ + + // find min x and min y vertex + auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); + + return it == shapelike::cend(sh) ? TPoint() : *it;; +} + +/** + * Get the vertex of the polygon that is at the highest values (top) in the Y + * axis and if there are more than one vertices on the same Y coordinate than + * the result will be the rightmost (with the lowest X coordinate). + */ +template +TPoint rightmostUpVertex(const RawShape& sh) +{ + + // find max x and max y vertex + auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); + + return it == shapelike::cend(sh) ? TPoint() : *it; +} + +/** + * A method to get a vertex from a polygon that always maintains a relative + * position to the coordinate system: It is always the rightmost top vertex. + * + * This way it does not matter in what order the vertices are stored, the + * reference will be always the same for the same polygon. + */ +template +inline TPoint referenceVertex(const RawShape& sh) +{ + return rightmostUpVertex(sh); +} + +/** + * The "trivial" Cuninghame-Green implementation of NFP for convex polygons. + * + * You can use this even if you provide implementations for the more complex + * cases (Through specializing the the NfpImpl struct). Currently, no other + * cases are covered in the library. + * + * Complexity should be no more than nlogn (std::sort) in the number of edges + * of the input polygons. + * + * \tparam RawShape the Polygon data type. + * \param sh The stationary polygon + * \param cother The orbiting polygon + * \return Returns a pair of the NFP and its reference vertex of the two input + * polygons which have to be strictly convex. The resulting NFP is proven to be + * convex as well in this case. + * + */ +template +inline NfpResult nfpConvexOnly(const RawShape& sh, + const RawShape& other) +{ + using Vertex = TPoint; using Edge = _Segment; + namespace sl = shapelike; + + RawShape rsh; // Final nfp placeholder + Vertex top_nfp; + std::vector edgelist; + + auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other); + + // Reserve the needed memory + edgelist.reserve(cap); + sl::reserve(rsh, static_cast(cap)); + + { // place all edges from sh into edgelist + auto first = sl::cbegin(sh); + auto next = std::next(first); + + while(next != sl::cend(sh)) { + edgelist.emplace_back(*(first), *(next)); + ++first; ++next; + } + } + + { // place all edges from other into edgelist + auto first = sl::cbegin(other); + auto next = std::next(first); + + while(next != sl::cend(other)) { + edgelist.emplace_back(*(next), *(first)); + ++first; ++next; + } + } + + // Sort the edges by angle to X axis. + std::sort(edgelist.begin(), edgelist.end(), + [](const Edge& e1, const Edge& e2) + { + return e1.angleToXaxis() > e2.angleToXaxis(); + }); + + __nfp::buildPolygon(edgelist, rsh, top_nfp); + + return {rsh, top_nfp}; +} + +// Specializable NFP implementation class. Specialize it if you have a faster +// or better NFP implementation +template +struct NfpImpl { + NfpResult operator()(const RawShape& sh, const RawShape& other) + { + static_assert(nfptype == NfpLevel::CONVEX_ONLY, + "Nfp::noFitPolygon() unimplemented!"); + + // Libnest2D has a default implementation for convex polygons and will + // use it if feasible. + return nfpConvexOnly(sh, other); + } +}; + +/// Helper function to get the NFP +template +inline NfpResult noFitPolygon(const RawShape& sh, + const RawShape& other) +{ + NfpImpl nfps; + return nfps(sh, other); +} + +} + +} + +#endif // GEOMETRIES_NOFITPOLYGON_HPP diff --git a/src/libnest2d/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp similarity index 99% rename from src/libnest2d/libnest2d/libnest2d.hpp rename to src/libnest2d/include/libnest2d/libnest2d.hpp index 8841d1b73..aac62e094 100644 --- a/src/libnest2d/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -215,7 +215,7 @@ public: switch(convexity_) { case Convexity::UNCHECKED: - ret = sl::isConvex(sl::getContour(transformedShape())); + ret = sl::isConvex(sl::contour(transformedShape())); convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE; break; case Convexity::C_TRUE: ret = true; break; @@ -805,16 +805,16 @@ public: class SConf = SelectionConfig> Nester( TBinType&& bin, Unit min_obj_distance = 0, - PConf&& pconfig = PConf(), - SConf&& sconfig = SConf()): + const PConf& pconfig = PConf(), + const SConf& sconfig = SConf()): bin_(std::forward(bin)), - pconfig_(std::forward(pconfig)), + pconfig_(pconfig), min_obj_distance_(min_obj_distance) { static_assert( std::is_same::value, "Incompatible placement and selection strategy!"); - selector_.configure(std::forward(sconfig)); + selector_.configure(sconfig); } void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } diff --git a/src/libnest2d/libnest2d/optimizer.hpp b/src/libnest2d/include/libnest2d/optimizer.hpp similarity index 96% rename from src/libnest2d/libnest2d/optimizer.hpp rename to src/libnest2d/include/libnest2d/optimizer.hpp index 90d2f2ff9..78e105598 100644 --- a/src/libnest2d/libnest2d/optimizer.hpp +++ b/src/libnest2d/include/libnest2d/optimizer.hpp @@ -105,6 +105,11 @@ struct StopCriteria { /// Stop if this value or better is found. double stop_score = std::nan(""); + /// A predicate that if evaluates to true, the optimization should terminate + /// and the best result found prior to termination should be returned. + std::function stop_condition = [] { return false; }; + + /// The max allowed number of iterations. unsigned max_iterations = 0; }; diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt new file mode 100644 index 000000000..2a32019f4 --- /dev/null +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt @@ -0,0 +1,61 @@ +find_package(NLopt 1.4) + +if(NOT NLopt_FOUND) + message(STATUS "NLopt not found so downloading " + "and automatic build is performed...") + + include(DownloadProject) + + if (CMAKE_VERSION VERSION_LESS 3.2) + set(UPDATE_DISCONNECTED_IF_AVAILABLE "") + else() + set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") + endif() + + set(URL_NLOPT "https://github.com/stevengj/nlopt.git" + CACHE STRING "Location of the nlopt git repository") + + # set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt) + include(DownloadProject) + download_project( PROJ nlopt + GIT_REPOSITORY ${URL_NLOPT} + GIT_TAG v2.5.0 + # CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR} + ${UPDATE_DISCONNECTED_IF_AVAILABLE} + ) + + set(SHARED_LIBS_STATE BUILD_SHARED_LIBS) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE) + set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE) + set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE) + set(NLOPT_GUILE OFF CACHE BOOL "" FORCE) + set(NLOPT_SWIG OFF CACHE BOOL "" FORCE) + set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) + + add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) + + set(NLopt_LIBS nlopt) + set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} ${nlopt_BINARY_DIR}/src/api) + set(SHARED_LIBS_STATE ${SHARED_STATE}) + + add_library(NloptOptimizer INTERFACE) + target_link_libraries(NloptOptimizer INTERFACE nlopt) + target_include_directories(NloptOptimizer INTERFACE ${NLopt_INCLUDE_DIR}) + +else() + add_library(NloptOptimizer INTERFACE) + target_link_libraries(NloptOptimizer INTERFACE Nlopt::Nlopt) +endif() + +#target_sources( NloptOptimizer INTERFACE +#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp +#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp +#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp +#${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp +#) + +target_compile_definitions(NloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) + +# And finally plug the NloptOptimizer into libnest2d +target_link_libraries(libnest2d INTERFACE NloptOptimizer) diff --git a/src/libnest2d/libnest2d/optimizers/genetic.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp similarity index 88% rename from src/libnest2d/libnest2d/optimizers/genetic.hpp rename to src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp index 276854a12..731ead554 100644 --- a/src/libnest2d/libnest2d/optimizers/genetic.hpp +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp @@ -19,7 +19,8 @@ public: template<> struct OptimizerSubclass { using Type = GeneticOptimizer; }; -template<> TOptimizer GlobalOptimizer( +template<> +inline TOptimizer GlobalOptimizer( Method localm, const StopCriteria& scr ) { return GeneticOptimizer (scr).localMethod(localm); diff --git a/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp similarity index 85% rename from src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp rename to src/libnest2d/include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp index 1a0f06e02..286d176c5 100644 --- a/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp @@ -13,7 +13,7 @@ #include #include -#include "libnest2d/metaloop.hpp" +#include #include @@ -100,7 +100,13 @@ protected: std::vector& /*grad*/, void *data) { - auto fnptr = static_cast*>(data); + using TData = std::pair*, NloptOptimizer*>; + auto typeddata = static_cast(data); + + if(typeddata->second->stopcr_.stop_condition()) + typeddata->second->opt_.force_stop(); + + auto fnptr = typeddata->first; auto funval = std::tuple(); // copy the obtained objectfunction arguments to the funval tuple. @@ -155,17 +161,25 @@ protected: // Take care of the initial values, copy them to initvals_ metaloop::apply(InitValFunc(*this), initvals); + std::pair*, NloptOptimizer*> data = + std::make_pair(&func, this); + switch(dir_) { case OptDir::MIN: - opt_.set_min_objective(optfunc, &func); break; + opt_.set_min_objective(optfunc, &data); break; case OptDir::MAX: - opt_.set_max_objective(optfunc, &func); break; + opt_.set_max_objective(optfunc, &data); break; } Result result; + nlopt::result rescode; - auto rescode = opt_.optimize(initvals_, result.score); - result.resultcode = static_cast(rescode); + try { + rescode = opt_.optimize(initvals_, result.score); + result.resultcode = static_cast(rescode); + } catch( nlopt::forced_stop& ) { + result.resultcode = ResultCodes::FORCED_STOP; + } metaloop::apply(ResultCopyFunc(*this), result.optimum); diff --git a/src/libnest2d/libnest2d/optimizers/simplex.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/simplex.hpp similarity index 100% rename from src/libnest2d/libnest2d/optimizers/simplex.hpp rename to src/libnest2d/include/libnest2d/optimizers/nlopt/simplex.hpp diff --git a/src/libnest2d/libnest2d/optimizers/subplex.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/subplex.hpp similarity index 100% rename from src/libnest2d/libnest2d/optimizers/subplex.hpp rename to src/libnest2d/include/libnest2d/optimizers/nlopt/subplex.hpp diff --git a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt new file mode 100644 index 000000000..efbbd9cfb --- /dev/null +++ b/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(Armadillo REQUIRED) + +add_library(OptimlibOptimizer INTERFACE) +target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS}) +target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES}) \ No newline at end of file diff --git a/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp similarity index 98% rename from src/libnest2d/libnest2d/placers/bottomleftplacer.hpp rename to src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 18c27c40c..7f10be7d7 100644 --- a/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -233,7 +233,8 @@ protected: assert(pleft.vertexCount() > 0); - auto trpleft = pleft.transformedShape(); + auto trpleft_poly = pleft.transformedShape(); + auto& trpleft = sl::contour(trpleft_poly); auto first = sl::begin(trpleft); auto next = first + 1; auto endit = sl::end(trpleft); @@ -355,8 +356,10 @@ protected: auto start = std::min(topleft_it->first, bottomleft_it->first); auto finish = std::max(topleft_it->first, bottomleft_it->first); + RawShape ret; + // the return shape - RawShape rsh; + auto& rsh = sl::contour(ret); // reserve for all vertices plus 2 for the left horizontal wall, 2 for // the additional vertices for maintaning min object distance @@ -401,7 +404,7 @@ protected: // Close the polygon sl::addVertex(rsh, topleft_vertex); - return rsh; + return ret; } }; diff --git a/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp similarity index 97% rename from src/libnest2d/libnest2d/placers/nfpplacer.hpp rename to src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index c86fb507e..14ed3d22c 100644 --- a/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -15,11 +15,13 @@ #ifndef NDEBUG #include #endif -#include "placer_boilerplate.hpp" -#include "../geometry_traits_nfp.hpp" -#include "libnest2d/optimizer.hpp" +#include +#include -#include "tools/svgtools.hpp" +#include "placer_boilerplate.hpp" + +// temporary +//#include "../tools/svgtools.hpp" #ifdef USE_TBB #include @@ -55,7 +57,7 @@ inline void enumerate( #elif defined(_OPENMP) if((policy & std::launch::async) == std::launch::async) { #pragma omp parallel for - for(TN n = 0; n < N; n++) fn(*(from + n), n); + for(int n = 0; n < int(N); n++) fn(*(from + n), TN(n)); } else { for(TN n = 0; n < N; n++) fn(*(from + n), n); @@ -72,19 +74,6 @@ inline void enumerate( #endif } -class SpinLock { - static std::atomic_flag locked; -public: - void lock() { - while (locked.test_and_set(std::memory_order_acquire)) { ; } - } - void unlock() { - locked.clear(std::memory_order_release); - } -}; - -std::atomic_flag SpinLock::locked = ATOMIC_FLAG_INIT ; - } namespace __itemhash { @@ -101,7 +90,7 @@ Key hash(const _Item& item) { std::string ret; auto& rhs = item.rawShape(); - auto& ctr = sl::getContour(rhs); + auto& ctr = sl::contour(rhs); auto it = ctr.begin(); auto nx = std::next(it); @@ -467,7 +456,7 @@ Circle minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; - auto& ctr = sl::getContour(sh); + auto& ctr = sl::contour(sh); if(ctr.empty()) return {{0, 0}, 0}; auto bb = sl::boundingBox(sh); @@ -641,6 +630,23 @@ private: Shapes nfps(items_.size()); const Item& trsh = itsh.first; + // ///////////////////////////////////////////////////////////////////// + // TODO: this is a workaround and should be solved in Item with mutexes + // guarding the mutable members when writing them. + // ///////////////////////////////////////////////////////////////////// + trsh.transformedShape(); + trsh.referenceVertex(); + trsh.rightmostTopVertex(); + trsh.leftmostBottomVertex(); + + for(Item& itm : items_) { + itm.transformedShape(); + itm.referenceVertex(); + itm.rightmostTopVertex(); + itm.leftmostBottomVertex(); + } + // ///////////////////////////////////////////////////////////////////// + __parallel::enumerate(items_.begin(), items_.end(), [&nfps, &trsh](const Item& sh, size_t n) { @@ -651,14 +657,10 @@ private: nfps[n] = subnfp_r.first; }); -// for(auto& n : nfps) { -// auto valid = sl::isValid(n); -// if(!valid.first) std::cout << "Warning: " << valid.second << std::endl; -// } - return nfp::merge(nfps); } + template Shapes calcnfp( const ItemWithHash itsh, Level) { // Function for arbitrary level of nfp implementation @@ -842,7 +844,11 @@ private: bool can_pack = false; double best_overfit = std::numeric_limits::max(); - auto remlist = ItemGroup(remaining.from, remaining.to); + ItemGroup remlist; + if(remaining.valid) { + remlist.insert(remlist.end(), remaining.from, remaining.to); + } + size_t itemhash = __itemhash::hash(item); if(items_.empty()) { diff --git a/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp similarity index 98% rename from src/libnest2d/libnest2d/placers/placer_boilerplate.hpp rename to src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index 0df1b8c91..9f940af4d 100644 --- a/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -1,7 +1,7 @@ #ifndef PLACER_BOILERPLATE_HPP #define PLACER_BOILERPLATE_HPP -#include "../libnest2d.hpp" +#include namespace libnest2d { namespace placers { diff --git a/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp similarity index 100% rename from src/libnest2d/libnest2d/selections/djd_heuristic.hpp rename to src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp diff --git a/src/libnest2d/libnest2d/selections/filler.hpp b/src/libnest2d/include/libnest2d/selections/filler.hpp similarity index 100% rename from src/libnest2d/libnest2d/selections/filler.hpp rename to src/libnest2d/include/libnest2d/selections/filler.hpp diff --git a/src/libnest2d/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp similarity index 99% rename from src/libnest2d/libnest2d/selections/firstfit.hpp rename to src/libnest2d/include/libnest2d/selections/firstfit.hpp index 6bb9c60e4..d25487d6b 100644 --- a/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -1,7 +1,6 @@ #ifndef FIRSTFIT_HPP #define FIRSTFIT_HPP -#include "../libnest2d.hpp" #include "selection_boilerplate.hpp" namespace libnest2d { namespace selections { diff --git a/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp similarity index 96% rename from src/libnest2d/libnest2d/selections/selection_boilerplate.hpp rename to src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index cfb98a9c8..8351a99e7 100644 --- a/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -1,8 +1,8 @@ #ifndef SELECTION_BOILERPLATE_HPP #define SELECTION_BOILERPLATE_HPP -#include "../libnest2d.hpp" #include +#include namespace libnest2d { namespace selections { diff --git a/src/libnest2d/libnest2d/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp similarity index 98% rename from src/libnest2d/libnest2d/boost_alg.hpp rename to src/libnest2d/include/libnest2d/utils/boost_alg.hpp index bb0403b06..c573edb47 100644 --- a/src/libnest2d/libnest2d/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -241,11 +241,11 @@ template<> struct tag { template<> struct exterior_ring { static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) { - return libnest2d::shapelike::getContour(p); + return libnest2d::shapelike::contour(p); } static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) { - return libnest2d::shapelike::getContour(p); + return libnest2d::shapelike::contour(p); } }; @@ -388,6 +388,14 @@ inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) return b; } +template<> +inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) +{ + bp2d::Box b; + boost::geometry::envelope(sh, b); + return b; +} + template<> inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, const MultiPolygonTag&) diff --git a/src/libnest2d/libnest2d/metaloop.hpp b/src/libnest2d/include/libnest2d/utils/metaloop.hpp similarity index 98% rename from src/libnest2d/libnest2d/metaloop.hpp rename to src/libnest2d/include/libnest2d/utils/metaloop.hpp index d88988ba1..ac099ac46 100644 --- a/src/libnest2d/libnest2d/metaloop.hpp +++ b/src/libnest2d/include/libnest2d/utils/metaloop.hpp @@ -1,7 +1,7 @@ #ifndef METALOOP_HPP #define METALOOP_HPP -#include "common.hpp" +#include #include #include @@ -12,7 +12,7 @@ namespace libnest2d { /* ************************************************************************** */ /** - * \brief C++11 conformant implementation of the index_sequence type from C++14 + * \brief C++11 compatible implementation of the index_sequence type from C++14 */ template struct index_sequence { using value_type = size_t; diff --git a/src/libnest2d/libnest2d/rotfinder.hpp b/src/libnest2d/include/libnest2d/utils/rotfinder.hpp similarity index 100% rename from src/libnest2d/libnest2d/rotfinder.hpp rename to src/libnest2d/include/libnest2d/utils/rotfinder.hpp diff --git a/src/libnest2d/libnest2d.h b/src/libnest2d/libnest2d.h deleted file mode 100644 index bfd88f4f5..000000000 --- a/src/libnest2d/libnest2d.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef LIBNEST2D_H -#define LIBNEST2D_H - -// The type of backend should be set conditionally by the cmake configuriation -// for now we set it statically to clipper backend -#include - -// We include the stock optimizers for local and global optimization -#include // Local subplex for NfpPlacer -#include // Genetic for min. bounding box - -#include -#include -#include -#include -#include -#include - -namespace libnest2d { - -using Point = PointImpl; -using Coord = TCoord; -using Box = _Box; -using Segment = _Segment; -using Circle = _Circle; - -using Item = _Item; -using Rectangle = _Rectangle; - -using PackGroup = _PackGroup; -using IndexedPackGroup = _IndexedPackGroup; - -using FillerSelection = selections::_FillerSelection; -using FirstFitSelection = selections::_FirstFitSelection; -using DJDHeuristic = selections::_DJDHeuristic; - -using NfpPlacer = placers::_NofitPolyPlacer; -using BottomLeftPlacer = placers::_BottomLeftPlacer; - -} - -#endif // LIBNEST2D_H diff --git a/src/libnest2d/libnest2d/geometry_traits.hpp b/src/libnest2d/libnest2d/geometry_traits.hpp deleted file mode 100644 index a78a03b3a..000000000 --- a/src/libnest2d/libnest2d/geometry_traits.hpp +++ /dev/null @@ -1,825 +0,0 @@ -#ifndef GEOMETRY_TRAITS_HPP -#define GEOMETRY_TRAITS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common.hpp" - -namespace libnest2d { - -/// Getting the coordinate data type for a geometry class. -template struct CoordType { using Type = long; }; - -/// TCoord as shorthand for typename `CoordType::Type`. -template -using TCoord = typename CoordType>::Type; - -/// Getting the type of point structure used by a shape. -template struct PointType { using Type = typename Sh::PointType; }; - -/// TPoint as shorthand for `typename PointType::Type`. -template -using TPoint = typename PointType>::Type; - -/** - * \brief A point pair base class for other point pairs (segment, box, ...). - * \tparam RawPoint The actual point type to use. - */ -template -struct PointPair { - RawPoint p1; - RawPoint p2; -}; - -struct PolygonTag {}; -struct MultiPolygonTag {}; -struct BoxTag {}; -struct CircleTag {}; - -template struct ShapeTag { using Type = typename Shape::Tag; }; -template using Tag = typename ShapeTag::Type; - -template struct MultiShape { using Type = std::vector; }; -template using TMultiShape = typename MultiShape::Type; - -/** - * \brief An abstraction of a box; - */ -template -class _Box: PointPair { - using PointPair::p1; - using PointPair::p2; -public: - - using Tag = BoxTag; - using PointType = RawPoint; - - inline _Box() = default; - inline _Box(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} - - inline _Box(TCoord width, TCoord height): - _Box(RawPoint{0, 0}, RawPoint{width, height}) {} - - inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; } - inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; } - - inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; } - inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; } - - inline TCoord width() const BP2D_NOEXCEPT; - inline TCoord height() const BP2D_NOEXCEPT; - - inline RawPoint center() const BP2D_NOEXCEPT; - - inline double area() const BP2D_NOEXCEPT { - return double(width()*height()); - } -}; - -template -class _Circle { - RawPoint center_; - double radius_ = 0; -public: - - using Tag = CircleTag; - using PointType = RawPoint; - - _Circle() = default; - - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} - - inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } - inline const void center(const RawPoint& c) { center_ = c; } - - inline double radius() const BP2D_NOEXCEPT { return radius_; } - inline void radius(double r) { radius_ = r; } - - inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_*radius_; - } -}; - -/** - * \brief An abstraction of a directed line segment with two points. - */ -template -class _Segment: PointPair { - using PointPair::p1; - using PointPair::p2; - mutable Radians angletox_ = std::nan(""); -public: - - using PointType = RawPoint; - - inline _Segment() = default; - - inline _Segment(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} - - /** - * @brief Get the first point. - * @return Returns the starting point. - */ - inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } - - /** - * @brief The end point. - * @return Returns the end point of the segment. - */ - inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } - - inline void first(const RawPoint& p) BP2D_NOEXCEPT - { - angletox_ = std::nan(""); p1 = p; - } - - inline void second(const RawPoint& p) BP2D_NOEXCEPT { - angletox_ = std::nan(""); p2 = p; - } - - /// Returns the angle measured to the X (horizontal) axis. - inline Radians angleToXaxis() const; - - /// The length of the segment in the measure of the coordinate system. - inline double length(); -}; - -// This struct serves almost as a namespace. The only difference is that is can -// used in friend declarations. -namespace pointlike { - - template - inline TCoord x(const RawPoint& p) - { - return p(0); - } - - template - inline TCoord y(const RawPoint& p) - { - return p(1); - } - - template - inline TCoord& x(RawPoint& p) - { - return p(0); - } - - template - inline TCoord& y(RawPoint& p) - { - return p(1); - } - - template - inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) - { - static_assert(always_false::value, - "PointLike::distance(point, point) unimplemented!"); - return 0; - } - - template - inline double distance(const RawPoint& /*p1*/, - const _Segment& /*s*/) - { - static_assert(always_false::value, - "PointLike::distance(point, segment) unimplemented!"); - return 0; - } - - template - inline std::pair, bool> horizontalDistance( - const RawPoint& p, const _Segment& s) - { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); - - TCoord ret; - - if( (y < y1 && y < y2) || (y > y1 && y > y2) ) - return {0, false}; - if ((y == y1 && y == y2) && (x > x1 && x > x2)) - ret = std::min( x-x1, x -x2); - else if( (y == y1 && y == y2) && (x < x1 && x < x2)) - ret = -std::min(x1 - x, x2 - x); - else if(std::abs(y - y1) <= std::numeric_limits::epsilon() && - std::abs(y - y2) <= std::numeric_limits::epsilon()) - ret = 0; - else - ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); - - return {ret, true}; - } - - template - inline std::pair, bool> verticalDistance( - const RawPoint& p, const _Segment& s) - { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); - - TCoord ret; - - if( (x < x1 && x < x2) || (x > x1 && x > x2) ) - return {0, false}; - if ((x == x1 && x == x2) && (y > y1 && y > y2)) - ret = std::min( y-y1, y -y2); - else if( (x == x1 && x == x2) && (y < y1 && y < y2)) - ret = -std::min(y1 - y, y2 - y); - else if(std::abs(x - x1) <= std::numeric_limits::epsilon() && - std::abs(x - x2) <= std::numeric_limits::epsilon()) - ret = 0; - else - ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); - - return {ret, true}; - } -} - -template -TCoord _Box::width() const BP2D_NOEXCEPT -{ - return pointlike::x(maxCorner()) - pointlike::x(minCorner()); -} - -template -TCoord _Box::height() const BP2D_NOEXCEPT -{ - return pointlike::y(maxCorner()) - pointlike::y(minCorner()); -} - -template -TCoord getX(const RawPoint& p) { return pointlike::x(p); } - -template -TCoord getY(const RawPoint& p) { return pointlike::y(p); } - -template -void setX(RawPoint& p, const TCoord& val) -{ - pointlike::x(p) = val; -} - -template -void setY(RawPoint& p, const TCoord& val) -{ - pointlike::y(p) = val; -} - -template -inline Radians _Segment::angleToXaxis() const -{ - if(std::isnan(static_cast(angletox_))) { - TCoord dx = getX(second()) - getX(first()); - TCoord dy = getY(second()) - getY(first()); - - double a = std::atan2(dy, dx); - auto s = std::signbit(a); - - if(s) a += Pi_2; - angletox_ = a; - } - return angletox_; -} - -template -inline double _Segment::length() -{ - return pointlike::distance(first(), second()); -} - -template -inline RawPoint _Box::center() const BP2D_NOEXCEPT { - auto& minc = minCorner(); - auto& maxc = maxCorner(); - - using Coord = TCoord; - - RawPoint ret = { // No rounding here, we dont know if these are int coords - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) - }; - - return ret; -} - -template -struct HolesContainer { - using Type = std::vector; -}; - -template -using THolesContainer = typename HolesContainer>::Type; - -template -struct CountourType { - using Type = RawShape; -}; - -template -using TContour = typename CountourType>::Type; - -enum class Orientation { - CLOCKWISE, - COUNTER_CLOCKWISE -}; - -template -struct OrientationType { - - // Default Polygon orientation that the library expects - static const Orientation Value = Orientation::CLOCKWISE; -}; - -enum class Formats { - WKT, - SVG -}; - -// This struct serves as a namespace. The only difference is that it can be -// used in friend declarations and can be aliased at class scope. -namespace shapelike { - - template - using Shapes = TMultiShape; - - template - inline RawShape create(const TContour& contour, - const THolesContainer& holes) - { - return RawShape(contour, holes); - } - - template - inline RawShape create(TContour&& contour, - THolesContainer&& holes) - { - return RawShape(contour, holes); - } - - template - inline RawShape create(const TContour& contour) - { - return create(contour, {}); - } - - template - inline RawShape create(TContour&& contour) - { - return create(contour, {}); - } - - template - inline THolesContainer& holes(RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - inline const THolesContainer& holes(const RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - inline TContour& getHole(RawShape& sh, unsigned long idx) - { - return holes(sh)[idx]; - } - - template - inline const TContour& getHole(const RawShape& sh, - unsigned long idx) - { - return holes(sh)[idx]; - } - - template - inline size_t holeCount(const RawShape& sh) - { - return holes(sh).size(); - } - - template - inline TContour& getContour(RawShape& sh) - { - return sh; - } - - template - inline const TContour& getContour(const RawShape& sh) - { - return sh; - } - - // Optional, does nothing by default - template - inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} - - template - inline void addVertex(RawShape& sh, Args...args) - { - return getContour(sh).emplace_back(std::forward(args)...); - } - - template - inline typename TContour::iterator begin(RawShape& sh) - { - return getContour(sh).begin(); - } - - template - inline typename TContour::iterator end(RawShape& sh) - { - return getContour(sh).end(); - } - - template - inline typename TContour::const_iterator - cbegin(const RawShape& sh) - { - return getContour(sh).cbegin(); - } - - template - inline typename TContour::const_iterator cend(const RawShape& sh) - { - return getContour(sh).cend(); - } - - template - inline std::string toString(const RawShape& /*sh*/) - { - return ""; - } - - template - inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) - { - static_assert(always_false::value, - "ShapeLike::serialize() unimplemented!"); - return ""; - } - - template - inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) - { - static_assert(always_false::value, - "ShapeLike::unserialize() unimplemented!"); - } - - template - inline double area(const RawShape& /*sh*/, const PolygonTag&) - { - static_assert(always_false::value, - "ShapeLike::area() unimplemented!"); - return 0; - } - - template - inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) - { - static_assert(always_false::value, - "ShapeLike::intersects() unimplemented!"); - return false; - } - - template - inline bool isInside(const TPoint& /*point*/, - const RawShape& /*shape*/) - { - static_assert(always_false::value, - "ShapeLike::isInside(point, shape) unimplemented!"); - return false; - } - - template - inline bool isInside(const RawShape& /*shape*/, - const RawShape& /*shape*/) - { - static_assert(always_false::value, - "ShapeLike::isInside(shape, shape) unimplemented!"); - return false; - } - - template - inline bool touches( const RawShape& /*shape*/, - const RawShape& /*shape*/) - { - static_assert(always_false::value, - "ShapeLike::touches(shape, shape) unimplemented!"); - return false; - } - - template - inline bool touches( const TPoint& /*point*/, - const RawShape& /*shape*/) - { - static_assert(always_false::value, - "ShapeLike::touches(point, shape) unimplemented!"); - return false; - } - - template - inline _Box> boundingBox(const RawShape& /*sh*/, - const PolygonTag&) - { - static_assert(always_false::value, - "ShapeLike::boundingBox(shape) unimplemented!"); - } - - template - inline _Box> - boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) - { - static_assert(always_false::value, - "ShapeLike::boundingBox(shapes) unimplemented!"); - } - - template - inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) - { - static_assert(always_false::value, - "ShapeLike::convexHull(shape) unimplemented!"); - return RawShape(); - } - - template - inline typename RawShapes::value_type - convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) - { - static_assert(always_false::value, - "ShapeLike::convexHull(shapes) unimplemented!"); - return typename RawShapes::value_type(); - } - - template - inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) - { - static_assert(always_false::value, - "ShapeLike::rotate() unimplemented!"); - } - - template - inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) - { - static_assert(always_false::value, - "ShapeLike::translate() unimplemented!"); - } - - template - inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) - { - dout() << "The current geometry backend does not support offsetting!\n"; - } - - template - inline std::pair isValid(const RawShape& /*sh*/) - { - return {false, "ShapeLike::isValid() unimplemented!"}; - } - - template - inline bool isConvex(const TContour& sh) - { - using Vertex = TPoint; - auto first = sh.begin(); - auto middle = std::next(first); - auto last = std::next(middle); - using CVrRef = const Vertex&; - - auto zcrossproduct = [](CVrRef k, CVrRef k1, CVrRef k2) { - auto dx1 = getX(k1) - getX(k); - auto dy1 = getY(k1) - getY(k); - auto dx2 = getX(k2) - getX(k1); - auto dy2 = getY(k2) - getY(k1); - return dx1*dy2 - dy1*dx2; - }; - - auto firstprod = zcrossproduct( *(std::prev(std::prev(sh.end()))), - *first, - *middle ); - - bool ret = true; - bool frsign = firstprod > 0; - while(last != sh.end()) { - auto &k = *first, &k1 = *middle, &k2 = *last; - auto zc = zcrossproduct(k, k1, k2); - ret &= frsign == (zc > 0); - ++first; ++middle; ++last; - } - - return ret; - } - - // ************************************************************************* - // No need to implement these - // ************************************************************************* - - template - inline Box boundingBox(const Box& box, const BoxTag& ) - { - return box; - } - - template - inline _Box boundingBox( - const Circle& circ, const CircleTag&) - { - using Point = typename Circle::PointType; - using Coord = TCoord; - Point pmin = { - static_cast(getX(circ.center()) - circ.radius()), - static_cast(getY(circ.center()) - circ.radius()) }; - - Point pmax = { - static_cast(getX(circ.center()) + circ.radius()), - static_cast(getY(circ.center()) + circ.radius()) }; - - return {pmin, pmax}; - } - - template // Dispatch function - inline _Box> boundingBox(const S& sh) - { - return boundingBox(sh, Tag() ); - } - - template - inline double area(const Box& box, const BoxTag& ) - { - return box.area(); - } - - template - inline double area(const Circle& circ, const CircleTag& ) - { - return circ.area(); - } - - template // Dispatching function - inline double area(const RawShape& sh) - { - return area(sh, Tag()); - } - - template - inline double area(const Shapes& shapes) - { - return std::accumulate(shapes.begin(), shapes.end(), 0.0, - [](double a, const RawShape& b) { - return a += area(b); - }); - } - - template - inline auto convexHull(const RawShape& sh) - -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce - { - return convexHull(sh, Tag()); - } - - template - inline bool isInside(const TPoint& point, - const _Circle>& circ) - { - return pointlike::distance(point, circ.center()) < circ.radius(); - } - - template - inline bool isInside(const TPoint& point, - const _Box>& box) - { - auto px = getX(point); - auto py = getY(point); - auto minx = getX(box.minCorner()); - auto miny = getY(box.minCorner()); - auto maxx = getX(box.maxCorner()); - auto maxy = getY(box.maxCorner()); - - return px > minx && px < maxx && py > miny && py < maxy; - } - - template - inline bool isInside(const RawShape& sh, - const _Circle>& circ) - { - return std::all_of(cbegin(sh), cend(sh), - [&circ](const TPoint& p){ - return isInside(p, circ); - }); - } - - template - inline bool isInside(const _Box>& box, - const _Circle>& circ) - { - return isInside(box.minCorner(), circ) && - isInside(box.maxCorner(), circ); - } - - template - inline bool isInside(const _Box>& ibb, - const _Box>& box) - { - auto iminX = getX(ibb.minCorner()); - auto imaxX = getX(ibb.maxCorner()); - auto iminY = getY(ibb.minCorner()); - auto imaxY = getY(ibb.maxCorner()); - - auto minX = getX(box.minCorner()); - auto maxX = getX(box.maxCorner()); - auto minY = getY(box.minCorner()); - auto maxY = getY(box.maxCorner()); - - return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; - } - - template // Potential O(1) implementation may exist - inline TPoint& vertex(RawShape& sh, unsigned long idx) - { - return *(begin(sh) + idx); - } - - template // Potential O(1) implementation may exist - inline const TPoint& vertex(const RawShape& sh, - unsigned long idx) - { - return *(cbegin(sh) + idx); - } - - template - inline size_t contourVertexCount(const RawShape& sh) - { - return cend(sh) - cbegin(sh); - } - - template - inline void foreachContourVertex(RawShape& sh, Fn fn) { - for(auto it = begin(sh); it != end(sh); ++it) fn(*it); - } - - template - inline void foreachHoleVertex(RawShape& sh, Fn fn) { - for(int i = 0; i < holeCount(sh); ++i) { - auto& h = getHole(sh, i); - for(auto it = begin(h); it != end(h); ++it) fn(*it); - } - } - - template - inline void foreachContourVertex(const RawShape& sh, Fn fn) { - for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it); - } - - template - inline void foreachHoleVertex(const RawShape& sh, Fn fn) { - for(int i = 0; i < holeCount(sh); ++i) { - auto& h = getHole(sh, i); - for(auto it = cbegin(h); it != cend(h); ++it) fn(*it); - } - } - - template - inline void foreachVertex(RawShape& sh, Fn fn) { - foreachContourVertex(sh, fn); - foreachHoleVertex(sh, fn); - } - - template - inline void foreachVertex(const RawShape& sh, Fn fn) { - foreachContourVertex(sh, fn); - foreachHoleVertex(sh, fn); - } -} - -#define DECLARE_MAIN_TYPES(T) \ - using Polygon = T; \ - using Point = TPoint; \ - using Coord = TCoord; \ - using Contour = TContour; \ - using Box = _Box; \ - using Circle = _Circle; \ - using Segment = _Segment; \ - using Polygons = TMultiShape - -} - -#endif // GEOMETRY_TRAITS_HPP diff --git a/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/libnest2d/geometry_traits_nfp.hpp deleted file mode 100644 index 2982454cd..000000000 --- a/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ /dev/null @@ -1,558 +0,0 @@ -#ifndef GEOMETRIES_NOFITPOLYGON_HPP -#define GEOMETRIES_NOFITPOLYGON_HPP - -#include "geometry_traits.hpp" -#include -#include -#include -#include - -namespace libnest2d { - -namespace __nfp { -// Do not specialize this... -template -inline bool _vsort(const TPoint& v1, const TPoint& v2) -{ - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; -} -} - -/// A collection of static methods for handling the no fit polygon creation. -namespace nfp { - -//namespace sl = shapelike; -//namespace pl = pointlike; - -/// The complexity level of a polygon that an NFP implementation can handle. -enum class NfpLevel: unsigned { - CONVEX_ONLY, - ONE_CONVEX, - BOTH_CONCAVE, - ONE_CONVEX_WITH_HOLES, - BOTH_CONCAVE_WITH_HOLES -}; - -template -using NfpResult = std::pair>; - -template struct MaxNfpLevel { - static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; -}; - - -// Shorthand for a pile of polygons -template -using Shapes = TMultiShape; - -/** - * Merge a bunch of polygons with the specified additional polygon. - * - * \tparam RawShape the Polygon data type. - * \param shc The pile of polygons that will be unified with sh. - * \param sh A single polygon to unify with shc. - * - * \return A set of polygons that is the union of the input polygons. Note that - * mostly it will be a set containing only one big polygon but if the input - * polygons are disjuct than the resulting set will contain more polygons. - */ -template -inline RawShapes merge(const RawShapes& /*shc*/) -{ - static_assert(always_false::value, - "Nfp::merge(shapes, shape) unimplemented!"); -} - -/** - * Merge a bunch of polygons with the specified additional polygon. - * - * \tparam RawShape the Polygon data type. - * \param shc The pile of polygons that will be unified with sh. - * \param sh A single polygon to unify with shc. - * - * \return A set of polygons that is the union of the input polygons. Note that - * mostly it will be a set containing only one big polygon but if the input - * polygons are disjuct than the resulting set will contain more polygons. - */ -template -inline TMultiShape merge(const TMultiShape& shc, - const RawShape& sh) -{ - auto m = nfp::merge(shc); - m.push_back(sh); - return nfp::merge(m); -} - -/** - * Get the vertex of the polygon that is at the lowest values (bottom) in the Y - * axis and if there are more than one vertices on the same Y coordinate than - * the result will be the leftmost (with the highest X coordinate). - */ -template -inline TPoint leftmostDownVertex(const RawShape& sh) -{ - - // find min x and min y vertex - auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), - __nfp::_vsort); - - return it == shapelike::cend(sh) ? TPoint() : *it;; -} - -/** - * Get the vertex of the polygon that is at the highest values (top) in the Y - * axis and if there are more than one vertices on the same Y coordinate than - * the result will be the rightmost (with the lowest X coordinate). - */ -template -TPoint rightmostUpVertex(const RawShape& sh) -{ - - // find max x and max y vertex - auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), - __nfp::_vsort); - - return it == shapelike::cend(sh) ? TPoint() : *it; -} - -/** - * A method to get a vertex from a polygon that always maintains a relative - * position to the coordinate system: It is always the rightmost top vertex. - * - * This way it does not matter in what order the vertices are stored, the - * reference will be always the same for the same polygon. - */ -template -inline TPoint referenceVertex(const RawShape& sh) -{ - return rightmostUpVertex(sh); -} - -/** - * The "trivial" Cuninghame-Green implementation of NFP for convex polygons. - * - * You can use this even if you provide implementations for the more complex - * cases (Through specializing the the NfpImpl struct). Currently, no other - * cases are covered in the library. - * - * Complexity should be no more than linear in the number of edges of the input - * polygons. - * - * \tparam RawShape the Polygon data type. - * \param sh The stationary polygon - * \param cother The orbiting polygon - * \return Returns a pair of the NFP and its reference vertex of the two input - * polygons which have to be strictly convex. The resulting NFP is proven to be - * convex as well in this case. - * - */ -template -inline NfpResult nfpConvexOnly(const RawShape& sh, - const RawShape& other) -{ - using Vertex = TPoint; using Edge = _Segment; - namespace sl = shapelike; - - RawShape rsh; // Final nfp placeholder - Vertex top_nfp; - std::vector edgelist; - - auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other); - - // Reserve the needed memory - edgelist.reserve(cap); - sl::reserve(rsh, static_cast(cap)); - - { // place all edges from sh into edgelist - auto first = sl::cbegin(sh); - auto next = std::next(first); - - while(next != sl::cend(sh)) { - edgelist.emplace_back(*(first), *(next)); - ++first; ++next; - } - } - - { // place all edges from other into edgelist - auto first = sl::cbegin(other); - auto next = std::next(first); - - while(next != sl::cend(other)) { - edgelist.emplace_back(*(next), *(first)); - ++first; ++next; - } - } - - // Sort the edges by angle to X axis. - std::sort(edgelist.begin(), edgelist.end(), - [](const Edge& e1, const Edge& e2) - { - return e1.angleToXaxis() > e2.angleToXaxis(); - }); - - // Add the two vertices from the first edge into the final polygon. - sl::addVertex(rsh, edgelist.front().first()); - sl::addVertex(rsh, edgelist.front().second()); - - // Sorting function for the nfp reference vertex search - auto& cmp = __nfp::_vsort; - - // the reference (rightmost top) vertex so far - top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); - - auto tmp = std::next(sl::begin(rsh)); - - // Construct final nfp by placing each edge to the end of the previous - for(auto eit = std::next(edgelist.begin()); - eit != edgelist.end(); - ++eit) - { - auto d = *tmp - eit->first(); - Vertex p = eit->second() + d; - - sl::addVertex(rsh, p); - - // Set the new reference vertex - if(cmp(top_nfp, p)) top_nfp = p; - - tmp = std::next(tmp); - } - - return {rsh, top_nfp}; -} - -template -NfpResult nfpSimpleSimple(const RawShape& cstationary, - const RawShape& cother) -{ - - // Algorithms are from the original algorithm proposed in paper: - // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 1: Obtaining the minkowski sum - // ///////////////////////////////////////////////////////////////////////// - - // I guess this is not a full minkowski sum of the two input polygons by - // definition. This yields a subset that is compatible with the next 2 - // algorithms. - - using Result = NfpResult; - using Vertex = TPoint; - using Coord = TCoord; - using Edge = _Segment; - namespace sl = shapelike; - using std::signbit; - using std::sort; - using std::vector; - using std::ref; - using std::reference_wrapper; - - // TODO The original algorithms expects the stationary polygon in - // counter clockwise and the orbiter in clockwise order. - // So for preventing any further complication, I will make the input - // the way it should be, than make my way around the orientations. - - // Reverse the stationary contour to counter clockwise - auto stcont = sl::getContour(cstationary); - std::reverse(stcont.begin(), stcont.end()); - RawShape stationary; - sl::getContour(stationary) = stcont; - - // Reverse the orbiter contour to counter clockwise - auto orbcont = sl::getContour(cother); - - std::reverse(orbcont.begin(), orbcont.end()); - - // Copy the orbiter (contour only), we will have to work on it - RawShape orbiter; - sl::getContour(orbiter) = orbcont; - - // Step 1: Make the orbiter reverse oriented - for(auto &v : sl::getContour(orbiter)) v = -v; - - // An egde with additional data for marking it - struct MarkedEdge { - Edge e; Radians turn_angle = 0; bool is_turning_point = false; - MarkedEdge() = default; - MarkedEdge(const Edge& ed, Radians ta, bool tp): - e(ed), turn_angle(ta), is_turning_point(tp) {} - }; - - // Container for marked edges - using EdgeList = vector; - - EdgeList A, B; - - // This is how an edge list is created from the polygons - auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) { - L.reserve(sl::contourVertexCount(poly)); - - auto it = sl::cbegin(poly); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != sl::cend(poly)) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - - auto getTurnAngle = [](const Edge& e1, const Edge& e2) { - auto phi = e1.angleToXaxis(); - auto phi_prev = e2.angleToXaxis(); - auto TwoPi = 2.0*Pi; - if(phi > Pi) phi -= TwoPi; - if(phi_prev > Pi) phi_prev -= TwoPi; - auto turn_angle = phi-phi_prev; - if(turn_angle > Pi) turn_angle -= TwoPi; - return phi-phi_prev; - }; - - if(dir > 0) { - auto eit = L.begin(); - auto enext = std::next(eit); - - eit->turn_angle = getTurnAngle(L.front().e, L.back().e); - - while(enext != L.end()) { - enext->turn_angle = getTurnAngle( enext->e, eit->e); - enext->is_turning_point = - signbit(enext->turn_angle) != signbit(eit->turn_angle); - ++eit; ++enext; - } - - L.front().is_turning_point = signbit(L.front().turn_angle) != - signbit(L.back().turn_angle); - } else { - std::cout << L.size() << std::endl; - - auto eit = L.rbegin(); - auto enext = std::next(eit); - - eit->turn_angle = getTurnAngle(L.back().e, L.front().e); - - while(enext != L.rend()) { - enext->turn_angle = getTurnAngle(enext->e, eit->e); - enext->is_turning_point = - signbit(enext->turn_angle) != signbit(eit->turn_angle); - std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl; - - ++eit; ++enext; - } - - L.back().is_turning_point = signbit(L.back().turn_angle) != - signbit(L.front().turn_angle); - } - }; - - // Step 2: Fill the edgelists - fillEdgeList(A, stationary, 1); - fillEdgeList(B, orbiter, -1); - - // A reference to a marked edge that also knows its container - struct MarkedEdgeRef { - reference_wrapper eref; - reference_wrapper> container; - Coord dir = 1; // Direction modifier - - inline Radians angleX() const { return eref.get().e.angleToXaxis(); } - inline const Edge& edge() const { return eref.get().e; } - inline Edge& edge() { return eref.get().e; } - inline bool isTurningPoint() const { - return eref.get().is_turning_point; - } - inline bool isFrom(const vector& cont ) { - return &(container.get()) == &cont; - } - inline bool eq(const MarkedEdgeRef& mr) { - return &(eref.get()) == &(mr.eref.get()); - } - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec): - eref(er), container(ec), dir(1) {} - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec, - Coord d): - eref(er), container(ec), dir(d) {} - }; - - using EdgeRefList = vector; - - // Comparing two marked edges - auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { - return e1.angleX() < e2.angleX(); - }; - - EdgeRefList Aref, Bref; // We create containers for the references - Aref.reserve(A.size()); Bref.reserve(B.size()); - - // Fill reference container for the stationary polygon - std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { - Aref.emplace_back( ref(me), ref(Aref) ); - }); - - // Fill reference container for the orbiting polygon - std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { - Bref.emplace_back( ref(me), ref(Bref) ); - }); - - struct EdgeGroup { typename EdgeRefList::const_iterator first, last; }; - - auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure - (const EdgeGroup& Q, const EdgeGroup& R, bool positive) - { - - // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" - // Sort the containers of edge references and merge them. - // Q could be sorted only once and be reused here but we would still - // need to merge it with sorted(R). - - EdgeRefList merged; - EdgeRefList S, seq; - merged.reserve((Q.last - Q.first) + (R.last - R.first)); - - merged.insert(merged.end(), Q.first, Q.last); - merged.insert(merged.end(), R.first, R.last); - sort(merged.begin(), merged.end(), sortfn); - - // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" - // we dont use i, instead, q is an iterator into Q. k would be an index - // into the merged sequence but we use "it" as an iterator for that - - // here we obtain references for the containers for later comparisons - const auto& Rcont = R.first->container.get(); - const auto& Qcont = Q.first->container.get(); - - // Set the intial direction - Coord dir = positive? 1 : -1; - - // roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q; - auto q = Q.first; - S.push_back(*q++); - - // Roughly step 3 - while(q != Q.last) { - auto it = merged.begin(); - while(it != merged.end() && !(it->eq(*(Q.first))) ) { - if(it->isFrom(Rcont)) { - auto s = *it; - s.dir = dir; - S.push_back(s); - } - if(it->eq(*q)) { - S.push_back(*q); - if(it->isTurningPoint()) dir = -dir; - if(q != Q.first) it += dir; - } - else it += dir; - } - ++q; // "Set i = i + 1" - } - - // Step 4: - - // "Let starting edge r1 be in position si in sequence" - // whaaat? I guess this means the following: - S[0] = *R.first; - auto it = S.begin(); - - // "Set j = 1, next = 2, direction = 1, seq1 = si" - // we dont use j, seq is expanded dynamically. - dir = 1; auto next = std::next(R.first); - - // Step 5: - // "If all si edges have been allocated to seqj" should mean that - // we loop until seq has equal size with S - while(seq.size() < S.size()) { - ++it; if(it == S.end()) it = S.begin(); - - if(it->isFrom(Qcont)) { - seq.push_back(*it); // "If si is from Q, j = j + 1, seqj = si" - - // "If si is a turning point in Q, - // direction = - direction, next = next + direction" - if(it->isTurningPoint()) { dir = -dir; next += dir; } - } - - if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext" - // "j = j + 1, seqj = si, next = next + direction" - seq.push_back(*it); next += dir; - } - } - - return seq; - }; - - EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() }; - auto it = Bref.begin(); - bool orientation = true; - EdgeRefList seqlist; - seqlist.reserve(3*(Aref.size() + Bref.size())); - - while(it != Bref.end()) // This is step 3 and step 4 in one loop - if(it->isTurningPoint()) { - R = {R.last, it++}; - auto seq = mink(Q, R, orientation); - - // TODO step 6 (should be 5 shouldn't it?): linking edges from A - // I don't get this step - - seqlist.insert(seqlist.end(), seq.begin(), seq.end()); - orientation = !orientation; - } else ++it; - - if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 2: breaking Minkowski sums into track line trips - // ///////////////////////////////////////////////////////////////////////// - - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 3: finding the boundary of the NFP from track line trips - // ///////////////////////////////////////////////////////////////////////// - - - - return Result(stationary, Vertex()); -} - -// Specializable NFP implementation class. Specialize it if you have a faster -// or better NFP implementation -template -struct NfpImpl { - NfpResult operator()(const RawShape& sh, const RawShape& other) - { - static_assert(nfptype == NfpLevel::CONVEX_ONLY, - "Nfp::noFitPolygon() unimplemented!"); - - // Libnest2D has a default implementation for convex polygons and will - // use it if feasible. - return nfpConvexOnly(sh, other); - } -}; - -/// Helper function to get the NFP -template -inline NfpResult noFitPolygon(const RawShape& sh, - const RawShape& other) -{ - NfpImpl nfps; - return nfps(sh, other); -} - -} - -} - -#endif // GEOMETRIES_NOFITPOLYGON_HPP diff --git a/src/libnest2d/tests/CMakeLists.txt b/src/libnest2d/tests/CMakeLists.txt index 3777f3c56..fc3cd309d 100644 --- a/src/libnest2d/tests/CMakeLists.txt +++ b/src/libnest2d/tests/CMakeLists.txt @@ -3,7 +3,10 @@ find_package(GTest 1.7) if(NOT GTEST_FOUND) - message(STATUS "GTest not found so downloading...") + set(URL_GTEST "https://github.com/google/googletest.git" + CACHE STRING "Google test source code repository location.") + + message(STATUS "GTest not found so downloading from ${URL_GTEST}") # Go and download google test framework, integrate it with the build set(GTEST_LIBS_TO_LINK gtest gtest_main) @@ -15,7 +18,7 @@ if(NOT GTEST_FOUND) include(DownloadProject) download_project(PROJ googletest - GIT_REPOSITORY https://github.com/google/googletest.git + GIT_REPOSITORY ${URL_GTEST} GIT_TAG release-1.7.0 ${UPDATE_DISCONNECTED_IF_AVAILABLE} ) @@ -35,17 +38,18 @@ else() set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads) endif() -add_executable(bp2d_tests test.cpp - ../tools/svgtools.hpp -# ../tools/libnfpglue.hpp -# ../tools/libnfpglue.cpp - printer_parts.h - printer_parts.cpp - ${LIBNEST2D_SRCFILES} - ) -target_link_libraries(bp2d_tests ${LIBNEST2D_LIBRARIES} ${GTEST_LIBS_TO_LINK} ) +add_executable(tests_clipper_nlopt + test.cpp + ../tools/svgtools.hpp +# ../tools/libnfpglue.hpp +# ../tools/libnfpglue.cpp + printer_parts.h + printer_parts.cpp +) -target_include_directories(bp2d_tests PRIVATE BEFORE ${LIBNEST2D_HEADERS} - ${GTEST_INCLUDE_DIRS}) +target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) -add_test(libnest2d_tests bp2d_tests) +target_include_directories(tests_clipper_nlopt PRIVATE BEFORE + ${GTEST_INCLUDE_DIRS}) + +add_test(libnest2d_tests tests_clipper_nlopt) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 323fb8d31..3b0eae161 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -4,6 +4,7 @@ #include #include "printer_parts.h" #include +#include "../tools/svgtools.hpp" //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" @@ -125,7 +126,7 @@ TEST(GeometryAlgorithms, boundingCircle) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - else for(auto v : shapelike::getContour(part.transformedShape()) ) { + else for(auto v : shapelike::contour(part.transformedShape()) ) { auto d = pointlike::distance(v, c.center()); if(d > c.radius() ) { auto e = std::abs( 1.0 - d/c.radius()); @@ -788,15 +789,6 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { // testNfp(nfp_concave_testdata); //} -TEST(GeometryAlgorithms, nfpConcaveConcave) { - using namespace libnest2d; - -// Rectangle r1(10, 10); -// Rectangle r2(20, 20); -// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(), -// r2.transformedShape()); -} - TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; diff --git a/src/libnest2d/tools/libnfpglue.cpp b/src/libnest2d/tools/libnfpglue.cpp deleted file mode 100644 index 31733acf9..000000000 --- a/src/libnest2d/tools/libnfpglue.cpp +++ /dev/null @@ -1,157 +0,0 @@ -//#ifndef NDEBUG -//#define NFP_DEBUG -//#endif - -#include "libnfpglue.hpp" -#include "tools/libnfporb/libnfporb.hpp" - -namespace libnest2d { - -namespace { -inline bool vsort(const libnfporb::point_t& v1, const libnfporb::point_t& v2) -{ - using Coord = libnfporb::coord_t; - Coord x1 = v1.x_, x2 = v2.x_, y1 = v1.y_, y2 = v2.y_; - auto diff = y1 - y2; -#ifdef LIBNFP_USE_RATIONAL - long double diffv = diff.convert_to(); -#else - long double diffv = diff.val(); -#endif - if(std::abs(diffv) <= - std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; -} - -TCoord getX(const libnfporb::point_t& p) { -#ifdef LIBNFP_USE_RATIONAL - return p.x_.convert_to>(); -#else - return static_cast>(std::round(p.x_.val())); -#endif -} - -TCoord getY(const libnfporb::point_t& p) { -#ifdef LIBNFP_USE_RATIONAL - return p.y_.convert_to>(); -#else - return static_cast>(std::round(p.y_.val())); -#endif -} - -libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { -#ifdef LIBNFP_USE_RATIONAL - auto px = p.x_.convert_to(); - auto py = p.y_.convert_to(); -#else - long double px = p.x_.val(); - long double py = p.y_.val(); -#endif - return {px*factor, py*factor}; -} - -} - -NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) -{ - namespace sl = shapelike; - - NfpR ret; - - try { - libnfporb::polygon_t pstat, porb; - - boost::geometry::convert(sh, pstat); - boost::geometry::convert(cother, porb); - - long double factor = 0.0000001;//libnfporb::NFP_EPSILON; - long double refactor = 1.0/factor; - - for(auto& v : pstat.outer()) v = scale(v, factor); -// std::string message; -// boost::geometry::is_valid(pstat, message); -// std::cout << message << std::endl; - for(auto& h : pstat.inners()) for(auto& v : h) v = scale(v, factor); - - for(auto& v : porb.outer()) v = scale(v, factor); -// message; -// boost::geometry::is_valid(porb, message); -// std::cout << message << std::endl; - for(auto& h : porb.inners()) for(auto& v : h) v = scale(v, factor); - - - // this can throw - auto nfp = libnfporb::generateNFP(pstat, porb, true); - - auto &ct = sl::getContour(ret.first); - ct.reserve(nfp.front().size()+1); - for(auto v : nfp.front()) { - v = scale(v, refactor); - ct.emplace_back(getX(v), getY(v)); - } - ct.push_back(ct.front()); - std::reverse(ct.begin(), ct.end()); - - auto &rholes = sl::holes(ret.first); - for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { - if(nfp[hidx].size() >= 3) { - rholes.emplace_back(); - auto& h = rholes.back(); - h.reserve(nfp[hidx].size()+1); - - for(auto& v : nfp[hidx]) { - v = scale(v, refactor); - h.emplace_back(getX(v), getY(v)); - } - h.push_back(h.front()); - std::reverse(h.begin(), h.end()); - } - } - - ret.second = nfp::referenceVertex(ret.first); - - } catch(std::exception& e) { - std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; -// auto ch_stat = ShapeLike::convexHull(sh); -// auto ch_orb = ShapeLike::convexHull(cother); - ret = nfp::nfpConvexOnly(sh, cother); - } - - return ret; -} - -NfpR nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother);//nfpConvexOnly(sh, cother); -} - -NfpR nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} - -NfpR nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} - -//PolygonImpl -//Nfp::NfpImpl::operator()( -// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -//{ -// return _nfp(sh, cother); -//} - -//PolygonImpl -//Nfp::NfpImpl::operator()( -// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -//{ -// return _nfp(sh, cother); -//} - -} diff --git a/src/libnest2d/tools/libnfpglue.hpp b/src/libnest2d/tools/libnfpglue.hpp deleted file mode 100644 index 1ff033cb9..000000000 --- a/src/libnest2d/tools/libnfpglue.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef LIBNFPGLUE_HPP -#define LIBNFPGLUE_HPP - -#include - -namespace libnest2d { - -using NfpR = nfp::NfpResult; - -NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); - -template<> -struct nfp::NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; - -template<> -struct nfp::NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; - -template<> -struct nfp::NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; - -//template<> -//struct Nfp::NfpImpl { -// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); -//}; - -//template<> -//struct Nfp::NfpImpl { -// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); -//}; - -template<> struct nfp::MaxNfpLevel { - static const BP2D_CONSTEXPR NfpLevel value = -// NfpLevel::CONVEX_ONLY; - NfpLevel::BOTH_CONCAVE; -}; - -} - - -#endif // LIBNFPGLUE_HPP diff --git a/src/libnest2d/tools/libnfporb/LICENSE b/src/libnest2d/tools/libnfporb/LICENSE deleted file mode 100644 index 94a9ed024..000000000 --- a/src/libnest2d/tools/libnfporb/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - 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/src/libnest2d/tools/libnfporb/ORIGIN b/src/libnest2d/tools/libnfporb/ORIGIN deleted file mode 100644 index 788bfd9af..000000000 --- a/src/libnest2d/tools/libnfporb/ORIGIN +++ /dev/null @@ -1,2 +0,0 @@ -https://github.com/kallaballa/libnfp.git -commit hash a5cf9f6a76ddab95567fccf629d4d099b60237d7 \ No newline at end of file diff --git a/src/libnest2d/tools/libnfporb/README.md b/src/libnest2d/tools/libnfporb/README.md deleted file mode 100644 index 9698972be..000000000 --- a/src/libnest2d/tools/libnfporb/README.md +++ /dev/null @@ -1,89 +0,0 @@ -[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) -##### If you give me a real good reason i might be willing to give you permission to use it under a different license for a specific application. Real good reasons include the following (non-exhausive): the greater good, educational purpose and money :) - -# libnfporb -Implementation of a robust no-fit polygon generation in a C++ library using an orbiting approach. - -__Please note:__ The paper this implementation is based it on has several bad assumptions that required me to "improvise". That means the code doesn't reflect the paper anymore and is running way slower than expected. At the moment I'm working on implementing a new approach based on this paper (using minkowski sums): https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - -## Description - -The no-fit polygon optimization makes it possible to check for overlap (or non-overlapping touch) of two polygons with only 1 point in polygon check (by providing the set of non-overlapping placements). -This library implements the orbiting approach to generate the no-fit polygon: Given two polygons A and B, A is the stationary one and B the orbiting one, B is slid as tightly as possibly around the edges of polygon A. During the orbiting a chosen reference point is tracked. By tracking the movement of the reference point a third polygon can be generated: the no-fit polygon. - -Once the no-fit polygon has been generated it can be used to test for overlap by only checking if the reference point is inside the NFP (overlap) outside the NFP (no overlap) or exactly on the edge of the NFP (touch). - -### Examples: - -The polygons: - -![Start of NFP](/images/start.png?raw=true) - -Orbiting: - -![State 1](/images/next0.png?raw=true) -![State 2](/images/next1.png?raw=true) -![State 3](/images/next2.png?raw=true) -![State 4](/images/next3.png?raw=true) - -![State 5](/images/next4.png?raw=true) -![State 6](/images/next5.png?raw=true) -![State 7](/images/next6.png?raw=true) -![State 8](/images/next7.png?raw=true) - -![State 9](/images/next8.png?raw=true) - -The resulting NFP is red: - -![nfp](/images/nfp.png?raw=true) - -Polygons can have concavities, holes, interlocks or might fit perfectly: - -![concavities](/images/concavities.png?raw=true) -![hole](/images/hole.png?raw=true) -![interlock](/images/interlock.png?raw=true) -![jigsaw](/images/jigsaw.png?raw=true) - -## The Approach -The approch of this library is highly inspired by the scientific paper [Complete and robust no-fit polygon generation -for the irregular stock cutting problem](https://pdfs.semanticscholar.org/e698/0dd78306ba7d5bb349d20c6d8f2e0aa61062.pdf) and by [Svgnest](http://svgnest.com) - -Note that is wasn't completely possible to implement it as suggested in the paper because it had several shortcomings that prevent complete NFP generation on some of my test cases. Especially the termination criteria (reference point returns to first point of NFP) proved to be wrong (see: test-case rect). Also tracking of used edges can't be performed as suggested in the paper since there might be situations where no edge of A is traversed (see: test-case doublecon). - -By default the library is using floating point as coordinate type but by defining the flag "LIBNFP_USE_RATIONAL" the library can be instructed to use infinite precision. - -## Build -The library has two dependencies: [Boost Geometry](http://www.boost.org/doc/libs/1_65_1/libs/geometry/doc/html/index.html) and [libgmp](https://gmplib.org). You need to install those first before building. Note that building is only required for the examples. The library itself is header-only. - - git clone https://github.com/kallaballa/libnfp.git - cd libnfp - make - sudo make install - -## Code Example - -```c++ -//uncomment next line to use infinite precision (slow) -//#define LIBNFP_USE_RATIONAL -#include "../src/libnfp.hpp" - -int main(int argc, char** argv) { - using namespace libnfp; - polygon_t pA; - polygon_t pB; - //read polygons from wkt files - read_wkt_polygon(argv[1], pA); - read_wkt_polygon(argv[2], pB); - - //generate NFP of polygon A and polygon B and check the polygons for validity. - //When the third parameters is false validity check is skipped for a little performance increase - nfp_t nfp = generateNFP(pA, pB, true); - - //write a svg containing pA, pB and NFP - write_svg("nfp.svg",{pA,pB},nfp); - return 0; -} -``` -Run the example program: - - examples/nfp data/crossing/A.wkt data/crossing/B.wkt diff --git a/src/libnest2d/tools/libnfporb/libnfporb.hpp b/src/libnest2d/tools/libnfporb/libnfporb.hpp deleted file mode 100644 index 8cb34567e..000000000 --- a/src/libnest2d/tools/libnfporb/libnfporb.hpp +++ /dev/null @@ -1,1547 +0,0 @@ -#ifndef NFP_HPP_ -#define NFP_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L - #define LIBNFP_NOEXCEPT - #define LIBNFP_CONSTEXPR -#elif __cplusplus >= 201103L - #define LIBNFP_NOEXCEPT noexcept - #define LIBNFP_CONSTEXPR constexpr -#endif - -#ifdef LIBNFP_USE_RATIONAL -#include -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef LIBNFP_USE_RATIONAL -namespace bm = boost::multiprecision; -#endif -namespace bg = boost::geometry; -namespace trans = boost::geometry::strategy::transform; - - -namespace libnfporb { -#ifdef NFP_DEBUG -#define DEBUG_VAL(x) std::cerr << x << std::endl; -#define DEBUG_MSG(title, value) std::cerr << title << ":" << value << std::endl; -#else -#define DEBUG_VAL(x) -#define DEBUG_MSG(title, value) -#endif - -using std::string; - -static LIBNFP_CONSTEXPR long double NFP_EPSILON=0.00000001; - -class LongDouble { -private: - long double val_; -public: - LongDouble() : val_(0) { - } - - LongDouble(const long double& val) : val_(val) { - } - - void setVal(const long double& v) { - val_ = v; - } - - long double val() const { - return val_; - } - - LongDouble operator/(const LongDouble& other) const { - return this->val_ / other.val_; - } - - LongDouble operator*(const LongDouble& other) const { - return this->val_ * other.val_; - } - - LongDouble operator-(const LongDouble& other) const { - return this->val_ - other.val_; - } - - LongDouble operator-() const { - return this->val_ * -1; - } - - LongDouble operator+(const LongDouble& other) const { - return this->val_ + other.val_; - } - - void operator/=(const LongDouble& other) { - this->val_ = this->val_ / other.val_; - } - - void operator*=(const LongDouble& other) { - this->val_ = this->val_ * other.val_; - } - - void operator-=(const LongDouble& other) { - this->val_ = this->val_ - other.val_; - } - - void operator+=(const LongDouble& other) { - this->val_ = this->val_ + other.val_; - } - - bool operator==(const int& other) const { - return this->operator ==(static_cast(other)); - } - - bool operator==(const LongDouble& other) const { - return this->operator ==(other.val()); - } - - bool operator==(const long double& other) const { - return this->val() == other; - } - - bool operator!=(const int& other) const { - return !this->operator ==(other); - } - - bool operator!=(const LongDouble& other) const { - return !this->operator ==(other); - } - - bool operator!=(const long double& other) const { - return !this->operator ==(other); - } - - bool operator<(const int& other) const { - return this->operator <(static_cast(other)); - } - - bool operator<(const LongDouble& other) const { - return this->operator <(other.val()); - } - - bool operator<(const long double& other) const { - return this->val() < other; - } - - bool operator>(const int& other) const { - return this->operator >(static_cast(other)); - } - - bool operator>(const LongDouble& other) const { - return this->operator >(other.val()); - } - - bool operator>(const long double& other) const { - return this->val() > other; - } - - bool operator>=(const int& other) const { - return this->operator >=(static_cast(other)); - } - - bool operator>=(const LongDouble& other) const { - return this->operator >=(other.val()); - } - - bool operator>=(const long double& other) const { - return this->val() >= other; - } - - bool operator<=(const int& other) const { - return this->operator <=(static_cast(other)); - } - - bool operator<=(const LongDouble& other) const { - return this->operator <=(other.val()); - } - - bool operator<=(const long double& other) const { - return this->val() <= other; - } -}; -} - - -namespace std { -template<> - struct numeric_limits - { - static const LIBNFP_CONSTEXPR bool is_specialized = true; - - static const LIBNFP_CONSTEXPR long double - min() LIBNFP_NOEXCEPT { return std::numeric_limits::min(); } - - static LIBNFP_CONSTEXPR long double - max() LIBNFP_NOEXCEPT { return std::numeric_limits::max(); } - -#if __cplusplus >= 201103L - static LIBNFP_CONSTEXPR long double - lowest() LIBNFP_NOEXCEPT { return -std::numeric_limits::lowest(); } -#endif - - static const LIBNFP_CONSTEXPR int digits = std::numeric_limits::digits; - static const LIBNFP_CONSTEXPR int digits10 = std::numeric_limits::digits10; -#if __cplusplus >= 201103L - static const LIBNFP_CONSTEXPR int max_digits10 - = std::numeric_limits::max_digits10; -#endif - static const LIBNFP_CONSTEXPR bool is_signed = true; - static const LIBNFP_CONSTEXPR bool is_integer = false; - static const LIBNFP_CONSTEXPR bool is_exact = false; - static const LIBNFP_CONSTEXPR int radix = std::numeric_limits::radix; - - static const LIBNFP_CONSTEXPR long double - epsilon() LIBNFP_NOEXCEPT { return libnfporb::NFP_EPSILON; } - - static const LIBNFP_CONSTEXPR long double - round_error() LIBNFP_NOEXCEPT { return 0.5L; } - - static const LIBNFP_CONSTEXPR int min_exponent = std::numeric_limits::min_exponent; - static const LIBNFP_CONSTEXPR int min_exponent10 = std::numeric_limits::min_exponent10; - static const LIBNFP_CONSTEXPR int max_exponent = std::numeric_limits::max_exponent; - static const LIBNFP_CONSTEXPR int max_exponent10 = std::numeric_limits::max_exponent10; - - - static const LIBNFP_CONSTEXPR bool has_infinity = std::numeric_limits::has_infinity; - static const LIBNFP_CONSTEXPR bool has_quiet_NaN = std::numeric_limits::has_quiet_NaN; - static const LIBNFP_CONSTEXPR bool has_signaling_NaN = has_quiet_NaN; - static const LIBNFP_CONSTEXPR float_denorm_style has_denorm - = std::numeric_limits::has_denorm; - static const LIBNFP_CONSTEXPR bool has_denorm_loss - = std::numeric_limits::has_denorm_loss; - - - static const LIBNFP_CONSTEXPR long double - infinity() LIBNFP_NOEXCEPT { return std::numeric_limits::infinity(); } - - static const LIBNFP_CONSTEXPR long double - quiet_NaN() LIBNFP_NOEXCEPT { return std::numeric_limits::quiet_NaN(); } - - static const LIBNFP_CONSTEXPR long double - signaling_NaN() LIBNFP_NOEXCEPT { return std::numeric_limits::signaling_NaN(); } - - - static const LIBNFP_CONSTEXPR long double - denorm_min() LIBNFP_NOEXCEPT { return std::numeric_limits::denorm_min(); } - - static const LIBNFP_CONSTEXPR bool is_iec559 - = has_infinity && has_quiet_NaN && has_denorm == denorm_present; - - static const LIBNFP_CONSTEXPR bool is_bounded = true; - static const LIBNFP_CONSTEXPR bool is_modulo = false; - - static const LIBNFP_CONSTEXPR bool traps = std::numeric_limits::traps; - static const LIBNFP_CONSTEXPR bool tinyness_before = - std::numeric_limits::tinyness_before; - static const LIBNFP_CONSTEXPR float_round_style round_style = - round_to_nearest; - }; -} - -namespace boost { -namespace numeric { - template<> - struct raw_converter> - { - typedef boost::numeric::conversion_traits::result_type result_type ; - typedef boost::numeric::conversion_traits::argument_type argument_type ; - - static result_type low_level_convert ( argument_type s ) { return s.val() ; } - } ; -} -} - -namespace libnfporb { - -#ifndef LIBNFP_USE_RATIONAL -typedef LongDouble coord_t; -#else -typedef bm::number rational_t; -typedef rational_t coord_t; -#endif - -bool equals(const LongDouble& lhs, const LongDouble& rhs); -#ifdef LIBNFP_USE_RATIONAL -bool equals(const rational_t& lhs, const rational_t& rhs); -#endif -bool equals(const long double& lhs, const long double& rhs); - -const coord_t MAX_COORD = 999999999999999999.0; -const coord_t MIN_COORD = std::numeric_limits::min(); - -class point_t { -public: - point_t() : x_(0), y_(0) { - } - point_t(coord_t x, coord_t y) : x_(x), y_(y) { - } - bool marked_ = false; - coord_t x_; - coord_t y_; - - point_t operator-(const point_t& other) const { - point_t result = *this; - bg::subtract_point(result, other); - return result; - } - - point_t operator+(const point_t& other) const { - point_t result = *this; - bg::add_point(result, other); - return result; - } - - bool operator==(const point_t& other) const { - return bg::equals(this, other); - } - - bool operator!=(const point_t& other) const { - return !this->operator ==(other); - } - - bool operator<(const point_t& other) const { - return boost::geometry::math::smaller(this->x_, other.x_) || (equals(this->x_, other.x_) && boost::geometry::math::smaller(this->y_, other.y_)); - } -}; - - - - -inline long double toLongDouble(const LongDouble& c) { - return c.val(); -} - -#ifdef LIBNFP_USE_RATIONAL -inline long double toLongDouble(const rational_t& c) { - return bm::numerator(c).convert_to() / bm::denominator(c).convert_to(); -} -#endif - -std::ostream& operator<<(std::ostream& os, const coord_t& p) { - os << toLongDouble(p); - return os; -} - -std::istream& operator>>(std::istream& is, LongDouble& c) { - long double val; - is >> val; - c.setVal(val); - return is; -} - -std::ostream& operator<<(std::ostream& os, const point_t& p) { - os << "{" << toLongDouble(p.x_) << "," << toLongDouble(p.y_) << "}"; - return os; -} -const point_t INVALID_POINT = {MAX_COORD, MAX_COORD}; - -typedef bg::model::segment segment_t; -} - -#ifdef LIBNFP_USE_RATIONAL -inline long double acos(const libnfporb::rational_t& r) { - return acos(libnfporb::toLongDouble(r)); -} -#endif - -inline long double acos(const libnfporb::LongDouble& ld) { - return acos(libnfporb::toLongDouble(ld)); -} - -#ifdef LIBNFP_USE_RATIONAL -inline long double sqrt(const libnfporb::rational_t& r) { - return sqrt(libnfporb::toLongDouble(r)); -} -#endif - -inline long double sqrt(const libnfporb::LongDouble& ld) { - return sqrt(libnfporb::toLongDouble(ld)); -} - -BOOST_GEOMETRY_REGISTER_POINT_2D(libnfporb::point_t, libnfporb::coord_t, cs::cartesian, x_, y_) - - -namespace boost { -namespace geometry { -namespace math { -namespace detail { - -template <> -struct square_root -{ - typedef libnfporb::LongDouble return_type; - - static inline libnfporb::LongDouble apply(libnfporb::LongDouble const& a) - { - return std::sqrt(a.val()); - } -}; - -#ifdef LIBNFP_USE_RATIONAL -template <> -struct square_root -{ - typedef libnfporb::rational_t return_type; - - static inline libnfporb::rational_t apply(libnfporb::rational_t const& a) - { - return std::sqrt(libnfporb::toLongDouble(a)); - } -}; -#endif - -template<> -struct abs - { - static libnfporb::LongDouble apply(libnfporb::LongDouble const& value) - { - libnfporb::LongDouble const zero = libnfporb::LongDouble(); - return value.val() < zero.val() ? -value.val() : value.val(); - } - }; - -template <> -struct equals -{ - template - static inline bool apply(libnfporb::LongDouble const& lhs, libnfporb::LongDouble const& rhs, Policy const& policy) - { - if(lhs.val() == rhs.val()) - return true; - - return bg::math::detail::abs::apply(lhs.val() - rhs.val()) <= policy.apply(lhs.val(), rhs.val()) * libnfporb::NFP_EPSILON; - } -}; - -template <> -struct smaller -{ - static inline bool apply(libnfporb::LongDouble const& lhs, libnfporb::LongDouble const& rhs) - { - if(lhs.val() == rhs.val() || bg::math::detail::abs::apply(lhs.val() - rhs.val()) <= libnfporb::NFP_EPSILON * std::max(lhs.val(), rhs.val())) - return false; - - return lhs < rhs; - } -}; -} -} -} -} - -namespace libnfporb { -inline bool smaller(const LongDouble& lhs, const LongDouble& rhs) { - return boost::geometry::math::detail::smaller::apply(lhs, rhs); -} - -inline bool larger(const LongDouble& lhs, const LongDouble& rhs) { - return smaller(rhs, lhs); -} - -bool equals(const LongDouble& lhs, const LongDouble& rhs) { - if(lhs.val() == rhs.val()) - return true; - - return bg::math::detail::abs::apply(lhs.val() - rhs.val()) <= libnfporb::NFP_EPSILON * std::max(lhs.val(), rhs.val()); -} - -#ifdef LIBNFP_USE_RATIONAL -inline bool smaller(const rational_t& lhs, const rational_t& rhs) { - return lhs < rhs; -} - -inline bool larger(const rational_t& lhs, const rational_t& rhs) { - return smaller(rhs, lhs); -} - -bool equals(const rational_t& lhs, const rational_t& rhs) { - return lhs == rhs; -} -#endif - -inline bool smaller(const long double& lhs, const long double& rhs) { - return lhs < rhs; -} - -inline bool larger(const long double& lhs, const long double& rhs) { - return smaller(rhs, lhs); -} - - -bool equals(const long double& lhs, const long double& rhs) { - return lhs == rhs; -} - -typedef bg::model::polygon polygon_t; -typedef std::vector nfp_t; -typedef bg::model::linestring linestring_t; - -typedef polygon_t::ring_type::size_type psize_t; - -typedef bg::model::d2::point_xy pointf_t; -typedef bg::model::segment segmentf_t; -typedef bg::model::polygon polygonf_t; - -polygonf_t::ring_type convert(const polygon_t::ring_type& r) { - polygonf_t::ring_type rf; - for(const auto& pt : r) { - rf.push_back(pointf_t(toLongDouble(pt.x_), toLongDouble(pt.y_))); - } - return rf; -} - -polygonf_t convert(polygon_t p) { - polygonf_t pf; - pf.outer() = convert(p.outer()); - - for(const auto& r : p.inners()) { - pf.inners().push_back(convert(r)); - } - - return pf; -} - -polygon_t nfpRingsToNfpPoly(const nfp_t& nfp) { - polygon_t nfppoly; - for (const auto& pt : nfp.front()) { - nfppoly.outer().push_back(pt); - } - - for (size_t i = 1; i < nfp.size(); ++i) { - nfppoly.inners().push_back({}); - for (const auto& pt : nfp[i]) { - nfppoly.inners().back().push_back(pt); - } - } - - return nfppoly; -} - -void write_svg(std::string const& filename,const std::vector& segments) { - std::ofstream svg(filename.c_str()); - - boost::geometry::svg_mapper mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\""); - for(const auto& seg : segments) { - segmentf_t segf({toLongDouble(seg.first.x_), toLongDouble(seg.first.y_)}, {toLongDouble(seg.second.x_), toLongDouble(seg.second.y_)}); - mapper.add(segf); - mapper.map(segf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2"); - } -} - -void write_svg(std::string const& filename, const polygon_t& p, const polygon_t::ring_type& ring) { - std::ofstream svg(filename.c_str()); - - boost::geometry::svg_mapper mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\""); - auto pf = convert(p); - auto rf = convert(ring); - - mapper.add(pf); - mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2"); - mapper.add(rf); - mapper.map(rf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2"); -} - -void write_svg(std::string const& filename, std::vector const& polygons) { - std::ofstream svg(filename.c_str()); - - boost::geometry::svg_mapper mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\""); - for (auto p : polygons) { - auto pf = convert(p); - mapper.add(pf); - mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2"); - } -} - -void write_svg(std::string const& filename, std::vector const& polygons, const nfp_t& nfp) { - polygon_t nfppoly; - for (const auto& pt : nfp.front()) { - nfppoly.outer().push_back(pt); - } - - for (size_t i = 1; i < nfp.size(); ++i) { - nfppoly.inners().push_back({}); - for (const auto& pt : nfp[i]) { - nfppoly.inners().back().push_back(pt); - } - } - std::ofstream svg(filename.c_str()); - - boost::geometry::svg_mapper mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\""); - for (auto p : polygons) { - auto pf = convert(p); - mapper.add(pf); - mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2"); - } - bg::correct(nfppoly); - auto nfpf = convert(nfppoly); - mapper.add(nfpf); - mapper.map(nfpf, "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2"); - - for(auto& r: nfpf.inners()) { - if(r.size() == 1) { - mapper.add(r.front()); - mapper.map(r.front(), "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2"); - } else if(r.size() == 2) { - segmentf_t seg(r.front(), *(r.begin()+1)); - mapper.add(seg); - mapper.map(seg, "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2"); - } - } -} - -std::ostream& operator<<(std::ostream& os, const segment_t& seg) { - os << "{" << seg.first << "," << seg.second << "}"; - return os; -} - -bool operator<(const segment_t& lhs, const segment_t& rhs) { - return lhs.first < rhs.first || ((lhs.first == rhs.first) && (lhs.second < rhs.second)); -} - -bool operator==(const segment_t& lhs, const segment_t& rhs) { - return (lhs.first == rhs.first && lhs.second == rhs.second) || (lhs.first == rhs.second && lhs.second == rhs.first); -} - -bool operator!=(const segment_t& lhs, const segment_t& rhs) { - return !operator==(lhs,rhs); -} - -enum Alignment { - LEFT, - RIGHT, - ON -}; - -point_t normalize(const point_t& pt) { - point_t norm = pt; - coord_t len = bg::length(segment_t{{0,0},pt}); - - if(len == 0.0L) - return {0,0}; - - norm.x_ /= len; - norm.y_ /= len; - - return norm; -} - -Alignment get_alignment(const segment_t& seg, const point_t& pt){ - coord_t res = ((seg.second.x_ - seg.first.x_)*(pt.y_ - seg.first.y_) - - (seg.second.y_ - seg.first.y_)*(pt.x_ - seg.first.x_)); - - if(equals(res, 0)) { - return ON; - } else if(larger(res,0)) { - return LEFT; - } else { - return RIGHT; - } -} - -long double get_inner_angle(const point_t& joint, const point_t& end1, const point_t& end2) { - coord_t dx21 = end1.x_-joint.x_; - coord_t dx31 = end2.x_-joint.x_; - coord_t dy21 = end1.y_-joint.y_; - coord_t dy31 = end2.y_-joint.y_; - coord_t m12 = sqrt((dx21*dx21 + dy21*dy21)); - coord_t m13 = sqrt((dx31*dx31 + dy31*dy31)); - if(m12 == 0.0L || m13 == 0.0L) - return 0; - return acos( (dx21*dx31 + dy21*dy31) / (m12 * m13) ); -} - -struct TouchingPoint { - enum Type { - VERTEX, - A_ON_B, - B_ON_A - }; - Type type_; - psize_t A_; - psize_t B_; -}; - -struct TranslationVector { - point_t vector_; - segment_t edge_; - bool fromA_; - string name_; - - bool operator<(const TranslationVector& other) const { - return this->vector_ < other.vector_ || ((this->vector_ == other.vector_) && (this->edge_ < other.edge_)); - } -}; - -std::ostream& operator<<(std::ostream& os, const TranslationVector& tv) { - os << "{" << tv.edge_ << " -> " << tv.vector_ << "} = " << tv.name_; - return os; -} - - -void read_wkt_polygon(const string& filename, polygon_t& p) { - std::ifstream t(filename); - - std::string str; - t.seekg(0, std::ios::end); - str.reserve(t.tellg()); - t.seekg(0, std::ios::beg); - - str.assign((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - str.pop_back(); - bg::read_wkt(str, p); - bg::correct(p); -} - -std::vector find_minimum_y(const polygon_t& p) { - std::vector result; - coord_t min = MAX_COORD; - auto& po = p.outer(); - for(psize_t i = 0; i < p.outer().size() - 1; ++i) { - if(smaller(po[i].y_, min)) { - result.clear(); - min = po[i].y_; - result.push_back(i); - } else if (equals(po[i].y_, min)) { - result.push_back(i); - } - } - return result; -} - -std::vector find_maximum_y(const polygon_t& p) { - std::vector result; - coord_t max = MIN_COORD; - auto& po = p.outer(); - for(psize_t i = 0; i < p.outer().size() - 1; ++i) { - if(larger(po[i].y_, max)) { - result.clear(); - max = po[i].y_; - result.push_back(i); - } else if (equals(po[i].y_, max)) { - result.push_back(i); - } - } - return result; -} - -psize_t find_point(const polygon_t::ring_type& ring, const point_t& pt) { - for(psize_t i = 0; i < ring.size(); ++i) { - if(ring[i] == pt) - return i; - } - return std::numeric_limits::max(); -} - -std::vector findTouchingPoints(const polygon_t::ring_type& ringA, const polygon_t::ring_type& ringB) { - std::vector touchers; - for(psize_t i = 0; i < ringA.size() - 1; i++) { - psize_t nextI = i+1; - for(psize_t j = 0; j < ringB.size() - 1; j++) { - psize_t nextJ = j+1; - if(ringA[i] == ringB[j]) { - touchers.push_back({TouchingPoint::VERTEX, i, j}); - } else if (ringA[nextI] != ringB[j] && bg::intersects(segment_t(ringA[i],ringA[nextI]), ringB[j])) { - touchers.push_back({TouchingPoint::B_ON_A, nextI, j}); - } else if (ringB[nextJ] != ringA[i] && bg::intersects(segment_t(ringB[j],ringB[nextJ]), ringA[i])) { - touchers.push_back({TouchingPoint::A_ON_B, i, nextJ}); - } - } - } - return touchers; -} - -//TODO deduplicate code -TranslationVector trimVector(const polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const TranslationVector& tv) { - coord_t shortest = bg::length(tv.edge_); - TranslationVector trimmed = tv; - for(const auto& ptA : rA) { - point_t translated; - //for polygon A we invert the translation - trans::translate_transformer translate(-tv.vector_.x_, -tv.vector_.y_); - boost::geometry::transform(ptA, translated, translate); - linestring_t projection; - segment_t segproj(ptA, translated); - projection.push_back(ptA); - projection.push_back(translated); - std::vector intersections; - bg::intersection(rB, projection, intersections); - if(bg::touches(projection, rB) && intersections.size() < 2) { - continue; - } - - //find shortest intersection - coord_t len; - segment_t segi; - for(const auto& pti : intersections) { - segi = segment_t(ptA,pti); - len = bg::length(segi); - if(smaller(len, shortest)) { - trimmed.vector_ = ptA - pti; - trimmed.edge_ = segi; - shortest = len; - } - } - } - - for(const auto& ptB : rB) { - point_t translated; - - trans::translate_transformer translate(tv.vector_.x_, tv.vector_.y_); - boost::geometry::transform(ptB, translated, translate); - linestring_t projection; - segment_t segproj(ptB, translated); - projection.push_back(ptB); - projection.push_back(translated); - std::vector intersections; - bg::intersection(rA, projection, intersections); - if(bg::touches(projection, rA) && intersections.size() < 2) { - continue; - } - - //find shortest intersection - coord_t len; - segment_t segi; - for(const auto& pti : intersections) { - - segi = segment_t(ptB,pti); - len = bg::length(segi); - if(smaller(len, shortest)) { - trimmed.vector_ = pti - ptB; - trimmed.edge_ = segi; - shortest = len; - } - } - } - return trimmed; -} - -std::vector findFeasibleTranslationVectors(polygon_t::ring_type& ringA, polygon_t::ring_type& ringB, const std::vector& touchers) { - //use a set to automatically filter duplicate vectors - std::vector potentialVectors; - std::vector> touchEdges; - - for (psize_t i = 0; i < touchers.size(); i++) { - point_t& vertexA = ringA[touchers[i].A_]; - vertexA.marked_ = true; - - // adjacent A vertices - auto prevAindex = static_cast(touchers[i].A_ - 1); - auto nextAindex = static_cast(touchers[i].A_ + 1); - - prevAindex = (prevAindex < 0) ? static_cast(ringA.size() - 2) : prevAindex; // loop - nextAindex = (static_cast(nextAindex) >= ringA.size()) ? 1 : nextAindex; // loop - - point_t& prevA = ringA[prevAindex]; - point_t& nextA = ringA[nextAindex]; - - // adjacent B vertices - point_t& vertexB = ringB[touchers[i].B_]; - - auto prevBindex = static_cast(touchers[i].B_ - 1); - auto nextBindex = static_cast(touchers[i].B_ + 1); - - prevBindex = (prevBindex < 0) ? static_cast(ringB.size() - 2) : prevBindex; // loop - nextBindex = (static_cast(nextBindex) >= ringB.size()) ? 1 : nextBindex; // loop - - point_t& prevB = ringB[prevBindex]; - point_t& nextB = ringB[nextBindex]; - - if (touchers[i].type_ == TouchingPoint::VERTEX) { - segment_t a1 = { vertexA, nextA }; - segment_t a2 = { vertexA, prevA }; - segment_t b1 = { vertexB, nextB }; - segment_t b2 = { vertexB, prevB }; - - //swap the segment elements so that always the first point is the touching point - //also make the second segment always a segment of ringB - touchEdges.push_back({a1, b1}); - touchEdges.push_back({a1, b2}); - touchEdges.push_back({a2, b1}); - touchEdges.push_back({a2, b2}); -#ifdef NFP_DEBUG - write_svg("touchersV" + std::to_string(i) + ".svg", {a1,a2,b1,b2}); -#endif - - //TODO test parallel edges for floating point stability - Alignment al; - //a1 and b1 meet at start vertex - al = get_alignment(a1, b1.second); - if(al == LEFT) { - potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex1"}); - } else if(al == RIGHT) { - potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex2"}); - } else { - potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex3"}); - } - - //a1 and b2 meet at start and end - al = get_alignment(a1, b2.second); - if(al == LEFT) { - //no feasible translation - } else if(al == RIGHT) { - potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex4"}); - } else { - potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex5"}); - } - - //a2 and b1 meet at end and start - al = get_alignment(a2, b1.second); - if(al == LEFT) { - //no feasible translation - } else if(al == RIGHT) { - potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex6"}); - } else { - potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex7"}); - } - } else if (touchers[i].type_ == TouchingPoint::B_ON_A) { - segment_t a1 = {vertexB, vertexA}; - segment_t a2 = {vertexB, prevA}; - segment_t b1 = {vertexB, prevB}; - segment_t b2 = {vertexB, nextB}; - - touchEdges.push_back({a1, b1}); - touchEdges.push_back({a1, b2}); - touchEdges.push_back({a2, b1}); - touchEdges.push_back({a2, b2}); -#ifdef NFP_DEBUG - write_svg("touchersB" + std::to_string(i) + ".svg", {a1,a2,b1,b2}); -#endif - potentialVectors.push_back({vertexA - vertexB, {vertexB, vertexA}, true, "bona"}); - } else if (touchers[i].type_ == TouchingPoint::A_ON_B) { - //TODO testme - segment_t a1 = {vertexA, prevA}; - segment_t a2 = {vertexA, nextA}; - segment_t b1 = {vertexA, vertexB}; - segment_t b2 = {vertexA, prevB}; -#ifdef NFP_DEBUG - write_svg("touchersA" + std::to_string(i) + ".svg", {a1,a2,b1,b2}); -#endif - touchEdges.push_back({a1, b1}); - touchEdges.push_back({a2, b1}); - touchEdges.push_back({a1, b2}); - touchEdges.push_back({a2, b2}); - potentialVectors.push_back({vertexA - vertexB, {vertexA, vertexB}, false, "aonb"}); - } - } - - //discard immediately intersecting translations - std::vector vectors; - for(const auto& v : potentialVectors) { - bool discarded = false; - for(const auto& sp : touchEdges) { - point_t normEdge = normalize(v.edge_.second - v.edge_.first); - point_t normFirst = normalize(sp.first.second - sp.first.first); - point_t normSecond = normalize(sp.second.second - sp.second.first); - - Alignment a1 = get_alignment({{0,0},normEdge}, normFirst); - Alignment a2 = get_alignment({{0,0},normEdge}, normSecond); - - if(a1 == a2 && a1 != ON) { - long double df = get_inner_angle({0,0},normEdge, normFirst); - long double ds = get_inner_angle({0,0},normEdge, normSecond); - - point_t normIn = normalize(v.edge_.second - v.edge_.first); - if (equals(df, ds)) { - TranslationVector trimmed = trimVector(ringA,ringB, v); - polygon_t::ring_type translated; - trans::translate_transformer translate(trimmed.vector_.x_, trimmed.vector_.y_); - boost::geometry::transform(ringB, translated, translate); - if (!(bg::intersects(translated, ringA) && !bg::overlaps(translated, ringA) && !bg::covered_by(translated, ringA) && !bg::covered_by(ringA, translated))) { - discarded = true; - break; - } - } else { - - if (normIn == normalize(v.vector_)) { - if (larger(ds, df)) { - discarded = true; - break; - } - } else { - if (smaller(ds, df)) { - discarded = true; - break; - } - } - } - } - } - if(!discarded) - vectors.push_back(v); - } - return vectors; -} - -bool find(const std::vector& h, const TranslationVector& tv) { - for(const auto& htv : h) { - if(htv.vector_ == tv.vector_) - return true; - } - return false; -} - -TranslationVector getLongest(const std::vector& tvs) { - coord_t len; - coord_t maxLen = MIN_COORD; - TranslationVector longest; - longest.vector_ = INVALID_POINT; - - for(auto& tv : tvs) { - len = bg::length(segment_t{{0,0},tv.vector_}); - if(larger(len, maxLen)) { - maxLen = len; - longest = tv; - } - } - return longest; -} - -TranslationVector selectNextTranslationVector(const polygon_t& pA, const polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const std::vector& tvs, const std::vector& history) { - if(!history.empty()) { - TranslationVector last = history.back(); - std::vector historyCopy = history; - if(historyCopy.size() >= 2) { - historyCopy.erase(historyCopy.end() - 1); - historyCopy.erase(historyCopy.end() - 1); - if(historyCopy.size() > 4) { - historyCopy.erase(historyCopy.begin(), historyCopy.end() - 4); - } - - } else { - historyCopy.clear(); - } - DEBUG_MSG("last", last); - - psize_t laterI = std::numeric_limits::max(); - point_t previous = rA[0]; - point_t next; - - if(last.fromA_) { - for (psize_t i = 1; i < rA.size() + 1; ++i) { - if (i >= rA.size()) - next = rA[i % rA.size()]; - else - next = rA[i]; - - segment_t candidate( previous, next ); - if(candidate == last.edge_) { - laterI = i; - break; - } - previous = next; - } - - if (laterI == std::numeric_limits::max()) { - point_t later; - if (last.vector_ == (last.edge_.second - last.edge_.first)) { - later = last.edge_.second; - } else { - later = last.edge_.first; - } - - laterI = find_point(rA, later); - } - } else { - point_t later; - if (last.vector_ == (last.edge_.second - last.edge_.first)) { - later = last.edge_.second; - } else { - later = last.edge_.first; - } - - laterI = find_point(rA, later); - } - - if (laterI == std::numeric_limits::max()) { - throw std::runtime_error( - "Internal error: Can't find later point of last edge"); - } - - std::vector viableEdges; - previous = rA[laterI]; - for(psize_t i = laterI + 1; i < rA.size() + laterI + 1; ++i) { - if(i >= rA.size()) - next = rA[i % rA.size()]; - else - next = rA[i]; - - viableEdges.push_back({previous, next}); - previous = next; - } - -// auto rng = std::default_random_engine {}; -// std::shuffle(std::begin(viableEdges), std::end(viableEdges), rng); - - //search with consulting the history to prevent oscillation - std::vector viableTrans; - for(const auto& ve: viableEdges) { - for(const auto& tv : tvs) { - if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first))) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_) && !find(historyCopy, tv)) { - viableTrans.push_back(tv); - } - } - for (const auto& tv : tvs) { - if (!tv.fromA_) { - point_t later; - if (tv.vector_ == (tv.edge_.second - tv.edge_.first) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_) && !find(historyCopy, tv)) { - later = tv.edge_.second; - } else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) { - later = tv.edge_.first; - } else - continue; - - if (later == ve.first || later == ve.second) { - viableTrans.push_back(tv); - } - } - } - } - - if(!viableTrans.empty()) - return getLongest(viableTrans); - - //search again without the history - for(const auto& ve: viableEdges) { - for(const auto& tv : tvs) { - if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first))) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_)) { - viableTrans.push_back(tv); - } - } - for (const auto& tv : tvs) { - if (!tv.fromA_) { - point_t later; - if (tv.vector_ == (tv.edge_.second - tv.edge_.first) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_)) { - later = tv.edge_.second; - } else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) { - later = tv.edge_.first; - } else - continue; - - if (later == ve.first || later == ve.second) { - viableTrans.push_back(tv); - } - } - } - } - if(!viableTrans.empty()) - return getLongest(viableTrans); - - /* - //search again without the history and without checking last edge - for(const auto& ve: viableEdges) { - for(const auto& tv : tvs) { - if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first)))) { - return tv; - } - } - for (const auto& tv : tvs) { - if (!tv.fromA_) { - point_t later; - if (tv.vector_ == (tv.edge_.second - tv.edge_.first)) { - later = tv.edge_.second; - } else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) { - later = tv.edge_.first; - } else - continue; - - if (later == ve.first || later == ve.second) { - return tv; - } - } - } - }*/ - - if(tvs.size() == 1) - return *tvs.begin(); - - TranslationVector tv; - tv.vector_ = INVALID_POINT; - return tv; - } else { - return getLongest(tvs); - } -} - -bool inNfp(const point_t& pt, const nfp_t& nfp) { - for(const auto& r : nfp) { - if(bg::touches(pt, r)) - return true; - } - - return false; -} - -enum SearchStartResult { - FIT, - FOUND, - NOT_FOUND -}; - -SearchStartResult searchStartTranslation(polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const nfp_t& nfp,const bool& inside, point_t& result) { - for(psize_t i = 0; i < rA.size() - 1; i++) { - psize_t index; - if (i >= rA.size()) - index = i % rA.size() + 1; - else - index = i; - - auto& ptA = rA[index]; - - if(ptA.marked_) - continue; - - ptA.marked_ = true; - - for(const auto& ptB: rB) { - point_t testTranslation = ptA - ptB; - polygon_t::ring_type translated; - boost::geometry::transform(rB, translated, trans::translate_transformer(testTranslation.x_, testTranslation.y_)); - - //check if the translated rB is identical to rA - bool identical = false; - for(const auto& ptT: translated) { - identical = false; - for(const auto& ptA: rA) { - if(ptT == ptA) { - identical = true; - break; - } - } - if(!identical) - break; - } - - if(identical) { - result = testTranslation; - return FIT; - } - - bool bInside = false; - for(const auto& ptT: translated) { - if(bg::within(ptT, rA)) { - bInside = true; - break; - } else if(!bg::touches(ptT, rA)) { - bInside = false; - break; - } - } - - if(((bInside && inside) || (!bInside && !inside)) && (!bg::overlaps(translated, rA) && !bg::covered_by(translated, rA) && !bg::covered_by(rA, translated)) && !inNfp(translated.front(), nfp)){ - result = testTranslation; - return FOUND; - } - - point_t nextPtA = rA[index + 1]; - TranslationVector slideVector; - slideVector.vector_ = nextPtA - ptA; - slideVector.edge_ = {ptA, nextPtA}; - slideVector.fromA_ = true; - TranslationVector trimmed = trimVector(rA, translated, slideVector); - polygon_t::ring_type translated2; - trans::translate_transformer trans(trimmed.vector_.x_, trimmed.vector_.y_); - boost::geometry::transform(translated, translated2, trans); - - //check if the translated rB is identical to rA - identical = false; - for(const auto& ptT: translated) { - identical = false; - for(const auto& ptA: rA) { - if(ptT == ptA) { - identical = true; - break; - } - } - if(!identical) - break; - } - - if(identical) { - result = trimmed.vector_ + testTranslation; - return FIT; - } - - bInside = false; - for(const auto& ptT: translated2) { - if(bg::within(ptT, rA)) { - bInside = true; - break; - } else if(!bg::touches(ptT, rA)) { - bInside = false; - break; - } - } - - if(((bInside && inside) || (!bInside && !inside)) && (!bg::overlaps(translated2, rA) && !bg::covered_by(translated2, rA) && !bg::covered_by(rA, translated2)) && !inNfp(translated2.front(), nfp)){ - result = trimmed.vector_ + testTranslation; - return FOUND; - } - } - } - return NOT_FOUND; -} - -enum SlideResult { - LOOP, - NO_LOOP, - NO_TRANSLATION -}; - -SlideResult slide(polygon_t& pA, polygon_t::ring_type& rA, polygon_t::ring_type& rB, nfp_t& nfp, const point_t& transB, bool inside) { - polygon_t::ring_type rifsB; - boost::geometry::transform(rB, rifsB, trans::translate_transformer(transB.x_, transB.y_)); - rB = std::move(rifsB); - -#ifdef NFP_DEBUG - write_svg("ifs.svg", pA, rB); -#endif - - bool startAvailable = true; - psize_t cnt = 0; - point_t referenceStart = rB.front(); - std::vector history; - - //generate the nfp for the ring - while(startAvailable) { - DEBUG_VAL(cnt); - //use first point of rB as reference - nfp.back().push_back(rB.front()); - if(cnt == 15) - std::cerr << ""; - - std::vector touchers = findTouchingPoints(rA, rB); - -#ifdef NFP_DEBUG - DEBUG_MSG("touchers", touchers.size()); - for(auto t : touchers) { - DEBUG_VAL(t.type_); - } -#endif - if(touchers.empty()) { - throw std::runtime_error("Internal error: No touching points found"); - } - std::vector transVectors = findFeasibleTranslationVectors(rA, rB, touchers); - -#ifdef NFP_DEBUG - DEBUG_MSG("collected vectors", transVectors.size()); - for(auto pt : transVectors) { - DEBUG_VAL(pt); - } -#endif - - if(transVectors.empty()) { - return NO_LOOP; - } - - TranslationVector next = selectNextTranslationVector(pA, rA, rB, transVectors, history); - - if(next.vector_ == INVALID_POINT) - return NO_TRANSLATION; - - DEBUG_MSG("next", next); - - TranslationVector trimmed = trimVector(rA, rB, next); - DEBUG_MSG("trimmed", trimmed); - - history.push_back(next); - - polygon_t::ring_type nextRB; - boost::geometry::transform(rB, nextRB, trans::translate_transformer(trimmed.vector_.x_, trimmed.vector_.y_)); - rB = std::move(nextRB); - -#ifdef NFP_DEBUG - write_svg("next" + std::to_string(cnt) + ".svg", pA,rB); -#endif - - ++cnt; - if(referenceStart == rB.front() || (inside && bg::touches(rB.front(), nfp.front()))) { - startAvailable = false; - } - } - return LOOP; -} - -void removeCoLinear(polygon_t::ring_type& r) { - assert(r.size() > 2); - psize_t nextI; - psize_t prevI = 0; - segment_t segment(r[r.size() - 2], r[0]); - polygon_t::ring_type newR; - - for (psize_t i = 1; i < r.size() + 1; ++i) { - if (i >= r.size()) - nextI = i % r.size() + 1; - else - nextI = i; - - if (get_alignment(segment, r[nextI]) != ON) { - newR.push_back(r[prevI]); - } - segment = {segment.second, r[nextI]}; - prevI = nextI; - } - - r = newR; -} - -void removeCoLinear(polygon_t& p) { - removeCoLinear(p.outer()); - for (auto& r : p.inners()) - removeCoLinear(r); -} - -nfp_t generateNFP(polygon_t& pA, polygon_t& pB, const bool checkValidity = true) { - removeCoLinear(pA); - removeCoLinear(pB); - - if(checkValidity) { - std::string reason; - if(!bg::is_valid(pA, reason)) - throw std::runtime_error("Polygon A is invalid: " + reason); - - if(!bg::is_valid(pB, reason)) - throw std::runtime_error("Polygon B is invalid: " + reason); - } - - nfp_t nfp; - -#ifdef NFP_DEBUG - write_svg("start.svg", {pA, pB}); -#endif - - DEBUG_VAL(bg::wkt(pA)) - DEBUG_VAL(bg::wkt(pB)); - - //prevent double vertex connections at start because we might come back the same way we go which would end the nfp prematurely - std::vector ptyaminI = find_minimum_y(pA); - std::vector ptybmaxI = find_maximum_y(pB); - - point_t pAstart; - point_t pBstart; - - if(ptyaminI.size() > 1 || ptybmaxI.size() > 1) { - //find right-most of A and left-most of B to prevent double connection at start - coord_t maxX = MIN_COORD; - psize_t iRightMost = 0; - for(psize_t& ia : ptyaminI) { - const point_t& candidateA = pA.outer()[ia]; - if(larger(candidateA.x_, maxX)) { - maxX = candidateA.x_; - iRightMost = ia; - } - } - - coord_t minX = MAX_COORD; - psize_t iLeftMost = 0; - for(psize_t& ib : ptybmaxI) { - const point_t& candidateB = pB.outer()[ib]; - if(smaller(candidateB.x_, minX)) { - minX = candidateB.x_; - iLeftMost = ib; - } - } - pAstart = pA.outer()[iRightMost]; - pBstart = pB.outer()[iLeftMost]; - } else { - pAstart = pA.outer()[ptyaminI.front()]; - pBstart = pB.outer()[ptybmaxI.front()]; - } - - nfp.push_back({}); - point_t transB = {pAstart - pBstart}; - - if(slide(pA, pA.outer(), pB.outer(), nfp, transB, false) != LOOP) { - throw std::runtime_error("Unable to complete outer nfp loop"); - } - - DEBUG_VAL("##### outer #####"); - point_t startTrans; - while(true) { - SearchStartResult res = searchStartTranslation(pA.outer(), pB.outer(), nfp, false, startTrans); - if(res == FOUND) { - nfp.push_back({}); - DEBUG_VAL("##### interlock start #####") - polygon_t::ring_type rifsB; - boost::geometry::transform(pB.outer(), rifsB, trans::translate_transformer(startTrans.x_, startTrans.y_)); - if(inNfp(rifsB.front(), nfp)) { - continue; - } - SlideResult sres = slide(pA, pA.outer(), pB.outer(), nfp, startTrans, true); - if(sres != LOOP) { - if(sres == NO_TRANSLATION) { - //no initial slide found -> jiggsaw - if(!inNfp(pB.outer().front(),nfp)) { - nfp.push_back({}); - nfp.back().push_back(pB.outer().front()); - } - } - } - DEBUG_VAL("##### interlock end #####"); - } else if(res == FIT) { - point_t reference = pB.outer().front(); - point_t translated; - trans::translate_transformer translate(startTrans.x_, startTrans.y_); - boost::geometry::transform(reference, translated, translate); - if(!inNfp(translated,nfp)) { - nfp.push_back({}); - nfp.back().push_back(translated); - } - break; - } else - break; - } - - - for(auto& rA : pA.inners()) { - while(true) { - SearchStartResult res = searchStartTranslation(rA, pB.outer(), nfp, true, startTrans); - if(res == FOUND) { - nfp.push_back({}); - DEBUG_VAL("##### hole start #####"); - slide(pA, rA, pB.outer(), nfp, startTrans, true); - DEBUG_VAL("##### hole end #####"); - } else if(res == FIT) { - point_t reference = pB.outer().front(); - point_t translated; - trans::translate_transformer translate(startTrans.x_, startTrans.y_); - boost::geometry::transform(reference, translated, translate); - if(!inNfp(translated,nfp)) { - nfp.push_back({}); - nfp.back().push_back(translated); - } - break; - } else - break; - } - } - -#ifdef NFP_DEBUG - write_svg("nfp.svg", {pA,pB}, nfp); -#endif - - return nfp; -} -} -#endif diff --git a/src/libnest2d/tools/nfp_svgnest.hpp b/src/libnest2d/tools/nfp_svgnest.hpp deleted file mode 100644 index ac5700c10..000000000 --- a/src/libnest2d/tools/nfp_svgnest.hpp +++ /dev/null @@ -1,1018 +0,0 @@ -#ifndef NFP_SVGNEST_HPP -#define NFP_SVGNEST_HPP - -#include -#include - -#include - -namespace libnest2d { - -namespace __svgnest { - -using std::sqrt; -using std::min; -using std::max; -using std::abs; -using std::isnan; - -//template struct _Scale { -// static const BP2D_CONSTEXPR long long Value = 1000000; -//}; - -template struct _alg { - using Contour = TContour; - using Point = TPoint; - using iCoord = TCoord; - using Coord = double; - using Shapes = nfp::Shapes; - - static const Coord TOL; - -#define dNAN std::nan("") - - struct Vector { - Coord x = 0.0, y = 0.0; - bool marked = false; - Vector() = default; - Vector(Coord X, Coord Y): x(X), y(Y) {} - Vector(const Point& p): x(Coord(getX(p))), y(Coord(getY(p))) {} - operator Point() const { return {iCoord(x), iCoord(y)}; } - Vector& operator=(const Point& p) { - x = getX(p), y = getY(p); return *this; - } - bool operator!=(const Vector& v) const { - return v.x != x || v.y != y; - } - Vector(std::initializer_list il): - x(*il.begin()), y(*std::next(il.begin())) {} - }; - - static inline Coord x(const Point& p) { return Coord(getX(p)); } - static inline Coord y(const Point& p) { return Coord(getY(p)); } - - static inline Coord x(const Vector& p) { return p.x; } - static inline Coord y(const Vector& p) { return p.y; } - - class Cntr { - std::vector v_; - public: - Cntr(const Contour& c) { - v_.reserve(c.size()); - std::transform(c.begin(), c.end(), std::back_inserter(v_), - [](const Point& p) { - return Vector(double(x(p)) / 1e6, double(y(p)) / 1e6); - }); - std::reverse(v_.begin(), v_.end()); - v_.pop_back(); - } - Cntr() = default; - - Coord offsetx = 0; - Coord offsety = 0; - size_t size() const { return v_.size(); } - bool empty() const { return v_.empty(); } - typename std::vector::const_iterator cbegin() const { return v_.cbegin(); } - typename std::vector::const_iterator cend() const { return v_.cend(); } - typename std::vector::iterator begin() { return v_.begin(); } - typename std::vector::iterator end() { return v_.end(); } - Vector& operator[](size_t idx) { return v_[idx]; } - const Vector& operator[](size_t idx) const { return v_[idx]; } - template - void emplace_back(Args&&...args) { - v_.emplace_back(std::forward(args)...); - } - template - void push(Args&&...args) { - v_.emplace_back(std::forward(args)...); - } - void clear() { v_.clear(); } - - operator Contour() const { - Contour cnt; - cnt.reserve(v_.size() + 1); - std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), - [](const Vector& vertex) { - return Point(iCoord(vertex.x) * 1000000, iCoord(vertex.y) * 1000000); - }); - if(!cnt.empty()) cnt.emplace_back(cnt.front()); - S sh = shapelike::create(cnt); - -// std::reverse(cnt.begin(), cnt.end()); - return shapelike::getContour(sh); - } - }; - - inline static bool _almostEqual(Coord a, Coord b, - Coord tolerance = TOL) - { - return std::abs(a - b) < tolerance; - } - - // returns true if p lies on the line segment defined by AB, - // but not at any endpoints may need work! - static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) { - - // vertical line - if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) { - if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && - p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){ - return true; - } - else{ - return false; - } - } - - // horizontal line - if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){ - if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && - p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){ - return true; - } - else{ - return false; - } - } - - //range check - if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || - (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) - return false; - - // exclude end points - if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || - (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) - return false; - - - double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); - - if(abs(cross) > TOL) return false; - - double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y); - - if(dot < 0 || _almostEqual(dot, 0)) return false; - - double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); - - if(dot > len2 || _almostEqual(dot, len2)) return false; - - return true; - } - - // return true if point is in the polygon, false if outside, and null if exactly on a point or edge - static int pointInPolygon(const Vector& point, const Cntr& polygon) { - if(polygon.size() < 3){ - return 0; - } - - bool inside = false; - Coord offsetx = polygon.offsetx; - Coord offsety = polygon.offsety; - - for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) { - auto xi = polygon[i].x + offsetx; - auto yi = polygon[i].y + offsety; - auto xj = polygon[j].x + offsetx; - auto yj = polygon[j].y + offsety; - - if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){ - return 0; // no result - } - - if(_onSegment({xi, yi}, {xj, yj}, point)){ - return 0; // exactly on the segment - } - - if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines - continue; - } - - bool intersect = ((yi > point.y) != (yj > point.y)) && - (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); - if (intersect) inside = !inside; - } - - return inside? 1 : -1; - } - - static bool intersect(const Cntr& A, const Cntr& B){ - Contour a = A, b = B; - return shapelike::intersects(shapelike::create(a), shapelike::create(b)); - } - - static Vector _normalizeVector(const Vector& v) { - if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){ - return Point(v); // given vector was already a unit vector - } - auto len = sqrt(v.x*v.x + v.y*v.y); - auto inverse = 1/len; - - return { Coord(v.x*inverse), Coord(v.y*inverse) }; - } - - static double pointDistance( const Vector& p, - const Vector& s1, - const Vector& s2, - Vector normal, - bool infinite = false) - { - normal = _normalizeVector(normal); - - Vector dir = { - normal.y, - -normal.x - }; - - auto pdot = p.x*dir.x + p.y*dir.y; - auto s1dot = s1.x*dir.x + s1.y*dir.y; - auto s2dot = s2.x*dir.x + s2.y*dir.y; - - auto pdotnorm = p.x*normal.x + p.y*normal.y; - auto s1dotnorm = s1.x*normal.x + s1.y*normal.y; - auto s2dotnorm = s2.x*normal.x + s2.y*normal.y; - - if(!infinite){ - if (((pdots1dot || _almostEqual(pdot, s1dot)) && - (pdot>s2dot || _almostEqual(pdot, s2dot)))) - { - // dot doesn't collide with segment, - // or lies directly on the vertex - return dNAN; - } - if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && - (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) - { - return min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm); - } - if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && - (pdotnorm EFmax){ - return dNAN; - } - - double overlap = 0; - - if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) - { - overlap = 1; - } - else{ - auto minMax = min(ABmax, EFmax); - auto maxMin = max(ABmin, EFmin); - - auto maxMax = max(ABmax, EFmax); - auto minMin = min(ABmin, EFmin); - - overlap = (minMax-maxMin)/(maxMax-minMin); - } - - auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); - auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); - - // lines are colinear - if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ - - Vector ABnorm = {B.y-A.y, A.x-B.x}; - Vector EFnorm = {F.y-E.y, E.x-F.x}; - - auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y); - ABnorm.x /= ABnormlength; - ABnorm.y /= ABnormlength; - - auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y); - EFnorm.x /= EFnormlength; - EFnorm.y /= EFnormlength; - - // segment normals must point in opposite directions - if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && - ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){ - // normal of AB segment must point in same direction as - // given direction vector - auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; - // the segments merely slide along eachother - if(_almostEqual(normdot,0, TOL)){ - return dNAN; - } - if(normdot < 0){ - return 0.0; - } - } - return dNAN; - } - - std::vector distances; distances.reserve(10); - - // coincident points - if(_almostEqual(dotA, dotE)){ - distances.emplace_back(crossA-crossE); - } - else if(_almostEqual(dotA, dotF)){ - distances.emplace_back(crossA-crossF); - } - else if(dotA > EFmin && dotA < EFmax){ - auto d = pointDistance(A,E,F,reverse); - if(!isnan(d) && _almostEqual(d, 0)) - { // A currently touches EF, but AB is moving away from EF - auto dB = pointDistance(B,E,F,reverse,true); - if(dB < 0 || _almostEqual(dB*overlap,0)){ - d = dNAN; - } - } - if(!isnan(d)){ - distances.emplace_back(d); - } - } - - if(_almostEqual(dotB, dotE)){ - distances.emplace_back(crossB-crossE); - } - else if(_almostEqual(dotB, dotF)){ - distances.emplace_back(crossB-crossF); - } - else if(dotB > EFmin && dotB < EFmax){ - auto d = pointDistance(B,E,F,reverse); - - if(!isnan(d) && _almostEqual(d, 0)) - { // crossA>crossB A currently touches EF, but AB is moving away from EF - double dA = pointDistance(A,E,F,reverse,true); - if(dA < 0 || _almostEqual(dA*overlap,0)){ - d = dNAN; - } - } - if(!isnan(d)){ - distances.emplace_back(d); - } - } - - if(dotE > ABmin && dotE < ABmax){ - auto d = pointDistance(E,A,B,direction); - if(!isnan(d) && _almostEqual(d, 0)) - { // crossF ABmin && dotF < ABmax){ - auto d = pointDistance(F,A,B,direction); - if(!isnan(d) && _almostEqual(d, 0)) - { // && crossE 0 || _almostEqual(d, 0)){ - distance = d; - } - } - } - } - return distance; - } - - static double polygonProjectionDistance(const Cntr& AA, - const Cntr& BB, - Vector direction) - { - Cntr A = AA; - Cntr B = BB; - - auto Boffsetx = B.offsetx; - auto Boffsety = B.offsety; - auto Aoffsetx = A.offsetx; - auto Aoffsety = A.offsety; - - // close the loop for polygons - if(A[0] != A[A.size()-1]){ - A.push(A[0]); - } - - if(B[0] != B[B.size()-1]){ - B.push(B[0]); - } - - auto& edgeA = A; - auto& edgeB = B; - - double distance = dNAN, d; -// Vector p, s1, s2; - - for(size_t i = 0; i < edgeB.size(); i++) { - // the shortest/most negative projection of B onto A - double minprojection = dNAN; - Vector minp; - for(size_t j = 0; j < edgeA.size() - 1; j++){ - Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; - Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; - Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety }; - - if(abs((s2.y-s1.y) * direction.x - - (s2.x-s1.x) * direction.y) < TOL) continue; - - // project point, ignore edge boundaries - d = pointDistance(p, s1, s2, direction); - - if(!isnan(d) && (isnan(minprojection) || d < minprojection)) { - minprojection = d; - minp = p; - } - } - - if(!isnan(minprojection) && (isnan(distance) || - minprojection > distance)){ - distance = minprojection; - } - } - - return distance; - } - - static std::pair searchStartPoint( - const Cntr& AA, const Cntr& BB, bool inside, const std::vector& NFP = {}) - { - // clone arrays - auto A = AA; - auto B = BB; - -// // close the loop for polygons -// if(A[0] != A[A.size()-1]){ -// A.push(A[0]); -// } - -// if(B[0] != B[B.size()-1]){ -// B.push(B[0]); -// } - - // returns true if point already exists in the given nfp - auto inNfp = [](const Vector& p, const std::vector& nfp){ - if(nfp.empty()){ - return false; - } - - for(size_t i=0; i < nfp.size(); i++){ - for(size_t j = 0; j< nfp[i].size(); j++){ - if(_almostEqual(p.x, nfp[i][j].x) && - _almostEqual(p.y, nfp[i][j].y)){ - return true; - } - } - } - - return false; - }; - - for(size_t i = 0; i < A.size() - 1; i++){ - if(!A[i].marked) { - A[i].marked = true; - for(size_t j = 0; j < B.size(); j++){ - B.offsetx = A[i].x - B[j].x; - B.offsety = A[i].y - B[j].y; - - int Binside = 0; - for(size_t k = 0; k < B.size(); k++){ - int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); - if(inpoly != 0){ - Binside = inpoly; - break; - } - } - - if(Binside == 0){ // A and B are the same - return {false, {}}; - } - - auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety)); - if(((Binside && inside) || (!Binside && !inside)) && - !intersect(A,B) && !inNfp(startPoint.second, NFP)){ - return startPoint; - } - - // slide B along vector - auto vx = A[i+1].x - A[i].x; - auto vy = A[i+1].y - A[i].y; - - double d1 = polygonProjectionDistance(A,B,{vx, vy}); - double d2 = polygonProjectionDistance(B,A,{-vx, -vy}); - - double d = dNAN; - - // todo: clean this up - if(isnan(d1) && isnan(d2)){ - // nothin - } - else if(isnan(d1)){ - d = d2; - } - else if(isnan(d2)){ - d = d1; - } - else{ - d = min(d1,d2); - } - - // only slide until no longer negative - // todo: clean this up - if(!isnan(d) && !_almostEqual(d,0) && d > 0){ - - } - else{ - continue; - } - - auto vd2 = vx*vx + vy*vy; - - if(d*d < vd2 && !_almostEqual(d*d, vd2)){ - auto vd = sqrt(vx*vx + vy*vy); - vx *= d/vd; - vy *= d/vd; - } - - B.offsetx += vx; - B.offsety += vy; - - for(size_t k = 0; k < B.size(); k++){ - int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); - if(inpoly != 0){ - Binside = inpoly; - break; - } - } - startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety}); - if(((Binside && inside) || (!Binside && !inside)) && - !intersect(A,B) && !inNfp(startPoint.second, NFP)){ - return startPoint; - } - } - } - } - - return {false, Vector(0, 0)}; - } - - static std::vector noFitPolygon(Cntr A, - Cntr B, - bool inside, - bool searchEdges) - { - if(A.size() < 3 || B.size() < 3) { - throw GeometryException(GeomErr::NFP); - return {}; - } - - A.offsetx = 0; - A.offsety = 0; - - long i = 0, j = 0; - - auto minA = y(A[0]); - long minAindex = 0; - - auto maxB = y(B[0]); - long maxBindex = 0; - - for(i = 1; i < A.size(); i++){ - A[i].marked = false; - if(y(A[i]) < minA){ - minA = y(A[i]); - minAindex = i; - } - } - - for(i = 1; i < B.size(); i++){ - B[i].marked = false; - if(y(B[i]) > maxB){ - maxB = y(B[i]); - maxBindex = i; - } - } - - std::pair startpoint; - - if(!inside){ - // shift B such that the bottom-most point of B is at the top-most - // point of A. This guarantees an initial placement with no - // intersections - startpoint = { true, - { x(A[minAindex]) - x(B[maxBindex]), - y(A[minAindex]) - y(B[maxBindex]) } - }; - } - else { - // no reliable heuristic for inside - startpoint = searchStartPoint(A, B, true); - } - - std::vector NFPlist; - - struct Touch { - int type; - long A; - long B; - Touch(int t, long a, long b): type(t), A(a), B(b) {} - }; - - while(startpoint.first) { - - B.offsetx = startpoint.second.x; - B.offsety = startpoint.second.y; - - // maintain a list of touching points/edges - std::vector touching; - - struct V { - Coord x, y; - Vector *start, *end; - operator bool() { - return start != nullptr && end != nullptr; - } - operator Vector() const { return {x, y}; } - } prevvector = {0, 0, nullptr, nullptr}; - - Cntr NFP; - NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); - - auto referencex = x(B[0]) + B.offsetx; - auto referencey = y(B[0]) + B.offsety; - auto startx = referencex; - auto starty = referencey; - unsigned counter = 0; - - // sanity check, prevent infinite loop - while(counter < 10*(A.size() + B.size())){ - touching.clear(); - - // find touching vertices/edges - for(i = 0; i < A.size(); i++){ - long nexti = (i == A.size() - 1) ? 0 : i + 1; - for(j = 0; j < B.size(); j++){ - - long nextj = (j == B.size() - 1) ? 0 : j + 1; - - if( _almostEqual(A[i].x, B[j].x+B.offsetx) && - _almostEqual(A[i].y, B[j].y+B.offsety)) - { - touching.emplace_back(0, i, j); - } - else if( _onSegment( - A[i], A[nexti], - { B[j].x+B.offsetx, B[j].y + B.offsety}) ) - { - touching.emplace_back(1, nexti, j); - } - else if( _onSegment( - {B[j].x+B.offsetx, B[j].y + B.offsety}, - {B[nextj].x+B.offsetx, B[nextj].y + B.offsety}, - A[i]) ) - { - touching.emplace_back(2, i, nextj); - } - } - } - - // generate translation vectors from touching vertices/edges - std::vector vectors; - for(i=0; i < touching.size(); i++){ - auto& vertexA = A[touching[i].A]; - vertexA.marked = true; - - // adjacent A vertices - auto prevAindex = touching[i].A - 1; - auto nextAindex = touching[i].A + 1; - - prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop - nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop - - auto& prevA = A[prevAindex]; - auto& nextA = A[nextAindex]; - - // adjacent B vertices - auto& vertexB = B[touching[i].B]; - - auto prevBindex = touching[i].B-1; - auto nextBindex = touching[i].B+1; - - prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop - nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop - - auto& prevB = B[prevBindex]; - auto& nextB = B[nextBindex]; - - if(touching[i].type == 0){ - - V vA1 = { - prevA.x - vertexA.x, - prevA.y - vertexA.y, - &vertexA, - &prevA - }; - - V vA2 = { - nextA.x - vertexA.x, - nextA.y - vertexA.y, - &vertexA, - &nextA - }; - - // B vectors need to be inverted - V vB1 = { - vertexB.x - prevB.x, - vertexB.y - prevB.y, - &prevB, - &vertexB - }; - - V vB2 = { - vertexB.x - nextB.x, - vertexB.y - nextB.y, - &nextB, - &vertexB - }; - - vectors.emplace_back(vA1); - vectors.emplace_back(vA2); - vectors.emplace_back(vB1); - vectors.emplace_back(vB2); - } - else if(touching[i].type == 1){ - vectors.emplace_back(V{ - vertexA.x-(vertexB.x+B.offsetx), - vertexA.y-(vertexB.y+B.offsety), - &prevA, - &vertexA - }); - - vectors.emplace_back(V{ - prevA.x-(vertexB.x+B.offsetx), - prevA.y-(vertexB.y+B.offsety), - &vertexA, - &prevA - }); - } - else if(touching[i].type == 2){ - vectors.emplace_back(V{ - vertexA.x-(vertexB.x+B.offsetx), - vertexA.y-(vertexB.y+B.offsety), - &prevB, - &vertexB - }); - - vectors.emplace_back(V{ - vertexA.x-(prevB.x+B.offsetx), - vertexA.y-(prevB.y+B.offsety), - &vertexB, - &prevB - }); - } - } - - // TODO: there should be a faster way to reject vectors that - // will cause immediate intersection. For now just check them all - - V translate = {0, 0, nullptr, nullptr}; - double maxd = 0; - - for(i = 0; i < vectors.size(); i++) { - if(vectors[i].x == 0 && vectors[i].y == 0){ - continue; - } - - // if this vector points us back to where we came from, ignore it. - // ie cross product = 0, dot product < 0 - if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){ - - // compare magnitude with unit vectors - double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y); - Vector unitv = {Coord(vectors[i].x/vectorlength), - Coord(vectors[i].y/vectorlength)}; - - double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y); - Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength}; - - // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here - if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){ - continue; - } - } - - V vi = vectors[i]; - double d = polygonSlideDistance(A, B, vi, true); - double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; - - if(isnan(d) || d*d > vecd2){ - double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y); - d = vecd; - } - - if(!isnan(d) && d > maxd){ - maxd = d; - translate = vectors[i]; - } - } - - if(!translate || _almostEqual(maxd, 0)) - { - // didn't close the loop, something went wrong here - NFP.clear(); - break; - } - - translate.start->marked = true; - translate.end->marked = true; - - prevvector = translate; - - // trim - double vlength2 = translate.x*translate.x + translate.y*translate.y; - if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){ - double scale = sqrt((maxd*maxd)/vlength2); - translate.x *= scale; - translate.y *= scale; - } - - referencex += translate.x; - referencey += translate.y; - - if(_almostEqual(referencex, startx) && - _almostEqual(referencey, starty)) { - // we've made a full loop - break; - } - - // if A and B start on a touching horizontal line, - // the end point may not be the start point - bool looped = false; - if(NFP.size() > 0) { - for(i = 0; i < NFP.size() - 1; i++) { - if(_almostEqual(referencex, NFP[i].x) && - _almostEqual(referencey, NFP[i].y)){ - looped = true; - } - } - } - - if(looped){ - // we've made a full loop - break; - } - - NFP.emplace_back(referencex, referencey); - - B.offsetx += translate.x; - B.offsety += translate.y; - - counter++; - } - - if(NFP.size() > 0){ - NFPlist.emplace_back(NFP); - } - - if(!searchEdges){ - // only get outer NFP or first inner NFP - break; - } - - startpoint = - searchStartPoint(A, B, inside, NFPlist); - - } - - return NFPlist; - } -}; - -template const double _alg::TOL = std::pow(10, -9); - -} -} - -#endif // NFP_SVGNEST_HPP diff --git a/src/libnest2d/tools/nfp_svgnest_glue.hpp b/src/libnest2d/tools/nfp_svgnest_glue.hpp deleted file mode 100644 index ea1fb4d07..000000000 --- a/src/libnest2d/tools/nfp_svgnest_glue.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef NFP_SVGNEST_GLUE_HPP -#define NFP_SVGNEST_GLUE_HPP - -#include "nfp_svgnest.hpp" - -#include - -namespace libnest2d { - -namespace __svgnest { - -//template<> struct _Tol { -// static const BP2D_CONSTEXPR TCoord Value = 1000000; -//}; - -} - -namespace nfp { - -using NfpR = NfpResult; - -template<> struct NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { -// return nfpConvexOnly(sh, cother); - namespace sl = shapelike; - using alg = __svgnest::_alg; - - auto nfp_p = alg::noFitPolygon(sl::getContour(sh), - sl::getContour(cother), false, false); - - PolygonImpl nfp_cntr; - if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); - return {nfp_cntr, referenceVertex(nfp_cntr)}; - } -}; - -template<> struct NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { -// return nfpConvexOnly(sh, cother); - namespace sl = shapelike; - using alg = __svgnest::_alg; - - std::cout << "Itt vagyok" << std::endl; - auto nfp_p = alg::noFitPolygon(sl::getContour(sh), - sl::getContour(cother), false, false); - - PolygonImpl nfp_cntr; - nfp_cntr.Contour = nfp_p.front(); - return {nfp_cntr, referenceVertex(nfp_cntr)}; - } -}; - -template<> -struct NfpImpl { - NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { - namespace sl = shapelike; - using alg = __svgnest::_alg; - - auto nfp_p = alg::noFitPolygon(sl::getContour(sh), - sl::getContour(cother), true, false); - - PolygonImpl nfp_cntr; - nfp_cntr.Contour = nfp_p.front(); - return {nfp_cntr, referenceVertex(nfp_cntr)}; - } -}; - -template<> struct MaxNfpLevel { -// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; - static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; -}; - -}} - -#endif // NFP_SVGNEST_GLUE_HPP diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index 776dd5a1a..e1ed1ad05 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -56,7 +56,7 @@ public: auto d = static_cast( std::round(conf_.height*conf_.mm_in_coord_units) ); - auto& contour = shapelike::getContour(tsh); + auto& contour = shapelike::contour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); auto& holes = shapelike::holes(tsh); diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 0abac9067..6fa2ea649 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -249,26 +249,26 @@ Polygons BridgeDetector::coverage(double angle, bool precise) const { if (n_supported >= 2) { // trim it to not allow to go outside of the intersections BoundingBox center_bound = intersects[0].bounding_box(); - coord_t min_y = center_bound.center().y, max_y = center_bound.center().y; + coord_t min_y = center_bound.center()(1), max_y = center_bound.center()(1); for (Polygon &poly_bound : intersects) { center_bound = poly_bound.bounding_box(); - if (min_y > center_bound.center().y) min_y = center_bound.center().y; - if (max_y < center_bound.center().y) max_y = center_bound.center().y; + if (min_y > center_bound.center()(1)) min_y = center_bound.center()(1); + if (max_y < center_bound.center()(1)) max_y = center_bound.center()(1); } - coord_t min_x = trapezoid[0].x, max_x = trapezoid[0].x; + coord_t min_x = trapezoid[0](0), max_x = trapezoid[0](0); for (Point &p : trapezoid.points) { - if (min_x > p.x) min_x = p.x; - if (max_x < p.x) max_x = p.x; + if (min_x > p(0)) min_x = p(0); + if (max_x < p(0)) max_x = p(0); } //add what get_trapezoids3 has removed (+EPSILON) min_x -= (this->spacing / 4 + 1); max_x += (this->spacing / 4 + 1); coord_t mid_x = (min_x + max_x) / 2; for (Point &p : trapezoid.points) { - if (p.y < min_y) p.y = min_y; - if (p.y > max_y) p.y = max_y; - if (p.x > min_x && p.x < mid_x) p.x = min_x; - if (p.x < max_x && p.x > mid_x) p.x = max_x; + if (p(1) < min_y) p(1) = min_y; + if (p(1) > max_y) p(1) = max_y; + if (p(0) > min_x && p(0) < mid_x) p(0) = min_x; + if (p(0) < max_x && p(0) > mid_x) p(0) = max_x; } } } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6955dd1e7..adbfb9de2 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -1,7 +1,13 @@ project(libslic3r) cmake_minimum_required(VERSION 2.6) +include(PrecompiledHeader) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY) + add_library(libslic3r STATIC + pchheader.cpp + pchheader.hpp BoundingBox.cpp BoundingBox.hpp BridgeDetector.cpp @@ -45,6 +51,8 @@ add_library(libslic3r STATIC Fill/FillRectilinear2.hpp Fill/FillRectilinear3.cpp Fill/FillRectilinear3.hpp + Fill/FillSmooth.cpp + Fill/FillSmooth.hpp Flow.cpp Flow.hpp Format/3mf.cpp @@ -96,11 +104,15 @@ add_library(libslic3r STATIC Layer.hpp LayerRegion.cpp libslic3r.h + "${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h" Line.cpp Line.hpp + MedialAxis.cpp + MedialAxis.hpp Model.cpp Model.hpp ModelArrange.hpp + ModelArrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp @@ -120,6 +132,8 @@ add_library(libslic3r STATIC PolylineCollection.hpp Print.cpp Print.hpp + PrintBase.cpp + PrintBase.hpp PrintExport.hpp PrintConfig.cpp PrintConfig.hpp @@ -127,6 +141,10 @@ add_library(libslic3r STATIC PrintRegion.cpp Rasterizer/Rasterizer.hpp Rasterizer/Rasterizer.cpp + SLAPrint.cpp + SLAPrint.hpp + SLA/SLAAutoSupports.hpp + SLA/SLAAutoSupports.cpp Slicing.cpp Slicing.hpp SlicingAdaptive.cpp @@ -142,30 +160,42 @@ add_library(libslic3r STATIC Technologies.hpp TriangleMesh.cpp TriangleMesh.hpp - SLABasePool.hpp - SLABasePool.cpp utils.cpp Utils.hpp + MTUtils.hpp + SLA/SLABoilerPlate.hpp + SLA/SLABasePool.hpp + SLA/SLABasePool.cpp + SLA/SLASupportTree.hpp + SLA/SLASupportTree.cpp + SLA/SLASupportTreeIGL.cpp + SLA/SLARotfinder.hpp + SLA/SLARotfinder.cpp + SLA/SLABoostAdapter.hpp + SLA/SLASpatIndex.hpp ) +if (NOT SLIC3R_SYNTAXONLY) + add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) +endif () + target_compile_definitions(libslic3r PUBLIC -DUSE_TBB ${PNG_DEFINITIONS}) -target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES} ${PNG_INCLUDE_DIRS}) +target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} ${PNG_INCLUDE_DIRS} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(libslic3r - ${LIBNEST2D_LIBRARIES} + libnest2d admesh - miniz + miniz ${Boost_LIBRARIES} - clipper + clipper nowide - ${EXPAT_LIBRARIES} - ${GLEW_LIBRARIES} + ${EXPAT_LIBRARIES} + ${GLEW_LIBRARIES} ${PNG_LIBRARIES} - polypartition - poly2tri + polypartition + poly2tri qhull semver - ${TBB_LIBRARIES} -# ${wxWidgets_LIBRARIES} + tbb ) if(SLIC3R_PROFILE) diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp new file mode 100644 index 000000000..8d1a07d35 --- /dev/null +++ b/src/libslic3r/Channel.hpp @@ -0,0 +1,104 @@ +#ifndef slic3r_Channel_hpp_ +#define slic3r_Channel_hpp_ + +#include +#include +#include +#include +#include + + +namespace Slic3r { + + +template class Channel +{ +private: + using UniqueLock = std::unique_lock; + using Queue = std::deque; +public: + class Guard + { + public: + Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {} + Guard(const Guard &other) = delete; + Guard(Guard &&other) = delete; + ~Guard() {} + + // Access trampolines + size_t size() const noexcept { return m_queue.size(); } + bool empty() const noexcept { return m_queue.empty(); } + typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); } + typename Queue::const_iterator end() const noexcept { return m_queue.end(); } + typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; } + + Guard& operator=(const Guard &other) = delete; + Guard& operator=(Guard &&other) = delete; + private: + UniqueLock m_lock; + const Queue &m_queue; + }; + + + Channel() {} + ~Channel() {} + + void push(const T& item, bool silent = false) + { + { + UniqueLock lock(m_mutex); + m_queue.push_back(item); + } + if (! silent) { m_condition.notify_one(); } + } + + void push(T &&item, bool silent = false) + { + { + UniqueLock lock(m_mutex); + m_queue.push_back(std::forward(item)); + } + if (! silent) { m_condition.notify_one(); } + } + + T pop() + { + UniqueLock lock(m_mutex); + m_condition.wait(lock, [this]() { return !m_queue.empty(); }); + auto item = std::move(m_queue.front()); + m_queue.pop_front(); + return item; + } + + boost::optional try_pop() + { + UniqueLock lock(m_mutex); + if (m_queue.empty()) { + return boost::none; + } else { + auto item = std::move(m_queue.front()); + m_queue.pop(); + return item; + } + } + + // Unlocked observers + // Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock! + size_t size() const noexcept { return m_queue.size(); } + bool empty() const noexcept { return m_queue.empty(); } + + Guard read() const + { + return Guard(UniqueLock(m_mutex), m_queue); + } + +private: + Queue m_queue; + std::mutex m_mutex; + std::condition_variable m_condition; +}; + + +} // namespace Slic3r + +#endif // slic3r_Channel_hpp_ diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 90ace9f25..cd3dc165d 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_ClipperUtils_hpp_ #define slic3r_ClipperUtils_hpp_ -#include +#include "libslic3r.h" #include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index e7442d773..88e0c7664 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace Slic3r { @@ -226,44 +227,6 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const return diff; } -template -void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase *this_c) -{ - const T* opt_init = static_cast(other.option(opt_key)); - const T* opt_cur = static_cast(this_c->option(opt_key)); - int opt_init_max_id = opt_init->values.size() - 1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - } -} - -t_config_option_keys ConfigBase::deep_diff(const ConfigBase &other) const -{ - t_config_option_keys diff; - for (const t_config_option_key &opt_key : this->keys()) { - const ConfigOption *this_opt = this->option(opt_key); - const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - { - if (opt_key == "bed_shape"){ diff.emplace_back(opt_key); continue; } - switch (other_opt->type()) - { - case coInts: add_correct_opts_to_diff(opt_key, diff, other, this); break; - case coBools: add_correct_opts_to_diff(opt_key, diff, other, this); break; - case coFloats: add_correct_opts_to_diff(opt_key, diff, other, this); break; - case coStrings: add_correct_opts_to_diff(opt_key, diff, other, this); break; - case coPercents:add_correct_opts_to_diff(opt_key, diff, other, this); break; - case coPoints: add_correct_opts_to_diff(opt_key, diff, other, this); break; - default: diff.emplace_back(opt_key); break; - } - } - } - return diff; -} - t_config_option_keys ConfigBase::equal(const ConfigBase &other) const { t_config_option_keys equal; @@ -373,7 +336,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati return static_cast(raw_opt)->get_abs_value(ratio_over); } -void ConfigBase::setenv_() +void ConfigBase::setenv_() const { t_config_option_keys opt_keys = this->keys(); for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { @@ -439,14 +402,16 @@ void ConfigBase::load_from_gcode_file(const std::string &file) ifs.read(data.data(), data_length); ifs.close(); - load_from_gcode_string(data.data()); + size_t key_value_pairs = load_from_gcode_string(data.data()); + if (key_value_pairs < 80) + throw std::runtime_error((boost::format("Suspiciously low number of configuration values extracted from %1: %2") % file % key_value_pairs).str()); } // Load the config keys from the given string. -void ConfigBase::load_from_gcode_string(const char* str) +size_t ConfigBase::load_from_gcode_string(const char* str) { if (str == nullptr) - return; + return 0; // Walk line by line in reverse until a non-configuration key appears. char *data_start = const_cast(str); @@ -497,11 +462,8 @@ void ConfigBase::load_from_gcode_string(const char* str) } end = start; } - if (num_key_value_pairs < 90) { - char msg[80]; - sprintf(msg, "Suspiciously low number of configuration values extracted: %d", num_key_value_pairs); - throw std::runtime_error(msg); - } + + return num_key_value_pairs; } void ConfigBase::save(const std::string &file) const @@ -566,6 +528,103 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre return opt; } +void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) +{ + std::vector args; + // push a bogus executable name (argv[0]) + args.emplace_back(const_cast("")); + for (size_t i = 0; i < tokens.size(); ++ i) + args.emplace_back(const_cast(tokens[i].c_str())); + this->read_cli(args.size(), &args[0], extra); +} + +bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) +{ + // cache the CLI option => opt_key mapping + std::map opts; + for (const auto &oit : this->def()->options) { + std::string cli = oit.second.cli; + cli = cli.substr(0, cli.find("=")); + boost::trim_right_if(cli, boost::is_any_of("!")); + std::vector tokens; + boost::split(tokens, cli, boost::is_any_of("|")); + for (const std::string &t : tokens) + opts[t] = oit.first; + } + + bool parse_options = true; + for (int i = 1; i < argc; ++ i) { + std::string token = argv[i]; + // Store non-option arguments in the provided vector. + if (! parse_options || ! boost::starts_with(token, "-")) { + extra->push_back(token); + continue; + } + // Stop parsing tokens as options when -- is supplied. + if (token == "--") { + parse_options = false; + continue; + } + // Remove leading dashes + boost::trim_left_if(token, boost::is_any_of("-")); + // Remove the "no-" prefix used to negate boolean options. + bool no = false; + if (boost::starts_with(token, "no-")) { + no = true; + boost::replace_first(token, "no-", ""); + } + // Read value when supplied in the --key=value form. + std::string value; + { + size_t equals_pos = token.find("="); + if (equals_pos != std::string::npos) { + value = token.substr(equals_pos+1); + token.erase(equals_pos); + } + } + // Look for the cli -> option mapping. + const auto it = opts.find(token); + if (it == opts.end()) { + printf("Warning: unknown option --%s\n", token.c_str()); + // instead of continuing, return false to caller + // to stop execution and print usage + return false; + //continue; + } + const t_config_option_key opt_key = it->second; + const ConfigOptionDef &optdef = this->def()->options.at(opt_key); + // If the option type expects a value and it was not already provided, + // look for it in the next token. + if (optdef.type != coBool && optdef.type != coBools && value.empty()) { + if (i == (argc-1)) { + printf("No value supplied for --%s\n", token.c_str()); + continue; + } + value = argv[++ i]; + } + // Store the option value. + const bool existing = this->has(opt_key); + if (ConfigOptionBool* opt = this->opt(opt_key, true)) { + opt->value = !no; + } else if (ConfigOptionBools* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->values.push_back(!no); + } else if (ConfigOptionStrings* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); + } else if (ConfigOptionFloats* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); + } else if (ConfigOptionPoints* opt = this->opt(opt_key, true)) { + if (!existing) opt->values.clear(); // remove the default values + opt->deserialize(value, true); + } else { + this->set_deserialize(opt_key, value, true); + } + } + return true; +} + t_config_option_keys DynamicConfig::keys() const { t_config_option_keys keys; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a305a21b6..7f826109a 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -13,6 +13,7 @@ #include "libslic3r.h" #include "Point.hpp" +#include #include namespace Slic3r { @@ -61,6 +62,12 @@ enum ConfigOptionType { coEnum = 8, }; +enum ConfigOptionMode { + comSimple, + comAdvanced, + comExpert +}; + // A generic value of a configuration option. class ConfigOption { public: @@ -756,7 +763,7 @@ public: } //FIXME this smells, the parent class has the method declared returning (unsigned char&). - bool get_at(size_t i) const { return bool((i < this->values.size()) ? this->values[i] : this->values.front()); } + bool get_at(size_t i) const { return ((i < this->values.size()) ? this->values[i] : this->values.front()) != 0; } std::string serialize() const override { @@ -810,6 +817,22 @@ public: ConfigOption* clone() const override { return new ConfigOptionEnum(*this); } ConfigOptionEnum& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionEnum &rhs) const { return this->value == rhs.value; } + int getInt() const override { return (int)this->value; } + + bool operator==(const ConfigOption &rhs) const override + { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionEnum: Comparing incompatible types"); + // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum + return this->value == (T)rhs.getInt(); + } + + void set(const ConfigOption *rhs) override { + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionEnum: Assigning an incompatible type"); + // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum + this->value = (T)rhs->getInt(); + } std::string serialize() const override { @@ -879,6 +902,21 @@ public: ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } + bool operator==(const ConfigOption &rhs) const override + { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types"); + // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum + return this->value == rhs.getInt(); + } + + void set(const ConfigOption *rhs) override { + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type"); + // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum + this->value = rhs->getInt(); + } + std::string serialize() const override { for (const auto &kvp : *this->keys_map) @@ -951,6 +989,7 @@ public: // By setting min=0, only nonnegative input is allowed. int min = INT_MIN; int max = INT_MAX; + ConfigOptionMode mode = comSimple; // Legacy names for this configuration option. // Used when parsing legacy configuration file. std::vector aliases; @@ -976,7 +1015,7 @@ public: // Map from a config option name to its definition. // The definition does not carry an actual value of the config option, only its constant default value. // t_config_option_key is std::string -typedef std::map t_optiondef_map; +typedef std::map t_optiondef_map; // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. // The configuration definition is static: It does not carry the actual configuration values, @@ -984,18 +1023,33 @@ typedef std::map t_optiondef_map; class ConfigDef { public: - t_optiondef_map options; - ~ConfigDef() { for (auto &opt : this->options) delete opt.second.default_value; } - ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { - ConfigOptionDef* opt = &this->options[opt_key]; - opt->type = type; - return opt; - } + ~ConfigDef() { + for (std::pair &def : this->options) + delete def.second.default_value; + this->options.clear(); + } + + t_optiondef_map options; + bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } const ConfigOptionDef* get(const t_config_option_key &opt_key) const { t_optiondef_map::iterator it = const_cast(this)->options.find(opt_key); return (it == this->options.end()) ? nullptr : &it->second; } + std::vector keys() const { + std::vector out; + out.reserve(options.size()); + for(auto const& kvp : options) + out.push_back(kvp.first); + return out; + } + +protected: + ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { + ConfigOptionDef* opt = &this->options[opt_key]; + opt->type = type; + return opt; + } }; // An abstract configuration store. @@ -1051,9 +1105,6 @@ public: void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; - // Use deep_diff to correct return of changed options, - // considering individual options for each extruder - t_config_option_keys deep_diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() @@ -1062,11 +1113,12 @@ public: double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; - void setenv_(); + void setenv_() const; void load(const std::string &file); void load_from_ini(const std::string &file); void load_from_gcode_file(const std::string &file); - void load_from_gcode_string(const char* str); + // Returns number of key/value pairs extracted. + size_t load_from_gcode_string(const char* str); void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; @@ -1176,7 +1228,6 @@ public: // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; }; - bool has(const t_config_option_key &opt_key) const { return this->options.find(opt_key) != this->options.end(); } template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } template const T* opt(const t_config_option_key &opt_key) const @@ -1185,6 +1236,7 @@ public: ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override; // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. t_config_option_keys keys() const override; + bool empty() const { return options.empty(); } // Set a value for an opt_key. Returns true if the value did not exist yet. // This DynamicConfig will take ownership of opt. @@ -1217,11 +1269,21 @@ public: int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + template + ENUM opt_enum(const t_config_option_key &opt_key) const { return (ENUM)dynamic_cast(this->option(opt_key))->value; } + bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } -protected: + // Command line processing + void read_cli(const std::vector &tokens, t_config_option_keys* extra); + bool read_cli(int argc, char** argv, t_config_option_keys* extra); + typedef std::map t_options_map; + t_options_map::const_iterator cbegin() const { return options.cbegin(); } + t_options_map::const_iterator cend() const { return options.cend(); } + +private: t_options_map options; }; diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 2b2893c80..c34aca8f2 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -9,7 +9,9 @@ #endif /* SLIC3R_GUI */ #include "libslic3r.h" +#include "ClipperUtils.hpp" #include "EdgeGrid.hpp" +#include "SVG.hpp" #if 0 // Enable debugging and assert in this file. @@ -756,8 +758,8 @@ void EdgeGrid::Grid::calculate_sdf() float search_radius = float(m_resolution<<1); m_signed_distance_field.assign(nrows * ncols, search_radius); // For each cell: - for (size_t r = 0; r < m_rows; ++ r) { - for (size_t c = 0; c < m_cols; ++ c) { + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { const Cell &cell = m_cells[r * m_cols + c]; // For each segment in the cell: for (size_t i = cell.begin; i != cell.end; ++ i) { @@ -842,6 +844,8 @@ void EdgeGrid::Grid::calculate_sdf() #if 0 static int iRun = 0; ++ iRun; + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); @@ -1356,9 +1360,101 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co return out; } +inline int segments_could_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum + int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); + return sij1 * sij2; +} + +inline bool segments_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && + segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; +} + +std::vector> EdgeGrid::Grid::intersecting_edges() const +{ + std::vector> out; + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) + // Segments of the same contour share a common vertex. + continue; + if (segments_intersect(ip1, ip2, jp1, jp2)) { + // The two segments intersect. Add them to the output. + int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); + out.emplace_back(jfirst ? + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) : + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt))); + } + } + } + } + } + Slic3r::sort_remove_duplicates(out); + return out; +} + +bool EdgeGrid::Grid::has_intersecting_edges() const +{ + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && + segments_intersect(ip1, ip2, jp1, jp2)) + return true; + } + } + } + } + return false; +} + #if 0 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); + unsigned int w = (bbox.max(0) - bbox.min(0) + resolution - 1) / resolution; unsigned int h = (bbox.max(1) - bbox.min(1) + resolution - 1) / resolution; wxImage img(w, h); @@ -1450,4 +1546,59 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo } #endif /* SLIC3R_GUI */ +// Find all pairs of intersectiong edges from the set of polygons. +std::vector> intersecting_edges(const Polygons &polygons) +{ + double len = 0; + size_t cnt = 0; + BoundingBox bbox; + for (const Polygon &poly : polygons) { + if (poly.points.size() < 2) + continue; + for (size_t i = 0; i < poly.points.size(); ++ i) { + bbox.merge(poly.points[i]); + size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1; + len += (poly.points[j] - poly.points[i]).cast().norm(); + ++ cnt; + } + } + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + return grid.intersecting_edges(); +} + +// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. +void export_intersections_to_svg(const std::string &filename, const Polygons &polygons) +{ + std::vector> intersections = intersecting_edges(polygons); + BoundingBox bbox = get_extents(polygons); + SVG svg(filename.c_str(), bbox); + svg.draw(union_ex(polygons), "gray", 0.25f); + svg.draw_outline(polygons, "black"); + std::set intersecting_contours; + for (const std::pair &ie : intersections) { + intersecting_contours.insert(ie.first.first); + intersecting_contours.insert(ie.second.first); + } + // Highlight the contours with intersections. + coord_t line_width = coord_t(scale_(0.01)); + for (const Points *ic : intersecting_contours) { + svg.draw_outline(Polygon(*ic), "green"); + svg.draw_outline(Polygon(*ic), "black", line_width); + } + // Paint the intersections. + for (const std::pair &intersecting_edges : intersections) { + auto edge = [](const EdgeGrid::Grid::ContourEdge &e) { + return Line(e.first->at(e.second), + e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1)); + }; + svg.draw(edge(intersecting_edges.first), "red", line_width); + svg.draw(edge(intersecting_edges.second), "red", line_width); + } + svg.Close(); +} + } // namespace Slic3r diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index ab1aa4ed0..f99ab30c4 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -60,6 +60,11 @@ public: // For supports: Contours enclosing the rasterized edges. Polygons contours_simplified(coord_t offset, bool fill_holes) const; + typedef std::pair ContourPoint; + typedef std::pair ContourEdge; + std::vector> intersecting_edges() const; + bool has_intersecting_edges() const; + protected: struct Cell { Cell() : begin(0), end(0) {} @@ -113,6 +118,13 @@ extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resoluti #endif /* SLIC3R_GUI */ } // namespace EdgeGrid + +// Find all pairs of intersectiong edges from the set of polygons. +extern std::vector> intersecting_edges(const Polygons &polygons); + +// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. +extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons); + } // namespace Slic3r #endif /* slic3r_EdgeGrid_hpp_ */ diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 2275e8a16..7423fface 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -314,11 +314,11 @@ ExPolygon::get_trapezoids3_half(Polygons* polygons, float spacing) const { BoundingBox bb(pp); // get all x coordinates - int min_x = pp[0].x, max_x = pp[0].x; + int min_x = pp[0].x(), max_x = pp[0].x(); std::vector xx; for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) { - if (min_x > p->x) min_x = p->x; - if (max_x < p->x) max_x = p->x; + if (min_x > p->x()) min_x = p->x(); + if (max_x < p->x()) max_x = p->x(); } for (int x = min_x; x < max_x-spacing/2; x += spacing) { xx.push_back(x); @@ -334,14 +334,14 @@ ExPolygon::get_trapezoids3_half(Polygons* polygons, float spacing) const { // build rectangle Polygon poly; poly.points.resize(4); - poly[0].x = *x +spacing / 4; - poly[0].y = bb.min.y; - poly[1].x = next_x -spacing / 4; - poly[1].y = bb.min.y; - poly[2].x = next_x -spacing / 4; - poly[2].y = bb.max.y; - poly[3].x = *x +spacing / 4; - poly[3].y = bb.max.y; + poly[0].x() = *x + spacing / 4; + poly[0].y() = bb.min(1); + poly[1].x() = next_x - spacing / 4; + poly[1].y() = bb.min(1); + poly[2].x() = next_x - spacing / 4; + poly[2].y() = bb.max(1); + poly[3].x() = *x + spacing / 4; + poly[3].y() = bb.max(1); // intersect with this expolygon // append results to return value diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index d8d01e78a..dc8ca0d0c 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -113,15 +113,17 @@ public: float feedrate; // Id of the extruder, used for visualization purposed. unsigned int extruder_id; + // Id of the color, used for visualization purposed in the color printing case. + unsigned int cp_color_id; - ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), m_role(role) {}; - ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), m_role(role) {}; - ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {} - ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}; + ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}; + ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} // ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {}; - ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = rhs.polyline; return *this; } - ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = std::move(rhs.polyline); return *this; } + ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->polyline = rhs.polyline; return *this; } + ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->polyline = std::move(rhs.polyline); return *this; } ExtrusionPath* clone() const { return new ExtrusionPath (*this); } void reverse() { this->polyline.reverse(); } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ed1c1c5a1..83c6ed583 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -59,7 +59,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) layerm.fill_surfaces.group(&groups); //if internal infill can be dense, place it on his own group - if (layerm.region()->config.infill_dense.getBool() && layerm.region()->config.fill_density<40) { + if (layerm.region()->config().infill_dense.getBool() && layerm.region()->config().fill_density<40) { SurfacesPtr *denseGroup = NULL; const uint32_t nbGroups = groups.size(); for (uint32_t num_group = 0; num_group < nbGroups; ++num_group) { @@ -193,8 +193,8 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) (surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) : ipRectilinear; } else { - if (layerm.region()->config.infill_dense.getBool() - && layerm.region()->config.fill_density<40 + if (layerm.region()->config().infill_dense.getBool() + && layerm.region()->config().fill_density<40 && surface.maxNbSolidLayersOnTop <= 1 && surface.maxNbSolidLayersOnTop > 0) { density = 42; @@ -262,8 +262,8 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; //give the overlap size to let the infill do his overlap //add overlap if at least one perimeter - if (layerm.region()->config.perimeters.getInt() > 0) { - f->overlap = layerm.region()->config.get_abs_value("infill_overlap", (perimeter_spacing + (f->spacing)) / 2); + if (layerm.region()->config().perimeters > 0) { + f->overlap = layerm.region()->config().get_abs_value("infill_overlap", (perimeter_spacing + (f->spacing)) / 2); if (f->overlap!=0) { f->no_overlap_expolygons = intersection_ex(layerm.fill_no_overlap_expolygons, ExPolygons() = { surface.expolygon }); } else { @@ -278,8 +278,8 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) FillParams params; params.density = 0.01 * density; params.dont_adjust = false; - params.fill_exactly = layerm.region()->config.enforce_full_fill_volume.getBool(); - params.dont_connect = layerm.region()->config.infill_not_connected.getBool(); + params.fill_exactly = layerm.region()->config().enforce_full_fill_volume.getBool(); + params.dont_connect = layerm.region()->config().infill_not_connected.getBool(); // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) @@ -293,8 +293,8 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) float flow_percent = 1; if(surface.is_overBridge()){ - params.density = layerm.region()->config.over_bridge_flow_ratio; - //params.flow_mult = layerm.region()->config.over_bridge_flow_ratio; + params.density = layerm.region()->config().over_bridge_flow_ratio; + //params.flow_mult = layerm.region()->config().over_bridge_flow_ratio; } f->fill_surface_extrusion(&surface, params, flow, erNone, out.entities); diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index f83ee71f1..bc2defe93 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -151,7 +151,7 @@ void Fill::fill_surface_extrusion(const Surface *surface, const FillParams ¶ for (auto pline = polylines.begin(); pline != polylines.end(); ++pline){ Lines lines = pline->lines(); for (auto line = lines.begin(); line != lines.end(); ++line){ - lengthTot += unscale(line->length()); + lengthTot += unscaled(line->length()); nbLines++; } } @@ -159,13 +159,13 @@ void Fill::fill_surface_extrusion(const Surface *surface, const FillParams ¶ // compute real volume double poylineVolume = 0; for (auto poly = this->no_overlap_expolygons.begin(); poly != this->no_overlap_expolygons.end(); ++poly) { - poylineVolume += flow.height*unscale(unscale(poly->area())); + poylineVolume += flow.height*unscaled(unscaled(poly->area())); // add external "perimeter gap" - double perimeterRoundGap = unscale(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; + double perimeterRoundGap = unscaled(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; // add holes "perimeter gaps" double holesGaps = 0; for (auto hole = poly->holes.begin(); hole != poly->holes.end(); ++hole) { - holesGaps += unscale(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; + holesGaps += unscaled(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; } poylineVolume += perimeterRoundGap + holesGaps; } @@ -196,7 +196,7 @@ void Fill::fill_surface_extrusion(const Surface *surface, const FillParams ¶ } /// push the path extrusion_entities_append_paths( - eec->entities, STDMOVE(polylines), + eec->entities, std::move(polylines), good_role, flow.mm3_per_mm() * params.flow_mult * multFlow, flow.width * params.flow_mult * multFlow, diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 0d2f05251..b3e01b361 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -89,8 +89,8 @@ void FillConcentricWGapFill::fill_surface_extrusion(const Surface *surface, cons coord_t distance = coord_t(min_spacing / params.density); if (params.density > 0.9999f && !params.dont_adjust) { - distance = this->_adjust_solid_spacing(bounding_box.size().x, distance); - this->spacing = unscale(distance); + distance = this->_adjust_solid_spacing(bounding_box.size().x(), distance); + this->spacing = unscaled(distance); } ExPolygons gaps; diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp index 8fc129eac..8a0b90ead 100644 --- a/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/src/libslic3r/Fill/FillRectilinear3.cpp @@ -22,7 +22,7 @@ #undef NDEBUG #define DEBUG #define _DEBUG - #include "SVG.hpp" + #include "../SVG.hpp" #endif #include diff --git a/xs/src/libslic3r/Fill/FillSmooth.cpp b/src/libslic3r/Fill/FillSmooth.cpp similarity index 91% rename from xs/src/libslic3r/Fill/FillSmooth.cpp rename to src/libslic3r/Fill/FillSmooth.cpp index 3b936a653..6497af7f6 100644 --- a/xs/src/libslic3r/Fill/FillSmooth.cpp +++ b/src/libslic3r/Fill/FillSmooth.cpp @@ -37,12 +37,12 @@ namespace Slic3r { double volumeToOccupy = 0; for (auto poly = this->no_overlap_expolygons.begin(); poly != this->no_overlap_expolygons.end(); ++poly) { // add external "perimeter gap" - double poylineVolume = flow.height*unscale(unscale(poly->area())); - double perimeterRoundGap = unscale(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; + double poylineVolume = flow.height*unscaled(unscaled(poly->area())); + double perimeterRoundGap = unscaled(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; // add holes "perimeter gaps" double holesGaps = 0; for (auto hole = poly->holes.begin(); hole != poly->holes.end(); ++hole) { - holesGaps += unscale(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; + holesGaps += unscaled(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; } poylineVolume += perimeterRoundGap + holesGaps; @@ -88,7 +88,7 @@ namespace Slic3r { for (auto pline = polylines_layer1.begin(); pline != polylines_layer1.end(); ++pline) { Lines lines = pline->lines(); for (auto line = lines.begin(); line != lines.end(); ++line) { - lengthTot += unscale(line->length()); + lengthTot += unscaled(line->length()); nbLines++; } } @@ -102,7 +102,7 @@ namespace Slic3r { eecroot->entities.push_back(eec); eec->no_sort = false; //can be sorted inside the pass extrusion_entities_append_paths( - eec->entities, STDMOVE(polylines_layer1), + eec->entities, std::move(polylines_layer1), flow.bridge ? erBridgeInfill : rolePass[0], //reduced flow height for a better view (it's only a gui thing) params.flow_mult * flow.mm3_per_mm() * percentFlow[0] * (params.fill_exactly ? volumeToOccupy / extrudedVolume : 1), @@ -122,18 +122,18 @@ namespace Slic3r { for (auto pline = polylines_layer1.begin(); pline != polylines_layer1.end(); ++pline) { Lines lines = pline->lines(); for (auto line = lines.begin(); line != lines.end(); ++line) { - lengthTot += unscale(line->length()); + lengthTot += unscaled(line->length()); nbLines++; } } // add external "perimeter gap" - double poylineVolume = flow.height*unscale(unscale(poly->area())); - double perimeterRoundGap = unscale(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; + double poylineVolume = flow.height*unscaled(unscaled(poly->area())); + double perimeterRoundGap = unscaled(poly->contour.length()) * flow.height * (1 - 0.25*PI) * 0.5; // add holes "perimeter gaps" double holesGaps = 0; for (auto hole = poly->holes.begin(); hole != poly->holes.end(); ++hole) { - holesGaps += unscale(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; + holesGaps += unscaled(hole->length()) * flow.height * (1 - 0.25*PI) * 0.5; } poylineVolume += perimeterRoundGap + holesGaps; @@ -149,7 +149,7 @@ namespace Slic3r { good_role = flow.bridge ? erBridgeInfill : rolePass[0]; } extrusion_entities_append_paths( - eec->entities, STDMOVE(polylines_layer1), + eec->entities, std::move(polylines_layer1), good_role, //reduced flow height for a better view (it's only a gui thing) params.flow_mult * flow.mm3_per_mm() * percentFlow[0] * (params.fill_exactly ? poylineVolume / extrudedVolume : 1), @@ -187,7 +187,7 @@ namespace Slic3r { for (auto pline = polylines_layer2.begin(); pline != polylines_layer2.end(); ++pline){ Lines lines = pline->lines(); for (auto line = lines.begin(); line != lines.end(); ++line){ - lengthTot += unscale(line->length()); + lengthTot += unscaled(line->length()); nbLines++; } } @@ -205,7 +205,7 @@ namespace Slic3r { } // print thin extrusion_entities_append_paths( - eec->entities, STDMOVE(polylines_layer2), + eec->entities, std::move(polylines_layer2), good_role, params.flow_mult * flow.mm3_per_mm() * percentFlow[1] * (params.fill_exactly ? volumeToOccupy / extrudedVolume : 1), //min-reduced flow width for a better view (it's only a gui thing) @@ -244,7 +244,7 @@ namespace Slic3r { for (auto pline = polylines_layer3.begin(); pline != polylines_layer3.end(); ++pline){ Lines lines = pline->lines(); for (auto line = lines.begin(); line != lines.end(); ++line){ - lengthTot += unscale(line->length()); + lengthTot += unscaled(line->length()); nbLines++; } } @@ -261,7 +261,7 @@ namespace Slic3r { } // print thin extrusion_entities_append_paths( - eec->entities, STDMOVE(polylines_layer3), + eec->entities, std::move(polylines_layer3), good_role, //slow (if last) //reduced flow width for a better view (it's only a gui thing) params.flow_mult * flow.mm3_per_mm() * percentFlow[2] * (params.fill_exactly ? volumeToOccupy / extrudedVolume : 1), diff --git a/xs/src/libslic3r/Fill/FillSmooth.hpp b/src/libslic3r/Fill/FillSmooth.hpp similarity index 100% rename from xs/src/libslic3r/Fill/FillSmooth.hpp rename to src/libslic3r/Fill/FillSmooth.hpp diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 43c99f19f..f5750162f 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2,7 +2,7 @@ #include "../Model.hpp" #include "../Utils.hpp" #include "../GCode.hpp" -#include "../slic3r/GUI/PresetBundle.hpp" +#include "../Geometry.hpp" #include "3mf.hpp" @@ -30,6 +30,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; const char* RESOURCES_TAG = "resources"; @@ -322,6 +323,7 @@ namespace Slic3r { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file unsigned int m_version; @@ -337,23 +339,27 @@ namespace Slic3r { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToSlaSupportPointsMap m_sla_support_points; std::string m_curr_metadata_name; std::string m_curr_characters; + std::string m_name; public: _3MF_Importer(); ~_3MF_Importer(); - bool load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle); + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); private: void _destroy_xml_parser(); void _stop_xml_parser(); - bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle); + bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); + void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); // handlers to parse the .model file @@ -439,6 +445,7 @@ namespace Slic3r { , m_unit_factor(1.0f) , m_curr_metadata_name("") , m_curr_characters("") + , m_name("") { } @@ -447,7 +454,7 @@ namespace Slic3r { _destroy_xml_parser(); } - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle) + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) { m_version = 0; m_model = &model; @@ -461,11 +468,12 @@ namespace Slic3r { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); clear_errors(); - return _load_model_from_file(filename, model, bundle); + return _load_model_from_file(filename, model, config); } void _3MF_Importer::_destroy_xml_parser() @@ -483,7 +491,7 @@ namespace Slic3r { XML_StopParser(m_xml_parser, false); } - bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle) + bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -499,6 +507,8 @@ namespace Slic3r { mz_zip_archive_file_stat stat; + m_name = boost::filesystem::path(filename).filename().stem().string(); + // we first loop the entries to read from the archive the .model file only, in order to extract the version from it for (mz_uint i = 0; i < num_entries; ++i) { @@ -533,10 +543,15 @@ namespace Slic3r { // extract slic3r lazer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) + { + // extract sla support points file + _extract_sla_support_points_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, bundle, filename); + _extract_print_config_from_archive(archive, stat, config, filename); } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { @@ -572,6 +587,10 @@ namespace Slic3r { object.second->layer_height_profile_valid = true; } + IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.first); + if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) + object.second->sla_support_points = obj_sla_support_points->second; + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) { @@ -656,7 +675,7 @@ namespace Slic3r { return true; } - void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename) + void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { @@ -667,7 +686,7 @@ namespace Slic3r { add_error("Error while reading config data to buffer"); return; } - bundle.load_config_string(buffer.data(), archive_filename.c_str()); + config.load_from_gcode_string(buffer.data()); } } @@ -742,6 +761,71 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + for (const std::string& object : objects) + { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) + { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) + { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) + { + add_error("Found invalid object id"); + continue; + } + + IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); + if (object_item != m_sla_support_points.end()) + { + add_error("Found duplicated SLA support points"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + std::vector sla_support_points; + + for (unsigned int i=0; i()); + + if (!sla_support_points.empty()) + m_sla_support_points.insert(IdToSlaSupportPointsMap::value_type(object_id, sla_support_points)); + } + } + } + + bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model) { if (stat.m_uncomp_size == 0) @@ -971,6 +1055,9 @@ namespace Slic3r { // set object data m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (m_curr_object.object->name.empty()) + m_curr_object.object->name = m_name + "_" + std::to_string(m_model->objects.size()); + m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); } @@ -1249,57 +1336,49 @@ namespace Slic3r { void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) { - // slic3r ModelInstance cannot be transformed using a matrix - // we extract from the given matrix only the values currently used +#if ENABLE_MODELVOLUME_TRANSFORM + Slic3r::Geometry::Transformation t(transform); + // invalid scale value, return + if (!t.get_scaling_factor().all()) + return; - // translation -#if ENABLE_MODELINSTANCE_3D_OFFSET - Vec3d offset(transform(0, 3), transform(1, 3), transform(2, 3)); + instance.set_transformation(t); #else - double offset_x = transform(0, 3); - double offset_y = transform(1, 3); - double offset_z = transform(2, 3); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + // translation + Vec3d offset = transform.matrix().block(0, 3, 3, 1); + + Eigen::Matrix m3x3 = transform.matrix().block(0, 0, 3, 3); + // mirror + // it is impossible to reconstruct the original mirroring factors from a matrix, + // we can only detect if the matrix contains a left handed reference system + // in which case we reorient it back to right handed by mirroring the x axis + Vec3d mirror = Vec3d::Ones(); + if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) + { + mirror(0) = -1.0; + // remove mirror + m3x3.col(0) *= -1.0; + } // scale - double sx = ::sqrt(sqr(transform(0, 0)) + sqr(transform(1, 0)) + sqr(transform(2, 0))); - double sy = ::sqrt(sqr(transform(0, 1)) + sqr(transform(1, 1)) + sqr(transform(2, 1))); - double sz = ::sqrt(sqr(transform(0, 2)) + sqr(transform(1, 2)) + sqr(transform(2, 2))); + Vec3d scale(m3x3.col(0).norm(), m3x3.col(1).norm(), m3x3.col(2).norm()); // invalid scale value, return - if ((sx == 0.0) || (sy == 0.0) || (sz == 0.0)) + if ((scale(0) == 0.0) || (scale(1) == 0.0) || (scale(2) == 0.0)) return; - // non-uniform scale value, return - if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001)) - return; + // remove scale + m3x3.col(0).normalize(); + m3x3.col(1).normalize(); + m3x3.col(2).normalize(); - double inv_sx = 1.0 / sx; - double inv_sy = 1.0 / sy; - double inv_sz = 1.0 / sz; + Vec3d rotation = Slic3r::Geometry::extract_euler_angles(m3x3); - Eigen::Matrix3d m3x3; - m3x3 << transform(0, 0) * inv_sx, transform(0, 1) * inv_sy, transform(0, 2) * inv_sz, - transform(1, 0) * inv_sx, transform(1, 1) * inv_sy, transform(1, 2) * inv_sz, - transform(2, 0) * inv_sx, transform(2, 1) * inv_sy, transform(2, 2) * inv_sz; - - Eigen::AngleAxisd rotation; - rotation.fromRotationMatrix(m3x3); - - // invalid rotation axis, we currently handle only rotations around Z axis - if ((rotation.angle() != 0.0) && (rotation.axis() != Vec3d::UnitZ()) && (rotation.axis() != -Vec3d::UnitZ())) - return; - - double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle(); - -#if ENABLE_MODELINSTANCE_3D_OFFSET instance.set_offset(offset); -#else - instance.offset(0) = offset_x; - instance.offset(1) = offset_y; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - instance.scaling_factor = sx; - instance.rotation = angle_z; + instance.set_scaling_factor(scale); + instance.set_rotation(rotation); + instance.set_mirror(mirror); +#endif // ENABLE_MODELVOLUME_TRANSFORM } bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) @@ -1344,7 +1423,7 @@ namespace Slic3r { return false; } - m_curr_config.volume_id = object->second.volumes.size(); + m_curr_config.volume_id = (int)object->second.volumes.size(); unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); @@ -1402,7 +1481,7 @@ namespace Slic3r { return false; } - unsigned int geo_tri_count = geometry.triangles.size() / 3; + unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3; for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { @@ -1423,7 +1502,7 @@ namespace Slic3r { unsigned int src_start_id = volume_data.first_triangle_id * 3; - for (size_t i = 0; i < triangles_count; ++i) + for (unsigned int i = 0; i < triangles_count; ++i) { unsigned int ii = i * 3; stl_facet& facet = stl.facet_start[i]; @@ -1536,10 +1615,10 @@ namespace Slic3r { IdToObjectDataMap m_objects_data; public: - bool save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config); + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config); private: - bool _save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config); + bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config); bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_relationships_file_to_archive(mz_zip_archive& archive); bool _add_model_file_to_archive(mz_zip_archive& archive, Model& model); @@ -1547,17 +1626,18 @@ namespace Slic3r { bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print); + bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model); }; - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config) + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config) { clear_errors(); - return _save_model_to_file(filename, model, print, export_print_config); + return _save_model_to_file(filename, model, config); } - bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config) + bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config) { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -1603,10 +1683,18 @@ namespace Slic3r { return false; } - // adds slic3r print config file - if (export_print_config) + // adds sla support points file + if (!_add_sla_support_points_file_to_archive(archive, model)) { - if (!_add_print_config_file_to_archive(archive, print)) + mz_zip_writer_end(&archive); + boost::filesystem::remove(filename); + return false; + } + + // adds slic3r print config file + if (config != nullptr) + { + if (!_add_print_config_file_to_archive(archive, *config)) { mz_zip_writer_end(&archive); boost::filesystem::remove(filename); @@ -1747,7 +1835,7 @@ namespace Slic3r { stream << " \n"; } - Transform3d t = instance->world_matrix(); + Transform3d t = instance->get_matrix(); build_items.emplace_back(instance_id, t); stream << " \n"; @@ -1770,7 +1858,7 @@ namespace Slic3r { if (volume == nullptr) continue; - VolumeToOffsetsMap::iterator volume_it = volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; + volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; if (!volume->mesh.repaired) volume->mesh.repair(); @@ -1787,12 +1875,23 @@ namespace Slic3r { vertices_count += stl.stats.shared_vertices; +#if ENABLE_MODELVOLUME_TRANSFORM + const Transform3d& matrix = volume->get_matrix(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + for (int i = 0; i < stl.stats.shared_vertices; ++i) { stream << " <" << VERTEX_TAG << " "; +#if ENABLE_MODELVOLUME_TRANSFORM + Vec3d v = matrix * stl.v_shared[i].cast(); + stream << "x=\"" << v(0) << "\" "; + stream << "y=\"" << v(1) << "\" "; + stream << "z=\"" << v(2) << "\" />\n"; +#else stream << "x=\"" << stl.v_shared[i](0) << "\" "; stream << "y=\"" << stl.v_shared[i](1) << "\" "; stream << "z=\"" << stl.v_shared[i](2) << "\" />\n"; +#endif // ENABLE_MODELVOLUME_TRANSFORM } } @@ -1900,13 +1999,51 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print) + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) + { + ++count; + const std::vector& sla_support_points = object->sla_support_points; + if (!sla_support_points.empty()) + { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < sla_support_points.size(); ++i) + { + sprintf(buffer, (i==0 ? "%f %f %f" : " %f %f %f"), sla_support_points[i](0), sla_support_points[i](1), sla_support_points[i](2)); + out += buffer; + } + out += "\n"; + } + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } + + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) { char buffer[1024]; sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; - GCode::append_full_config(print, out); + for (const std::string &key : config.keys()) + if (key != "compatible_printers") + out += "; " + key + " = " + config.serialize(key) + "\n"; if (!out.empty()) { @@ -1995,24 +2132,24 @@ namespace Slic3r { return true; } - bool load_3mf(const char* path, PresetBundle* bundle, Model* model) + bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model) { - if ((path == nullptr) || (bundle == nullptr) || (model == nullptr)) + if ((path == nullptr) || (config == nullptr) || (model == nullptr)) return false; _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *bundle); + bool res = importer.load_model_from_file(path, *model, *config); importer.log_errors(); return res; } - bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config) + bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config) { - if ((path == nullptr) || (model == nullptr) || (print == nullptr)) + if ((path == nullptr) || (model == nullptr)) return false; _3MF_Exporter exporter; - bool res = exporter.save_model_to_file(path, *model, *print, export_print_config); + bool res = exporter.save_model_to_file(path, *model, config); if (!res) exporter.log_errors(); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 85bc812e3..44b37c1ae 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -4,15 +4,14 @@ namespace Slic3r { class Model; - class Print; - class PresetBundle; + class DynamicPrintConfig; // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, PresetBundle* bundle, Model* model); + extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices - extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config); + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config); }; // namespace Slic3r diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 458ce79de..089368865 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -8,8 +8,8 @@ #include "../libslic3r.h" #include "../Model.hpp" #include "../GCode.hpp" +#include "../PrintConfig.hpp" #include "../Utils.hpp" -#include "../slic3r/GUI/PresetBundle.hpp" #include "AMF.hpp" #include @@ -29,12 +29,11 @@ // VERSION NUMBERS // 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them. // 1 : Introduction of amf versioning. No other change in data saved into amf files. -#if ENABLE_MODELINSTANCE_3D_OFFSET -// 2 : Added z component of offset. +// 2 : Added z component of offset +// Added x and y components of rotation +// Added x, y and z components of scale +// Added x, y and z components of mirror const unsigned int VERSION_AMF = 2; -#else -const unsigned int VERSION_AMF = 1; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; @@ -44,7 +43,7 @@ namespace Slic3r struct AMFParserContext { - AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) : + AMFParserContext(XML_Parser parser, DynamicPrintConfig *config, Model *model) : m_version(0), m_parser(parser), m_model(*model), @@ -52,8 +51,7 @@ struct AMFParserContext m_volume(nullptr), m_material(nullptr), m_instance(nullptr), - m_preset_bundle(preset_bundle), - m_archive_filename(archive_filename) + m_config(config) { m_path.reserve(12); } @@ -124,37 +122,58 @@ struct AMFParserContext NODE_TYPE_INSTANCE, // amf/constellation/instance NODE_TYPE_DELTAX, // amf/constellation/instance/deltax NODE_TYPE_DELTAY, // amf/constellation/instance/deltay -#if ENABLE_MODELINSTANCE_3D_OFFSET NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + NODE_TYPE_RX, // amf/constellation/instance/rx + NODE_TYPE_RY, // amf/constellation/instance/ry NODE_TYPE_RZ, // amf/constellation/instance/rz NODE_TYPE_SCALE, // amf/constellation/instance/scale + NODE_TYPE_SCALEX, // amf/constellation/instance/scalex + NODE_TYPE_SCALEY, // amf/constellation/instance/scaley + NODE_TYPE_SCALEZ, // amf/constellation/instance/scalez + NODE_TYPE_MIRRORX, // amf/constellation/instance/mirrorx + NODE_TYPE_MIRRORY, // amf/constellation/instance/mirrory + NODE_TYPE_MIRRORZ, // amf/constellation/instance/mirrorz NODE_TYPE_METADATA, // anywhere under amf/*/metadata }; struct Instance { -#if ENABLE_MODELINSTANCE_3D_OFFSET - Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {} -#else - Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {} -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + Instance() + : deltax_set(false), deltay_set(false), deltaz_set(false) + , rx_set(false), ry_set(false), rz_set(false) + , scalex_set(false), scaley_set(false), scalez_set(false) + , mirrorx_set(false), mirrory_set(false), mirrorz_set(false) {} // Shift in the X axis. float deltax; bool deltax_set; // Shift in the Y axis. float deltay; bool deltay_set; -#if ENABLE_MODELINSTANCE_3D_OFFSET // Shift in the Z axis. float deltaz; bool deltaz_set; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + // Rotation around the X axis. + float rx; + bool rx_set; + // Rotation around the Y axis. + float ry; + bool ry_set; // Rotation around the Z axis. float rz; bool rz_set; - // Scaling factor - float scale; - bool scale_set; + // Scaling factors + float scalex; + bool scalex_set; + float scaley; + bool scaley_set; + float scalez; + bool scalez_set; + // Mirroring factors + float mirrorx; + bool mirrorx_set; + float mirrory; + bool mirrory_set; + float mirrorz; + bool mirrorz_set; }; struct Object { @@ -187,10 +206,8 @@ struct AMFParserContext Instance *m_instance; // Generic string buffer for vertices, face indices, metadata etc. std::string m_value[3]; - // Pointer to preset bundle to update if config data are stored inside the amf file - PresetBundle* m_preset_bundle; - // Fullpath name of the amf file - std::string m_archive_filename; + // Pointer to config to update if config data are stored inside the amf file + DynamicPrintConfig *m_config; private: AMFParserContext& operator=(AMFParserContext&); @@ -271,14 +288,28 @@ void AMFParserContext::startElement(const char *name, const char **atts) node_type_new = NODE_TYPE_DELTAX; else if (strcmp(name, "deltay") == 0) node_type_new = NODE_TYPE_DELTAY; -#if ENABLE_MODELINSTANCE_3D_OFFSET else if (strcmp(name, "deltaz") == 0) node_type_new = NODE_TYPE_DELTAZ; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + else if (strcmp(name, "rx") == 0) + node_type_new = NODE_TYPE_RX; + else if (strcmp(name, "ry") == 0) + node_type_new = NODE_TYPE_RY; else if (strcmp(name, "rz") == 0) node_type_new = NODE_TYPE_RZ; + else if (strcmp(name, "scalex") == 0) + node_type_new = NODE_TYPE_SCALEX; + else if (strcmp(name, "scaley") == 0) + node_type_new = NODE_TYPE_SCALEY; + else if (strcmp(name, "scalez") == 0) + node_type_new = NODE_TYPE_SCALEZ; else if (strcmp(name, "scale") == 0) node_type_new = NODE_TYPE_SCALE; + else if (strcmp(name, "mirrorx") == 0) + node_type_new = NODE_TYPE_MIRRORX; + else if (strcmp(name, "mirrory") == 0) + node_type_new = NODE_TYPE_MIRRORY; + else if (strcmp(name, "mirrorz") == 0) + node_type_new = NODE_TYPE_MIRRORZ; } break; case 4: @@ -335,15 +366,19 @@ void AMFParserContext::characters(const XML_Char *s, int len) { switch (m_path.size()) { case 4: -#if ENABLE_MODELINSTANCE_3D_OFFSET - if (m_path.back() == NODE_TYPE_DELTAX || - m_path.back() == NODE_TYPE_DELTAY || - m_path.back() == NODE_TYPE_DELTAZ || - m_path.back() == NODE_TYPE_RZ || - m_path.back() == NODE_TYPE_SCALE) -#else - if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE) -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + if (m_path.back() == NODE_TYPE_DELTAX || + m_path.back() == NODE_TYPE_DELTAY || + m_path.back() == NODE_TYPE_DELTAZ || + m_path.back() == NODE_TYPE_RX || + m_path.back() == NODE_TYPE_RY || + m_path.back() == NODE_TYPE_RZ || + m_path.back() == NODE_TYPE_SCALEX || + m_path.back() == NODE_TYPE_SCALEY || + m_path.back() == NODE_TYPE_SCALEZ || + m_path.back() == NODE_TYPE_SCALE || + m_path.back() == NODE_TYPE_MIRRORX || + m_path.back() == NODE_TYPE_MIRRORY || + m_path.back() == NODE_TYPE_MIRRORZ) m_value[0].append(s, len); break; case 6: @@ -383,14 +418,24 @@ void AMFParserContext::endElement(const char * /* name */) m_instance->deltay_set = true; m_value[0].clear(); break; -#if ENABLE_MODELINSTANCE_3D_OFFSET case NODE_TYPE_DELTAZ: assert(m_instance); m_instance->deltaz = float(atof(m_value[0].c_str())); m_instance->deltaz_set = true; m_value[0].clear(); break; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + case NODE_TYPE_RX: + assert(m_instance); + m_instance->rx = float(atof(m_value[0].c_str())); + m_instance->rx_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_RY: + assert(m_instance); + m_instance->ry = float(atof(m_value[0].c_str())); + m_instance->ry_set = true; + m_value[0].clear(); + break; case NODE_TYPE_RZ: assert(m_instance); m_instance->rz = float(atof(m_value[0].c_str())); @@ -399,8 +444,48 @@ void AMFParserContext::endElement(const char * /* name */) break; case NODE_TYPE_SCALE: assert(m_instance); - m_instance->scale = float(atof(m_value[0].c_str())); - m_instance->scale_set = true; + m_instance->scalex = float(atof(m_value[0].c_str())); + m_instance->scalex_set = true; + m_instance->scaley = float(atof(m_value[0].c_str())); + m_instance->scaley_set = true; + m_instance->scalez = float(atof(m_value[0].c_str())); + m_instance->scalez_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_SCALEX: + assert(m_instance); + m_instance->scalex = float(atof(m_value[0].c_str())); + m_instance->scalex_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_SCALEY: + assert(m_instance); + m_instance->scaley = float(atof(m_value[0].c_str())); + m_instance->scaley_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_SCALEZ: + assert(m_instance); + m_instance->scalez = float(atof(m_value[0].c_str())); + m_instance->scalez_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_MIRRORX: + assert(m_instance); + m_instance->mirrorx = float(atof(m_value[0].c_str())); + m_instance->mirrorx_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_MIRRORY: + assert(m_instance); + m_instance->mirrory = float(atof(m_value[0].c_str())); + m_instance->mirrory_set = true; + m_value[0].clear(); + break; + case NODE_TYPE_MIRRORZ: + assert(m_instance); + m_instance->mirrorz = float(atof(m_value[0].c_str())); + m_instance->mirrorz_set = true; m_value[0].clear(); break; @@ -466,9 +551,8 @@ void AMFParserContext::endElement(const char * /* name */) break; case NODE_TYPE_METADATA: - if ((m_preset_bundle != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) { - m_preset_bundle->load_config_string(m_value[1].c_str(), m_archive_filename.c_str()); - } + if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) + m_config->load_from_gcode_string(m_value[1].c_str()); else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { const char *opt_key = m_value[0].c_str() + 7; if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { @@ -495,7 +579,28 @@ void AMFParserContext::endElement(const char * /* name */) p = end + 1; } m_object->layer_height_profile_valid = true; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { + } + else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) { + // Parse object's layer height profile, a semicolon separated list of floats. + unsigned char coord_idx = 0; + Vec3f point(Vec3f::Zero()); + char *p = const_cast(m_value[1].c_str()); + for (;;) { + char *end = strchr(p, ';'); + if (end != nullptr) + *end = 0; + + point(coord_idx) = atof(p); + if (++coord_idx == 3) { + m_object->sla_support_points.push_back(point); + coord_idx = 0; + } + if (end == nullptr) + break; + p = end + 1; + } + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. @@ -526,7 +631,6 @@ void AMFParserContext::endElement(const char * /* name */) default: break; } - m_path.pop_back(); } @@ -540,20 +644,16 @@ void AMFParserContext::endDocument() for (const Instance &instance : object.second.instances) if (instance.deltax_set && instance.deltay_set) { ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); -#if ENABLE_MODELINSTANCE_3D_OFFSET - mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz)); -#else - mi->offset(0) = instance.deltax; - mi->offset(1) = instance.deltay; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - mi->rotation = instance.rz_set ? instance.rz : 0.f; - mi->scaling_factor = instance.scale_set ? instance.scale : 1.f; + mi->set_offset(Vec3d(instance.deltax_set ? (double)instance.deltax : 0.0, instance.deltay_set ? (double)instance.deltay : 0.0, instance.deltaz_set ? (double)instance.deltaz : 0.0)); + mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0)); + mi->set_scaling_factor(Vec3d(instance.scalex_set ? (double)instance.scalex : 1.0, instance.scaley_set ? (double)instance.scaley : 1.0, instance.scalez_set ? (double)instance.scalez : 1.0)); + mi->set_mirror(Vec3d(instance.mirrorx_set ? (double)instance.mirrorx : 1.0, instance.mirrory_set ? (double)instance.mirrory : 1.0, instance.mirrorz_set ? (double)instance.mirrorz : 1.0)); } } } // Load an AMF file into a provided model. -bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) +bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) { if ((path == nullptr) || (model == nullptr)) return false; @@ -570,7 +670,7 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) return false; } - AMFParserContext ctx(parser, path, bundle, model); + AMFParserContext ctx(parser, config, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -605,7 +705,7 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) return result; } -bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, unsigned int& version) { if (stat.m_uncomp_size == 0) { @@ -621,7 +721,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi return false; } - AMFParserContext ctx(parser, path, bundle, model); + AMFParserContext ctx(parser, config, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -657,7 +757,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi } // Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model) { if ((path == nullptr) || (model == nullptr)) return false; @@ -684,7 +784,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) { if (boost::iends_with(stat.m_filename, ".amf")) { - if (!extract_model_from_archive(archive, stat, path, bundle, model, version)) + if (!extract_model_from_archive(archive, stat, config, model, version)) { mz_zip_reader_end(&archive); printf("Archive does not contain a valid model"); @@ -712,12 +812,12 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) } // Load an AMF file into a provided model. -// If bundle is not a null pointer, updates it if the amf file/archive contains config data -bool load_amf(const char *path, PresetBundle* bundle, Model *model) +// If config is not a null pointer, updates it if the amf file/archive contains config data +bool load_amf(const char *path, DynamicPrintConfig *config, Model *model) { if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output - return load_amf_file(path, bundle, model); + return load_amf_file(path, config, model); else if (boost::iends_with(path, ".amf")) { boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); @@ -728,15 +828,15 @@ bool load_amf(const char *path, PresetBundle* bundle, Model *model) file.read(const_cast(zip_mask.data()), 2); file.close(); - return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model); + return (zip_mask == "PK") ? load_amf_archive(path, config, model) : load_amf_file(path, config, model); } else return false; } -bool store_amf(const char *path, Model *model, Print* print, bool export_print_config) +bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) { - if ((path == nullptr) || (model == nullptr) || (print == nullptr)) + if ((path == nullptr) || (model == nullptr)) return false; // forces ".zip.amf" extension @@ -757,11 +857,13 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << "Slic3r " << SLIC3R_VERSION << "\n"; stream << "" << VERSION_AMF << "\n"; - if (export_print_config) + if (config != nullptr) { - std::string config = "\n"; - GCode::append_full_config(*print, config); - stream << "" << xml_escape(config) << "\n"; + std::string str_config = "\n"; + for (const std::string &key : config->keys()) + if (key != "compatible_printers") + str_config += "; " + key + " = " + config->serialize(key) + "\n"; + stream << "" << xml_escape(str_config) << "\n"; } for (const auto &material : model->materials) { @@ -793,6 +895,19 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << "\n \n"; } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + + const std::vector& sla_support_points = object->sla_support_points; + if (!sla_support_points.empty()) { + // Store the SLA supports as a single semicolon separated list. + stream << " "; + for (size_t i = 0; i < sla_support_points.size(); ++i) { + if (i != 0) + stream << ";"; + stream << sla_support_points[i](0) << ";" << sla_support_points[i](1) << ";" << sla_support_points[i](2); + } + stream << "\n \n"; + } + stream << " \n"; stream << " \n"; std::vector vertices_offsets; @@ -830,7 +945,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { + for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) stream << " " << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "\n"; @@ -847,23 +962,31 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c " \n" " %lf\n" " %lf\n" -#if ENABLE_MODELINSTANCE_3D_OFFSET " %lf\n" -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + " %lf\n" + " %lf\n" " %lf\n" - " %lf\n" + " %lf\n" + " %lf\n" + " %lf\n" + " %lf\n" + " %lf\n" + " %lf\n" " \n", object_id, -#if ENABLE_MODELINSTANCE_3D_OFFSET instance->get_offset(X), instance->get_offset(Y), instance->get_offset(Z), -#else - instance->offset(0), - instance->offset(1), -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - instance->rotation, - instance->scaling_factor); + instance->get_rotation(X), + instance->get_rotation(Y), + instance->get_rotation(Z), + instance->get_scaling_factor(X), + instance->get_scaling_factor(Y), + instance->get_scaling_factor(Z), + instance->get_mirror(X), + instance->get_mirror(Y), + instance->get_mirror(Z)); + //FIXME missing instance->scaling_factor instances.append(buf); } diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index 4779e9a51..e085ad22e 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -4,16 +4,15 @@ namespace Slic3r { class Model; -class Print; -class PresetBundle; +class DynamicPrintConfig; -// Load the content of an amf file into the given model and preset bundle. -extern bool load_amf(const char *path, PresetBundle* bundle, Model *model); +// Load the content of an amf file into the given model and configuration. +extern bool load_amf(const char *path, DynamicPrintConfig *config, Model *model); -// Save the given model and the config data contained in the given Print into an amf file. +// Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices -extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config); +extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config); }; // namespace Slic3r -#endif /* slic3r_Format_AMF_hpp_ */ \ No newline at end of file +#endif /* slic3r_Format_AMF_hpp_ */ diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 45eb56c63..6c0b055d6 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -1,12 +1,10 @@ -#ifdef SLIC3R_PRUS - #include +#include +#include #include -#include -#include -#include +#include #include @@ -35,64 +33,28 @@ struct StlHeader static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct"); -// Buffered line reader for the wxInputStream. +// Buffered line reader to a string buffer. class LineReader { public: - LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) : - m_input_stream(input_stream), - m_pos(0), - m_len(initial_len) - { - assert(initial_len >= 0 && initial_len < m_bufsize); - memcpy(m_buffer, initial_data, initial_len); - } + LineReader(std::vector &data) : m_buffer(data), m_pos(0), m_len((int)data.size()) {} const char* next_line() { - for (;;) { - // Skip empty lines. - while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) - ++ m_pos; - if (m_pos == m_len) { - // Empty buffer, fill it from the input stream. - m_pos = 0; - m_input_stream.Read(m_buffer, m_bufsize - 1); - m_len = m_input_stream.LastRead(); - assert(m_len >= 0 && m_len < m_bufsize); - if (m_len == 0) - // End of file. - return nullptr; - // Skip empty lines etc. - continue; - } - // The buffer is nonempty and it does not start with end of lines. Find the first end of line. - int end = m_pos + 1; - while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') - ++ end; - if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) { - // Move the buffer content to the buffer start and fill the rest of the buffer. - assert(m_pos > 0); - memmove(m_buffer, m_buffer + m_pos, m_len - m_pos); - m_len -= m_pos; - assert(m_len >= 0 && m_len < m_bufsize); - m_pos = 0; - m_input_stream.Read(m_buffer + m_len, m_bufsize - 1 - m_len); - int new_data = m_input_stream.LastRead(); - if (new_data > 0) { - m_len += new_data; - assert(m_len >= 0 && m_len < m_bufsize); - continue; - } - } - char *ptr_out = m_buffer + m_pos; - m_pos = end + 1; - m_buffer[end] = 0; - if (m_pos >= m_len) { - m_pos = 0; - m_len = 0; - } - return ptr_out; + // Skip empty lines. + while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) + ++ m_pos; + if (m_pos == m_len) { + // End of file. + return nullptr; } + // The buffer is nonempty and it does not start with end of lines. Find the first end of line. + int end = m_pos + 1; + while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') + ++ end; + char *ptr_out = m_buffer.data() + m_pos; + m_pos = end + 1; + m_buffer[end] = 0; + return ptr_out; } int next_line_scanf(const char *format, ...) @@ -109,291 +71,284 @@ public: } private: - wxInputStream &m_input_stream; - static const int m_bufsize = 4096; - char m_buffer[m_bufsize]; - int m_pos = 0; - int m_len = 0; + std::vector &m_buffer; + int m_pos; + int m_len; }; +static void extract_model_from_archive( + // name of the model file + const char *name, + // path to the archive + const char *path, + // content of scene.xml + const std::vector &scene_xml_data, + // loaded data of this STL + std::vector &data, + // Model, to which the newly loaded objects will be added + Model *model, + // To map multiple STLs into a single model object for multi-material prints. + std::map &group_to_model_object) +{ + // Find the model entry in the XML data. + char model_name_tag[1024]; + sprintf(model_name_tag, "", name); + const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); + const char *zero_tag = ""; + const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); + float trafo[3][4] = { 0 }; + Vec3d instance_rotation = Vec3d::Zero(); + Vec3d instance_scaling_factor = Vec3d::Ones(); + Vec3d instance_offset = Vec3d::Zero(); + bool trafo_set = false; + unsigned int group_id = (unsigned int)-1; + unsigned int extruder_id = (unsigned int)-1; + ModelObject *model_object = nullptr; + if (model_xml != nullptr) { + model_xml += strlen(model_name_tag); + const char *position_tag = ""; + const char *position_xml = strstr(model_xml, position_tag); + const char *rotation_tag = ""; + const char *rotation_xml = strstr(model_xml, rotation_tag); + const char *scale_tag = ""; + const char *scale_xml = strstr(model_xml, scale_tag); + float position[3], rotation[3], scale[3], zero[3]; + if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr && + sscanf(position_xml+strlen(position_tag), + "[%f, %f, %f]", position, position+1, position+2) == 3 && + sscanf(rotation_xml+strlen(rotation_tag), + "[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 && + sscanf(scale_xml+strlen(scale_tag), + "[%f, %f, %f]", scale, scale+1, scale+2) == 3 && + sscanf(zero_xml+strlen(zero_tag), + "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { + instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]); + instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]); + Eigen::Matrix3f mat_rot, mat_scale, mat_trafo; + mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) * + Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) * + Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX()); + mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]); + mat_trafo = mat_rot * mat_scale; + for (size_t r = 0; r < 3; ++ r) { + for (size_t c = 0; c < 3; ++ c) + trafo[r][c] += mat_trafo(r, c); + } + instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); + // CHECK_ME -> Is the following correct ? + trafo[2][3] = position[2] / (float)instance_scaling_factor(2); + trafo_set = true; + } + const char *group_tag = ""; + const char *group_xml = strstr(model_xml, group_tag); + const char *extruder_tag = ""; + const char *extruder_xml = strstr(model_xml, extruder_tag); + if (group_xml != nullptr) { + int group = atoi(group_xml + strlen(group_tag)); + if (group > 0) { + group_id = group; + auto it = group_to_model_object.find(group_id); + if (it != group_to_model_object.end()) + model_object = it->second; + } + } + if (extruder_xml != nullptr) { + int e = atoi(extruder_xml + strlen(extruder_tag)); + if (e > 0) + extruder_id = e; + } + } + if (! trafo_set) + throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); + + // Extract the STL. + StlHeader header; + TriangleMesh mesh; + bool mesh_valid = false; + bool stl_ascii = false; + if (data.size() > sizeof(StlHeader)) { + memcpy((char*)&header, data.data(), sizeof(StlHeader)); + if (strncmp(header.comment, "solid ", 6) == 0) + stl_ascii = true; + else { + // Header has been extracted. Now read the faces. + stl_file &stl = mesh.stl; + stl.error = 0; + stl.stats.type = inmemory; + stl.stats.number_of_facets = header.nTriangles; + stl.stats.original_num_facets = header.nTriangles; + stl_allocate(&stl); + if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { + memcpy((char*)stl.facet_start, data.data() + sizeof(StlHeader), 50 * header.nTriangles); + if (sizeof(stl_facet) > SIZEOF_STL_FACET) { + // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. + unsigned char *data = (unsigned char*)stl.facet_start; + for (size_t i = header.nTriangles - 1; i > 0; -- i) + memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); + } + // All the faces have been read. + stl_get_size(&stl); + mesh.repair(); + // Transform the model. + stl_transform(&stl, &trafo[0][0]); + if (std::abs(stl.stats.min(2)) < EPSILON) + stl.stats.min(2) = 0.; + // Add a mesh to a model. + if (mesh.facets_count() > 0) + mesh_valid = true; + } + } + } else + stl_ascii = true; + + if (stl_ascii) { + // Try to parse ASCII STL. + char normal_buf[3][32]; + stl_facet facet; + std::vector facets; + LineReader line_reader(data); + std::string solid_name; + facet.extra[0] = facet.extra[1] = 0; + for (;;) { + const char *line = line_reader.next_line(); + if (line == nullptr) + // End of file. + break; + if (strncmp(line, "solid", 5) == 0) { + // Opening the "solid" block. + if (! solid_name.empty()) { + // Error, solid block is already open. + facets.clear(); + break; + } + solid_name = line + 5; + if (solid_name.empty()) + solid_name = "unknown"; + continue; + } + if (strncmp(line, "endsolid", 8) == 0) { + // Closing the "solid" block. + if (solid_name.empty()) { + // Error, no solid block is open. + facets.clear(); + break; + } + solid_name.clear(); + continue; + } + // Line has to start with the word solid. + int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); + assert(res_normal == 3); + int res_outer_loop = line_reader.next_line_scanf(" outer loop"); + assert(res_outer_loop == 0); + int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); + assert(res_vertex1 == 3); + int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); + assert(res_vertex2 == 3); + int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); + assert(res_vertex3 == 3); + int res_endloop = line_reader.next_line_scanf(" endloop"); + assert(res_endloop == 0); + int res_endfacet = line_reader.next_line_scanf(" endfacet"); + if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { + // perror("Something is syntactically very wrong with this ASCII STL!"); + facets.clear(); + break; + } + // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. + if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { + // Normal was mangled. Maybe denormals or "not a number" were stored? + // Just reset the normal and silently ignore it. + memset(&facet.normal, 0, sizeof(facet.normal)); + } + facets.emplace_back(facet); + } + if (! facets.empty() && solid_name.empty()) { + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = (uint32_t)facets.size(); + stl.stats.original_num_facets = (int)facets.size(); + stl_allocate(&stl); + memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); + stl_get_size(&stl); + mesh.repair(); + // Transform the model. + stl_transform(&stl, &trafo[0][0]); + // Add a mesh to a model. + if (mesh.facets_count() > 0) + mesh_valid = true; + } + } + + if (! mesh_valid) + throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name); + + // Add this mesh to the model. + ModelVolume *volume = nullptr; + if (model_object == nullptr) { + // This is a first mesh of a group. Create a new object & volume. + model_object = model->add_object(name, path, std::move(mesh)); + volume = model_object->volumes.front(); + ModelInstance *instance = model_object->add_instance(); + instance->set_rotation(instance_rotation); + instance->set_scaling_factor(instance_scaling_factor); + instance->set_offset(instance_offset); + if (group_id != (size_t)-1) + group_to_model_object[group_id] = model_object; + } else { + // This is not the 1st mesh of a group. Add it to the ModelObject. + volume = model_object->add_volume(std::move(mesh)); + volume->name = name; + } + // Set the extruder to the volume. + if (extruder_id != (unsigned int)-1) { + char str_extruder[64]; + sprintf(str_extruder, "%ud", extruder_id); + volume->config.set_deserialize("extruder", str_extruder); + } +} + // Load a PrusaControl project file into a provided model. bool load_prus(const char *path, Model *model) { - // To receive the content of the zipped 'scene.xml' file. - std::vector scene_xml_data; - wxFFileInputStream in( -#ifdef WIN32 - // On Windows, convert to a 16bit unicode string. - boost::nowide::widen(path).c_str() -#else - path -#endif - ); - wxZipInputStream zip(in); - std::unique_ptr entry; - size_t num_models = 0; - std::map group_to_model_object; - while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) { - wxString name = entry->GetName(); - if (name == "scene.xml") { - if (! scene_xml_data.empty()) { - // scene.xml has been found more than once in the archive. - return false; - } - size_t size_last = 0; - size_t size_incr = 4096; - scene_xml_data.resize(size_incr); - while (! zip.Read(scene_xml_data.data() + size_last, size_incr).Eof()) { - size_last += zip.LastRead(); - if (scene_xml_data.size() < size_last + size_incr) - scene_xml_data.resize(size_last + size_incr); - } - size_last += zip.LastRead(); - if (scene_xml_data.size() == size_last) - scene_xml_data.resize(size_last + 1); - else if (scene_xml_data.size() > size_last + 1) - scene_xml_data.erase(scene_xml_data.begin() + size_last + 1, scene_xml_data.end()); - scene_xml_data[size_last] = 0; - } - else if (name.EndsWith(".stl") || name.EndsWith(".STL")) { - // Find the model entry in the XML data. - const wxScopedCharBuffer name_utf8 = name.ToUTF8(); - char model_name_tag[1024]; - sprintf(model_name_tag, "", name_utf8.data()); - const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); - const char *zero_tag = ""; - const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); - float trafo[3][4] = { 0 }; - double instance_rotation = 0.; - double instance_scaling_factor = 1.f; -#if ENABLE_MODELINSTANCE_3D_OFFSET - Vec3d instance_offset = Vec3d::Zero(); -#else - Vec2d instance_offset(0., 0.); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - bool trafo_set = false; - unsigned int group_id = (unsigned int)-1; - unsigned int extruder_id = (unsigned int)-1; - ModelObject *model_object = nullptr; - if (model_xml != nullptr) { - model_xml += strlen(model_name_tag); - const char *position_tag = ""; - const char *position_xml = strstr(model_xml, position_tag); - const char *rotation_tag = ""; - const char *rotation_xml = strstr(model_xml, rotation_tag); - const char *scale_tag = ""; - const char *scale_xml = strstr(model_xml, scale_tag); - float position[3], rotation[3], scale[3], zero[3]; - if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr && - sscanf(position_xml+strlen(position_tag), - "[%f, %f, %f]", position, position+1, position+2) == 3 && - sscanf(rotation_xml+strlen(rotation_tag), - "[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 && - sscanf(scale_xml+strlen(scale_tag), - "[%f, %f, %f]", scale, scale+1, scale+2) == 3 && - sscanf(zero_xml+strlen(zero_tag), - "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { - if (scale[0] == scale[1] && scale[1] == scale[2]) { - instance_scaling_factor = scale[0]; - scale[0] = scale[1] = scale[2] = 1.; - } - if (rotation[0] == 0. && rotation[1] == 0.) { - instance_rotation = - rotation[2]; - rotation[2] = 0.; - } - Eigen::Matrix3f mat_rot, mat_scale, mat_trafo; - mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) * - Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) * - Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX()); - mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]); - mat_trafo = mat_rot * mat_scale; - for (size_t r = 0; r < 3; ++ r) { - for (size_t c = 0; c < 3; ++ c) - trafo[r][c] += mat_trafo(r, c); - } -#if ENABLE_MODELINSTANCE_3D_OFFSET - instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); -#else - instance_offset(0) = position[0] - zero[0]; - instance_offset(1) = position[1] - zero[1]; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - trafo[2][3] = position[2] / instance_scaling_factor; - trafo_set = true; - } - const char *group_tag = ""; - const char *group_xml = strstr(model_xml, group_tag); - const char *extruder_tag = ""; - const char *extruder_xml = strstr(model_xml, extruder_tag); - if (group_xml != nullptr) { - int group = atoi(group_xml + strlen(group_tag)); - if (group > 0) { - group_id = group; - auto it = group_to_model_object.find(group_id); - if (it != group_to_model_object.end()) - model_object = it->second; - } - } - if (extruder_xml != nullptr) { - int e = atoi(extruder_xml + strlen(extruder_tag)); - if (e > 0) - extruder_id = e; - } - } - if (trafo_set) { - // Extract the STL. - StlHeader header; - TriangleMesh mesh; - bool mesh_valid = false; - bool stl_ascii = false; - if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) { - if (strncmp(header.comment, "solid ", 6) == 0) - stl_ascii = true; - else { - // Header has been extracted. Now read the faces. - stl_file &stl = mesh.stl; - stl.error = 0; - stl.stats.type = inmemory; - stl.stats.number_of_facets = header.nTriangles; - stl.stats.original_num_facets = header.nTriangles; - stl_allocate(&stl); - if (header.nTriangles > 0 && zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) { - if (sizeof(stl_facet) > SIZEOF_STL_FACET) { - // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. - unsigned char *data = (unsigned char*)stl.facet_start; - for (size_t i = header.nTriangles - 1; i > 0; -- i) - memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); - } - // All the faces have been read. - stl_get_size(&stl); - mesh.repair(); - // Transform the model. - stl_transform(&stl, &trafo[0][0]); - if (std::abs(stl.stats.min(2)) < EPSILON) - stl.stats.min(2) = 0.; - // Add a mesh to a model. - if (mesh.facets_count() > 0) - mesh_valid = true; - } - } - } else - stl_ascii = true; - if (stl_ascii) { - // Try to parse ASCII STL. - char normal_buf[3][32]; - stl_facet facet; - std::vector facets; - LineReader line_reader(zip, (char*)&header, zip.LastRead()); - std::string solid_name; - facet.extra[0] = facet.extra[1] = 0; - for (;;) { - const char *line = line_reader.next_line(); - if (line == nullptr) - // End of file. - break; - if (strncmp(line, "solid", 5) == 0) { - // Opening the "solid" block. - if (! solid_name.empty()) { - // Error, solid block is already open. - facets.clear(); - break; - } - solid_name = line + 5; - if (solid_name.empty()) - solid_name = "unknown"; - continue; - } - if (strncmp(line, "endsolid", 8) == 0) { - // Closing the "solid" block. - if (solid_name.empty()) { - // Error, no solid block is open. - facets.clear(); - break; - } - solid_name.clear(); - continue; - } - // Line has to start with the word solid. - int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); - assert(res_normal == 3); - int res_outer_loop = line_reader.next_line_scanf(" outer loop"); - assert(res_outer_loop == 0); - int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); - assert(res_vertex1 == 3); - int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); - assert(res_vertex2 == 3); - int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); - assert(res_vertex3 == 3); - int res_endloop = line_reader.next_line_scanf(" endloop"); - assert(res_endloop == 0); - int res_endfacet = line_reader.next_line_scanf(" endfacet"); - if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { - // perror("Something is syntactically very wrong with this ASCII STL!"); - facets.clear(); - break; - } - // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { - // Normal was mangled. Maybe denormals or "not a number" were stored? - // Just reset the normal and silently ignore it. - memset(&facet.normal, 0, sizeof(facet.normal)); - } - facets.emplace_back(facet); - } - if (! facets.empty() && solid_name.empty()) { - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - stl.stats.number_of_facets = facets.size(); - stl.stats.original_num_facets = facets.size(); - stl_allocate(&stl); - memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); - stl_get_size(&stl); - mesh.repair(); - // Transform the model. - stl_transform(&stl, &trafo[0][0]); - // Add a mesh to a model. - if (mesh.facets_count() > 0) - mesh_valid = true; - } - } - - if (mesh_valid) { - // Add this mesh to the model. - ModelVolume *volume = nullptr; - if (model_object == nullptr) { - // This is a first mesh of a group. Create a new object & volume. - model_object = model->add_object(name_utf8.data(), path, std::move(mesh)); - volume = model_object->volumes.front(); - ModelInstance *instance = model_object->add_instance(); - instance->rotation = instance_rotation; - instance->scaling_factor = instance_scaling_factor; -#if ENABLE_MODELINSTANCE_3D_OFFSET - instance->set_offset(instance_offset); -#else - instance->offset = instance_offset; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - ++num_models; - if (group_id != (size_t)-1) - group_to_model_object[group_id] = model_object; - } else { - // This is not the 1st mesh of a group. Add it to the ModelObject. - volume = model_object->add_volume(std::move(mesh)); - volume->name = name_utf8.data(); - } - // Set the extruder to the volume. - if (extruder_id != (unsigned int)-1) { - char str_extruder[64]; - sprintf(str_extruder, "%ud", extruder_id); - volume->config.set_deserialize("extruder", str_extruder); - } - } + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + mz_bool res = mz_zip_reader_init_file(&archive, path, 0); + size_t n_models_initial = model->objects.size(); + try { + if (res == MZ_FALSE) + throw std::runtime_error(std::string("Unable to init zip reader for ") + path); + std::vector scene_xml_data; + // For grouping multiple STLs into a single ModelObject for multi-material prints. + std::map group_to_model_object; + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + for (mz_uint i = 0; i < num_entries; ++ i) { + mz_zip_archive_file_stat stat; + if (! mz_zip_reader_file_stat(&archive, i, &stat)) + continue; + std::vector buffer; + buffer.assign((size_t)stat.m_uncomp_size, 0); + res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == MZ_FALSE) + std::runtime_error(std::string("Error while extracting a file from ") + path); + if (strcmp(stat.m_filename, "scene.xml") == 0) { + if (! scene_xml_data.empty()) + throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path); + scene_xml_data = std::move(buffer); + } else if (boost::iends_with(stat.m_filename, ".stl")) { + // May throw std::exception + extract_model_from_archive(stat.m_filename, path, scene_xml_data, buffer, model, group_to_model_object); } } + } catch (std::exception &ex) { + mz_zip_reader_end(&archive); + throw ex; } - return num_models > 0; + + mz_zip_reader_end(&archive); + return model->objects.size() > n_models_initial; } }; // namespace Slic3r - -#endif /* SLIC3R_PRUS */ diff --git a/src/libslic3r/Format/PRUS.hpp b/src/libslic3r/Format/PRUS.hpp index 8559a70d6..be5c5c61a 100644 --- a/src/libslic3r/Format/PRUS.hpp +++ b/src/libslic3r/Format/PRUS.hpp @@ -1,4 +1,3 @@ -#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_) #define slic3r_Format_PRUS_hpp_ namespace Slic3r { @@ -10,5 +9,3 @@ class Model; extern bool load_prus(const char *path, Model *model); }; // namespace Slic3r - -#endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 293ee45ae..9c55fe1e1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -373,11 +373,10 @@ std::vector>> GCode::collec size_t layer_idx; }; - PrintObjectPtrs printable_objects = print.get_printable_objects(); - std::vector> per_object(printable_objects.size(), std::vector()); + std::vector> per_object(print.objects().size(), std::vector()); std::vector ordering; - for (size_t i = 0; i < printable_objects.size(); ++i) { - per_object[i] = collect_layers_to_print(*printable_objects[i]); + for (size_t i = 0; i < print.objects().size(); ++i) { + per_object[i] = collect_layers_to_print(*print.objects()[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); @@ -402,7 +401,7 @@ std::vector>> GCode::collec std::pair> merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z); - merged.second.assign(printable_objects.size(), LayerToPrint()); + merged.second.assign(print.objects().size(), LayerToPrint()); for (; i < j; ++ i) { const OrderingItem &oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); @@ -445,7 +444,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ boost::nowide::remove(path_tmp.c_str()); throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } - } catch (std::exception &ex) { + } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. // Close and remove the file. fclose(file); @@ -455,9 +454,12 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ fclose(file); if (print->config().remaining_times.value) { + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode"; m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - if (m_silent_time_estimator_enabled) + if (m_silent_time_estimator_enabled) { + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode"; m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + } } if (! m_placeholder_parser_failed_templates.empty()) { @@ -566,10 +568,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; - PrintObjectPtrs printable_objects = print.get_printable_objects(); if (print.config().complete_objects.value) { // Add each of the object's layers separately. - for (auto object : printable_objects) { + for (auto object : print.objects()) { std::vector zs; zs.reserve(object->layers().size() + object->support_layers().size()); for (auto layer : object->layers()) @@ -582,7 +583,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } else { // Print all objects with the same print_z together. std::vector zs; - for (auto object : printable_objects) { + for (auto object : print.objects()) { zs.reserve(zs.size() + object->layers().size() + object->support_layers().size()); for (auto layer : object->layers()) zs.push_back(layer->print_z); @@ -598,15 +599,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) this->apply_print_config(print.config()); this->set_extruders(print.extruders()); + // Initialize colorprint. + m_colorprint_heights = cast(print.config().colorprint_heights.values); + // Initialize autospeed. { // get the minimum cross-section used in the print std::vector mm3_per_mm; - for (auto object : printable_objects) { - for (size_t region_id = 0; region_id < print.regions().size(); ++region_id) { - auto region = print.regions()[region_id]; + for (auto object : print.objects()) { + for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { + const PrintRegion* region = print.regions()[region_id]; for (auto layer : object->layers()) { - auto layerm = layer->regions()[region_id]; + const LayerRegion* layerm = layer->regions()[region_id]; if (region->config().get_abs_value("perimeter_speed" ) == 0 || region->config().get_abs_value("small_perimeter_speed" ) == 0 || region->config().get_abs_value("external_perimeter_speed" ) == 0 || @@ -667,11 +671,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.throw_if_canceled(); // Write some terse information on the slicing parameters. - const PrintObject *first_object = printable_objects.front(); + const PrintObject *first_object = print.objects().front(); const double layer_height = first_object->config().layer_height.value; const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height); - for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) { - auto region = print.regions()[region_id]; + for (const PrintRegion* region : print.regions()) { _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width); _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width); @@ -706,8 +709,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) bool has_wipe_tower = false; if (print.config().complete_objects.value) { // Find the 1st printing object, find its tool ordering and the initial extruder ID. - for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) { - tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id); + for (; initial_print_object_id < print.objects().size(); ++initial_print_object_id) { + tool_ordering = ToolOrdering(*print.objects()[initial_print_object_id], initial_extruder_id); if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) break; } @@ -791,7 +794,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Collect outer contours of all objects over all layers. // Discard objects only containing thin walls (offset would fail on an empty polygon). Polygons islands; - for (const PrintObject *object : printable_objects) + for (const PrintObject *object : print.objects()) for (const Layer *layer : object->layers()) for (const ExPolygon &expoly : layer->slices.expolygons) for (const Point © : object->copies()) { @@ -844,7 +847,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if (print.config().complete_objects.value) { // Print objects from the smallest to the tallest to avoid collisions // when moving onto next object starting point. - std::vector objects(printable_objects); + std::vector objects(print.objects()); std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size(2) < po2->size(2); }); size_t finished_objects = 0; for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) { @@ -907,8 +910,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Order objects using a nearest neighbor search. std::vector object_indices; Points object_reference_points; - PrintObjectPtrs printable_objects = print.get_printable_objects(); - for (PrintObject *object : printable_objects) + for (PrintObject *object : print.objects()) object_reference_points.push_back(object->copies().front()); Slic3r::Geometry::chained_path(object_reference_points, object_indices); // Sort layers by Z. @@ -923,7 +925,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : printable_objects) + for (const PrintObject *print_object : print.objects()) bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); @@ -1033,9 +1035,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } print.throw_if_canceled(); - // starts analizer calculations - if (preview_data != nullptr) + // starts analyzer calculations + if (preview_data != nullptr) { + BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; m_analyzer.calc_gcode_preview_data(*preview_data); + } } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) @@ -1233,7 +1237,7 @@ void GCode::process_layer( const size_t single_object_idx) { assert(! layers.empty()); - assert(! layer_tools.extruders.empty()); +// assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_idx == size_t(-1) || layers.size() == 1); @@ -1314,6 +1318,18 @@ void GCode::process_layer( m_second_layer_things_done = true; } + // Let's issue a filament change command if requested at this layer. + // In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all. + // (Layers can be close to each other, model could have been resliced with bigger layer height, ...). + bool colorprint_change = false; + while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) { + m_colorprint_heights.erase(m_colorprint_heights.begin()); + colorprint_change = true; + } + if (colorprint_change && print.extruders().size()==1) + gcode += "M600\n"; + + // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. bool extrude_skirt = @@ -1437,7 +1453,7 @@ void GCode::process_layer( }; for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; + const LayerRegion *layerm = (region_id < layer.regions().size()) ? layer.regions()[region_id] : nullptr; if (layerm == nullptr) continue; const PrintRegion ®ion = *print.regions()[region_id]; @@ -1486,7 +1502,7 @@ void GCode::process_layer( // fill->first_point fits inside ith slice point_inside_surface(i, fill->first_point())) { if (islands[i].by_region.empty()) { - islands[i].by_region.assign(print.regions.size(), ObjectByExtruder::Island::Region()); + islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); } //don't do fill->entities because it will discard no_sort islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); @@ -1653,9 +1669,10 @@ void GCode::append_full_config(const Print& print, std::string& str) } const DynamicConfig &full_config = print.placeholder_parser().config(); for (const char *key : { - "print_settings_id", "filament_settings_id", "printer_settings_id", - "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", - "compatible_printers_condition_cummulative", "inherits_cummulative" }) { + "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", + "printer_model", "printer_variant", + "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", + "compatible_prints_condition_cummulative", "compatible_printers_condition_cummulative", "inherits_cummulative" }) { const ConfigOption *opt = full_config.option(key); if (opt != nullptr) str += std::string("; ") + key + " = " + opt->serialize() + "\n"; @@ -2167,9 +2184,11 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path // make a little move inwards before leaving loop - if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1) { + if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (left for contours, right for holes) + //FIXME improve the algorithm in case the loop is tiny. + //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). Point a = paths.front().polyline.points[1]; // second point Point b = *(paths.back().polyline.points.end()-3); // second to last point if (was_clockwise) { @@ -2274,8 +2293,8 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorconfig().infill_first == is_infill_first) { - m_config.apply(print.regions[®ion - &by_region.front()]->config); + if (print.regions()[®ion - &by_region.front()]->config().infill_first == is_infill_first) { + m_config.apply(print.regions()[®ion - &by_region.front()]->config()); ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); gcode += extrude_entity(chained, "infill"); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index d3ccb07c1..2dd1e3571 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -317,6 +317,9 @@ protected: bool m_second_layer_things_done; // Index of a last object copy extruded. std::pair m_last_obj_copy; + // Layer heights for colorprint - updated before the export and erased during the process + // so no toolchange occurs twice. + std::vector m_colorprint_heights; // Time estimators GCodeTimeEstimator m_normal_time_estimator; diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 51d5b1a06..c56f02753 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -14,6 +14,7 @@ static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float INCHES_TO_MM = 25.4f; static const float DEFAULT_FEEDRATE = 0.0f; static const unsigned int DEFAULT_EXTRUDER_ID = 0; +static const unsigned int DEFAULT_COLOR_PRINT_ID = 0; static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f); static const float DEFAULT_START_EXTRUSION = 0.0f; @@ -31,6 +32,7 @@ const float GCodeAnalyzer::Default_Height = 0.0f; GCodeAnalyzer::Metadata::Metadata() : extrusion_role(erNone) , extruder_id(DEFAULT_EXTRUDER_ID) + , cp_color_id(DEFAULT_COLOR_PRINT_ID) , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm) , width(GCodeAnalyzer::Default_Width) , height(GCodeAnalyzer::Default_Height) @@ -38,13 +40,14 @@ GCodeAnalyzer::Metadata::Metadata() { } -GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate) +GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id/* = 0*/) : extrusion_role(extrusion_role) , extruder_id(extruder_id) , mm3_per_mm(mm3_per_mm) , width(width) , height(height) , feedrate(feedrate) + , cp_color_id(cp_color_id) { } @@ -68,12 +71,15 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) if (feedrate != other.feedrate) return true; + if (cp_color_id != other.cp_color_id) + return true; + return false; } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder) +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id/* = 0*/) : type(type) - , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate) + , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, cp_color_id) , start_position(start_position) , end_position(end_position) , delta_extruder(delta_extruder) @@ -101,6 +107,7 @@ void GCodeAnalyzer::reset() _set_e_local_positioning_type(Absolute); _set_extrusion_role(erNone); _set_extruder_id(DEFAULT_EXTRUDER_ID); + _set_cp_color_id(DEFAULT_COLOR_PRINT_ID); _set_mm3_per_mm(Default_mm3_per_mm); _set_width(Default_Width); _set_height(Default_Height); @@ -220,6 +227,11 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi { switch (::atoi(&cmd[1])) { + case 600: // Set color change + { + _processM600(line); + break; + } case 82: // Set extruder to absolute mode { _processM82(line); @@ -401,6 +413,12 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) _set_e_local_positioning_type(Relative); } +void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) +{ + m_state.cur_cp_color_id++; + _set_cp_color_id(m_state.cur_cp_color_id); +} + void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) { std::string cmd = line.cmd(); @@ -532,6 +550,16 @@ unsigned int GCodeAnalyzer::_get_extruder_id() const return m_state.data.extruder_id; } +void GCodeAnalyzer::_set_cp_color_id(unsigned int id) +{ + m_state.data.cp_color_id = id; +} + +unsigned int GCodeAnalyzer::_get_cp_color_id() const +{ + return m_state.data.cp_color_id; +} + void GCodeAnalyzer::_set_mm3_per_mm(double value) { m_state.data.mm3_per_mm = value; @@ -625,7 +653,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) it = m_moves_map.insert(TypeToMovesMap::value_type(type, GCodeMovesList())).first; // store move - it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion()); + it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion(), _get_cp_color_id()); } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const @@ -660,6 +688,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ path.polyline = polyline; path.feedrate = data.feedrate; path.extruder_id = data.extruder_id; + path.cp_color_id = data.cp_color_id; get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path); } @@ -698,7 +727,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ // update current values data = move.data; - z = move.start_position.z(); + z = (float)move.start_position.z(); volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm; height_range.update_from(move.data.height); width_range.update_from(move.data.width); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 27a49b869..f50138b56 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -5,8 +5,8 @@ #include "../PrintConfig.hpp" #include "../ExtrusionEntity.hpp" -#include "Point.hpp" -#include "GCodeReader.hpp" +#include "../Point.hpp" +#include "../GCodeReader.hpp" namespace Slic3r { @@ -53,9 +53,10 @@ public: float width; // mm float height; // mm float feedrate; // mm/s + unsigned int cp_color_id; Metadata(); - Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate); + Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id = 0); bool operator != (const Metadata& other) const; }; @@ -79,7 +80,7 @@ public: Vec3d end_position; float delta_extruder; - GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); + GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id = 0); GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); }; @@ -96,6 +97,7 @@ private: Vec3d start_position = Vec3d::Zero(); float start_extrusion; float position[Num_Axis]; + unsigned int cur_cp_color_id = 0; }; private: @@ -154,6 +156,9 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); + // Set color change + void _processM600(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) void _processT(const GCodeReader::GCodeLine& line); @@ -188,6 +193,9 @@ private: void _set_extruder_id(unsigned int id); unsigned int _get_extruder_id() const; + void _set_cp_color_id(unsigned int id); + unsigned int _get_cp_color_id() const; + void _set_mm3_per_mm(double value); double _get_mm3_per_mm() const; diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index c04aeae3c..e44faa220 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -1,7 +1,8 @@ #include "PostProcessor.hpp" -#if 1 -//#ifdef WIN32 +#include + +#ifdef WIN32 namespace Slic3r { @@ -15,6 +16,10 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config #else #include +#ifndef WIN32 + #include //for getting filesystem UID/GID + #include //for getting current UID/GID +#endif namespace Slic3r { @@ -22,19 +27,30 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config { if (config.post_process.values.empty()) return; + config.setenv_(); + auto gcode_file = boost::filesystem::path(path); + if (! boost::filesystem::exists(gcode_file)) + throw std::runtime_error(std::string("Post-processor can't find exported gcode file")); + for (std::string script: config.post_process.values) { // Ignore empty post processing script lines. boost::trim(script); if (script.empty()) continue; BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path; - if (! boost::filesystem::exists(boost::filesystem::path(path))) - throw std::runtime_exception(std::string("The configured post-processing script does not exist: ") + path); + if (! boost::filesystem::exists(boost::filesystem::path(script))) + throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script); #ifndef WIN32 - file_status fs = boost::filesystem::status(path); - //FIXME test if executible by the effective UID / GID. - // throw std::runtime_exception(std::string("The configured post-processing script is not executable: check permissions. ") + path)); + struct stat info; + if (stat(script.c_str(), &info)) + throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script); + boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions(); + //if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm. + if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe + : ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe + : boost::filesystem::perms::others_exe)))) + throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script); #endif int result = 0; #ifdef WIN32 @@ -45,10 +61,10 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config ::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH); boost::filesystem::path path_exe(wpath_exe); // Replace it with the current perl interpreter. - result = boost::process::system((path_exe.parent_path() / "perl5.24.0.exe").string(), script, output_file); + result = boost::process::system((path_exe.parent_path() / "perl5.24.0.exe").string(), script, gcode_file); } else #else - result = boost::process::system(script, output_file); + result = boost::process::system(script, gcode_file); #endif if (result < 0) BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned."; diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 6736b5006..50d441e5e 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -1,7 +1,6 @@ #include "Analyzer.hpp" #include "PreviewData.hpp" #include -#include #include #include @@ -220,6 +219,7 @@ void GCodePreviewData::Travel::set_default() width = Default_Width; height = Default_Height; ::memcpy((void*)type_colors, (const void*)Default_Type_Colors, Num_Types * sizeof(Color)); + color_print_idx = 0; is_visible = false; } @@ -377,12 +377,14 @@ std::string GCodePreviewData::get_legend_title() const return L("Tool"); case Extrusion::Filament: return L("Filament"); + case Extrusion::ColorPrint: + return L("Color Print"); } return ""; } -GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector& tool_colors) const +GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector& tool_colors, const std::vector>& cp_values) const { struct Helper { @@ -449,6 +451,34 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.emplace_back((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str(), color); } + break; + } + case Extrusion::ColorPrint: + { + const auto color_print_cnt = cp_values.size(); + for (int i = color_print_cnt; i >= 0 ; --i) + { + int val = i; + while (val >= GCodePreviewData::Range::Colors_Count) + val -= GCodePreviewData::Range::Colors_Count; + GCodePreviewData::Color color = Range::Default_Colors[val]; + + if (color_print_cnt == 0) { + items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); + break; + } + if (i == 0) { + items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); + break; + } + if (i == color_print_cnt) { + items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); + continue; + } + +// items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1] % cp_values[i]).str(), color); + items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1].second % cp_values[i].first).str(), color); + } break; } } diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index eef995a4b..c1854505e 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -3,8 +3,7 @@ #include "../libslic3r.h" #include "../ExtrusionEntity.hpp" - -#include "Point.hpp" +#include "../Point.hpp" namespace Slic3r { @@ -73,6 +72,7 @@ public: VolumetricRate, Tool, Filament, + ColorPrint, Num_View_Types }; @@ -142,6 +142,7 @@ public: float height; Color type_colors[Num_Types]; bool is_visible; + size_t color_print_idx; void set_default(); }; @@ -198,7 +199,7 @@ public: void set_extrusion_paths_colors(const std::vector& colors); std::string get_legend_title() const; - LegendItemsList get_legend_items(const std::vector& tool_colors) const; + LegendItemsList get_legend_items(const std::vector& tool_colors, const std::vector>& cp_values) const; }; GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 175b69447..51622e39a 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -68,12 +68,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool { m_print_config_ptr = &print.config(); - PrintObjectPtrs objects = print.get_printable_objects(); // Initialize the print layers for all objects and all layers. coordf_t object_bottom_z = 0.; { std::vector zs; - for (auto object : objects) { + for (auto object : print.objects()) { zs.reserve(zs.size() + object->layers().size() + object->support_layers().size()); for (auto layer : object->layers()) zs.emplace_back(layer->print_z); @@ -86,7 +85,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool } // Collect extruders reuqired to print the layers. - for (auto object : objects) + for (auto object : print.objects()) this->collect_extruders(*object); // Reorder the extruders to minimize tool switches. @@ -151,7 +150,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object) for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); // What extruders are required to print this object layer? - for (size_t region_id = 0; region_id < object.print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) { const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; if (layerm == nullptr) continue; @@ -451,7 +450,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it // we will sort objects so that dedicated for wiping are at the beginning: - PrintObjectPtrs object_list = print.get_printable_objects(); + PrintObjectPtrs object_list = print.objects(); std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; }); // We will now iterate through @@ -479,14 +478,14 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves - for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { const auto& region = *object->print()->regions()[region_id]; if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; - if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) { + if ((!region.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) { for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); @@ -499,7 +498,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (volume_to_wipe<=0) continue; - if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill) + if (!object->config().wipe_into_objects && !region.config().infill_first && region.config().wipe_into_infill) // In this case we must check that the original extruder is used on this layer before the one we are overridding // (and the perimeters will be finished before the infill is printed): if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder)) @@ -513,7 +512,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int } // Now the same for perimeters - see comments above for explanation: - if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done)) + if (object->config().wipe_into_objects && (region.config().infill_first ? perimeters_done : !perimeters_done)) { for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { auto* fill = dynamic_cast(ee); @@ -547,8 +546,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); - PrintObjectPtrs printable_objects = print.get_printable_objects(); - for (const PrintObject* object : printable_objects) { + for (const PrintObject* object : print.objects()) { // Finds this layer: auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)layers().end()) @@ -557,7 +555,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) unsigned int num_of_copies = object->copies().size(); for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves - for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { const auto& region = *object->print()->regions()[region_id]; if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) @@ -574,12 +572,12 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // printed before its perimeter, or not be printed at all (in case its original extruder has // not been added to LayerTools // Either way, we will now force-override it with something suitable: - if (print.config().infill_first + if (region.config().infill_first || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely || lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints || std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME) ) - set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); + set_extruder_override(fill, copy, (region.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. // Force overriding would mean it gets printed before its perimeter. @@ -593,7 +591,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) || is_entity_overridden(fill, copy) ) continue; - set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); + set_extruder_override(fill, copy, (region.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); } } } diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 4dcf6516a..538127810 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -3,7 +3,7 @@ #ifndef slic3r_ToolOrdering_hpp_ #define slic3r_ToolOrdering_hpp_ -#include "libslic3r.h" +#include "../libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 5cbbc1ca9..8ea3abd93 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_WipeTower_hpp_ #define slic3r_WipeTower_hpp_ +#include #include #include #include diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 597b60e94..eb41d6139 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -315,6 +315,20 @@ public: return *this; }; + // Let the firmware back up the active speed override value. + Writer& speed_override_backup() + { + m_gcode += "M220 B\n"; + return *this; + }; + + // Let the firmware restore the active speed override value. + Writer& speed_override_restore() + { + m_gcode += "M220 R\n"; + return *this; + }; + // Set digital trimpot motor Writer& set_extruder_trimpot(int current) { @@ -473,7 +487,6 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam return INVALID; } - // Returns gcode to prime the nozzles at the front edge of the print bed. WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. @@ -501,12 +514,15 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP PRIMING START\n") - .append(";--------------------\n") - .speed_override(100); + .append(";--------------------\n"); + if (m_retain_speed_override) + writer.speed_override_backup(); + writer.speed_override(100); writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position - .travel(cleaning_box.ld, 7200) - .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { unsigned int tool = tools[idx_tool]; @@ -533,8 +549,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) // Reset the extruder current to a normal value. - writer.set_extruder_trimpot(550) - .feedrate(6000) + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + if (m_retain_speed_override) + writer.speed_override_restore(); + writer.feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP PRIMING END\n" @@ -545,7 +564,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( m_print_brim = true; // Ask our writer about how much material was consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); ToolChangeResult result; result.priming = true; @@ -599,14 +619,17 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based .comment_material(m_filpar[m_current_tool].material) - .append(";--------------------\n") - .speed_override(100); + .append(";--------------------\n"); + if (m_retain_speed_override) + writer.speed_override_backup(); + writer.speed_override(100); xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. - writer.set_extruder_trimpot(750); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. @@ -634,8 +657,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } } - writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value. - .feedrate(6000) + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. + if (m_retain_speed_override) + writer.speed_override_restore(); + writer.feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP TOOLCHANGE END\n" @@ -700,7 +726,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded // Ask our writer about how much material was consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); ToolChangeResult result; result.priming = false; @@ -870,7 +897,8 @@ void WipeTowerPrusaMM::toolchange_Change( material_type new_material) { // Ask the writer about how much of the old filament we consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); // Speed override for the material. Go slow for flex and soluble materials. int speed_override; @@ -880,14 +908,15 @@ void WipeTowerPrusaMM::toolchange_Change( case FLEX: speed_override = 35; break; default: speed_override = 100; } - writer.set_tool(new_tool) - .speed_override(speed_override) - .flush_planner_queue(); + writer.set_tool(new_tool); + if (m_retain_speed_override) + assert(speed_override == 100); + else + writer.speed_override(speed_override); + writer.flush_planner_queue(); m_current_tool = new_tool; } - - void WipeTowerPrusaMM::toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box) @@ -915,12 +944,10 @@ void WipeTowerPrusaMM::toolchange_Load( .resume_preview(); // Reset the extruder current to the normal value. - writer.set_extruder_trimpot(550); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); } - - - // Wipe the newly loaded filament until the end of the assigned wipe area. void WipeTowerPrusaMM::toolchange_Wipe( PrusaMultiMaterial::Writer &writer, @@ -1071,7 +1098,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; - // Ask our writer about how much material was consumed. + // Ask our writer about how much material was consumed: if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); @@ -1090,7 +1117,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) { - assert(m_plan.back().z <= z_par + WT_EPSILON ); // refuses to add a layer below the last one + assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index ee2436051..42e3417d3 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -44,7 +44,8 @@ public: // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, + float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, + float bridging, bool set_extruder_trimpot, const std::vector>& wiping_matrix, unsigned int initial_tool, float first_layer_width) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), @@ -57,6 +58,7 @@ public: m_parking_pos_retraction(parking_pos_retraction), m_extra_loading_move(extra_loading_move), m_bridging(bridging), + m_set_extruder_trimpot(set_extruder_trimpot), m_current_tool(initial_tool), wipe_volumes(wiping_matrix), m_brim_width(first_layer_width) @@ -74,6 +76,11 @@ public: m_filpar.push_back(FilamentParameters()); m_filpar[idx].material = material; + if (material == FLEX || material == SCAFF || material == PVA) { + // MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials. + // Therefore it does not make sense to use the new M220 B and M220 R (backup / restore). + m_retain_speed_override = false; + } m_filpar[idx].temperature = temp; m_filpar[idx].first_layer_temperature = first_layer_temp; m_filpar[idx].loading_speed = loading_speed; @@ -213,6 +220,8 @@ private: float m_parking_pos_retraction = 0.f; float m_extra_loading_move = 0.f; float m_bridging = 0.f; + bool m_set_extruder_trimpot = false; + bool m_retain_speed_override = true; bool m_adhesion = true; float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 84ed89a7c..13f9e7dd7 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -126,8 +126,16 @@ private: static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; } static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); } static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); } - static const char* skip_whitespaces(const char *c) { for (; is_whitespace(*c); ++ c); return c; } - static const char* skip_word(const char *c) { for (; ! is_end_of_word(*c); ++ c); return c; } + static const char* skip_whitespaces(const char *c) { + for (; is_whitespace(*c); ++ c) + ; // silence -Wempty-body + return c; + } + static const char* skip_word(const char *c) { + for (; ! is_end_of_word(*c); ++ c) + ; // silence -Wempty-body + return c; + } GCodeConfig m_config; char m_extrusion_axis; diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index f97265ee3..5c8cc2659 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -484,14 +484,14 @@ namespace Slic3r { { _state.filament_load_times.clear(); for (double t : filament_load_times) - _state.filament_load_times.push_back(t); + _state.filament_load_times.push_back((float)t); } void GCodeTimeEstimator::set_filament_unload_times(const std::vector &filament_unload_times) { _state.filament_unload_times.clear(); for (double t : filament_unload_times) - _state.filament_unload_times.push_back(t); + _state.filament_unload_times.push_back((float)t); } float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) @@ -731,7 +731,7 @@ namespace Slic3r { #endif // ENABLE_MOVE_STATS } - _last_st_synchronized_block_id = _blocks.size() - 1; + _last_st_synchronized_block_id = (int)_blocks.size() - 1; // The additional time has been consumed (added to the total time), reset it to zero. set_additional_time(0.); } @@ -1223,7 +1223,8 @@ namespace Slic3r { return; // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - float factor = (dialect == gcfMarlin) ? 1.0f : MMMIN_TO_MMSEC; + // http://smoothieware.org/supported-g-codes + float factor = (dialect == gcfMarlin || dialect == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; if (line.has_x()) set_axis_max_feedrate(X, line.x() * factor); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 283e7c202..c47c5b38b 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,3 +1,4 @@ +#include "libslic3r.h" #include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" @@ -369,12 +370,6 @@ contains(const std::vector &vector, const Point &point) } template bool contains(const ExPolygons &vector, const Point &point); -double -rad2deg(double angle) -{ - return angle / PI * 180.0; -} - double rad2deg_dir(double angle) { @@ -899,4 +894,228 @@ private: }; +void assemble_transform(Transform3d& transform, const Vec3d& translation, const Vec3d& rotation, const Vec3d& scale, const Vec3d& mirror) +{ + transform = Transform3d::Identity(); + transform.translate(translation); + transform.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + transform.rotate(Eigen::AngleAxisd(rotation(1), Vec3d::UnitY())); + transform.rotate(Eigen::AngleAxisd(rotation(0), Vec3d::UnitX())); + transform.scale(scale); + transform.scale(mirror); +} + +Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, const Vec3d& scale, const Vec3d& mirror) +{ + Transform3d transform; + assemble_transform(transform, translation, rotation, scale, mirror); + return transform; +} + +Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) +{ + // see: https://www.learnopencv.com/rotation-matrix-to-euler-angles/ + double sy = ::sqrt(sqr(rotation_matrix(0, 0)) + sqr(rotation_matrix(1, 0))); + + Vec3d angles = Vec3d::Zero(); + + if (sy >= 1e-6) + { + angles(0) = ::atan2(rotation_matrix(2, 1), rotation_matrix(2, 2)); + angles(1) = ::atan2(-rotation_matrix(2, 0), sy); + angles(2) = ::atan2(rotation_matrix(1, 0), rotation_matrix(0, 0)); + } + else + { + angles(0) = ::atan2(-rotation_matrix(1, 2), rotation_matrix(1, 1)); + angles(1) = ::atan2(-rotation_matrix(2, 0), sy); + angles(2) = 0.0; + } + + return angles; +} + +Vec3d extract_euler_angles(const Transform3d& transform) +{ + // use only the non-translational part of the transform + Eigen::Matrix m = transform.matrix().block(0, 0, 3, 3); + // remove scale + m.col(0).normalize(); + m.col(1).normalize(); + m.col(2).normalize(); + return extract_euler_angles(m); +} + +#if ENABLE_MODELVOLUME_TRANSFORM +Transformation::Flags::Flags() + : dont_translate(true) + , dont_rotate(true) + , dont_scale(true) + , dont_mirror(true) +{ +} + +bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +{ + return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); +} + +void Transformation::Flags::set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) +{ + this->dont_translate = dont_translate; + this->dont_rotate = dont_rotate; + this->dont_scale = dont_scale; + this->dont_mirror = dont_mirror; +} + +Transformation::Transformation() + : m_offset(Vec3d::Zero()) + , m_rotation(Vec3d::Zero()) + , m_scaling_factor(Vec3d::Ones()) + , m_mirror(Vec3d::Ones()) + , m_matrix(Transform3d::Identity()) + , m_dirty(false) +{ +} + +Transformation::Transformation(const Transform3d& transform) +{ + set_from_transform(transform); +} + +void Transformation::set_offset(const Vec3d& offset) +{ + set_offset(X, offset(0)); + set_offset(Y, offset(1)); + set_offset(Z, offset(2)); +} + +void Transformation::set_offset(Axis axis, double offset) +{ + if (m_offset(axis) != offset) + { + m_offset(axis) = offset; + m_dirty = true; + } +} + +void Transformation::set_rotation(const Vec3d& rotation) +{ + set_rotation(X, rotation(0)); + set_rotation(Y, rotation(1)); + set_rotation(Z, rotation(2)); +} + +void Transformation::set_rotation(Axis axis, double rotation) +{ + rotation = angle_to_0_2PI(rotation); + + if (m_rotation(axis) != rotation) + { + m_rotation(axis) = rotation; + m_dirty = true; + } +} + +void Transformation::set_scaling_factor(const Vec3d& scaling_factor) +{ + set_scaling_factor(X, scaling_factor(0)); + set_scaling_factor(Y, scaling_factor(1)); + set_scaling_factor(Z, scaling_factor(2)); +} + +void Transformation::set_scaling_factor(Axis axis, double scaling_factor) +{ + if (m_scaling_factor(axis) != std::abs(scaling_factor)) + { + m_scaling_factor(axis) = std::abs(scaling_factor); + m_dirty = true; + } +} + +void Transformation::set_mirror(const Vec3d& mirror) +{ + set_mirror(X, mirror(0)); + set_mirror(Y, mirror(1)); + set_mirror(Z, mirror(2)); +} + +void Transformation::set_mirror(Axis axis, double mirror) +{ + double abs_mirror = std::abs(mirror); + if (abs_mirror == 0.0) + mirror = 1.0; + else if (abs_mirror != 1.0) + mirror /= abs_mirror; + + if (m_mirror(axis) != mirror) + { + m_mirror(axis) = mirror; + m_dirty = true; + } +} + +void Transformation::set_from_transform(const Transform3d& transform) +{ + // offset + set_offset(transform.matrix().block(0, 3, 3, 1)); + + Eigen::Matrix m3x3 = transform.matrix().block(0, 0, 3, 3); + + // mirror + // it is impossible to reconstruct the original mirroring factors from a matrix, + // we can only detect if the matrix contains a left handed reference system + // in which case we reorient it back to right handed by mirroring the x axis + Vec3d mirror = Vec3d::Ones(); + if (m3x3.col(0).dot(m3x3.col(1).cross(m3x3.col(2))) < 0.0) + { + mirror(0) = -1.0; + // remove mirror + m3x3.col(0) *= -1.0; + } + set_mirror(mirror); + + // scale + set_scaling_factor(Vec3d(m3x3.col(0).norm(), m3x3.col(1).norm(), m3x3.col(2).norm())); + + // remove scale + m3x3.col(0).normalize(); + m3x3.col(1).normalize(); + m3x3.col(2).normalize(); + + // rotation + set_rotation(extract_euler_angles(m3x3)); + + // forces matrix recalculation matrix + m_matrix = get_matrix(); + +// // debug check +// if (!m_matrix.isApprox(transform)) +// std::cout << "something went wrong in extracting data from matrix" << std::endl; +} + +const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +{ + if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) + { + m_matrix = Geometry::assemble_transform( + dont_translate ? Vec3d::Zero() : m_offset, + dont_rotate ? Vec3d::Zero() : m_rotation, + dont_scale ? Vec3d::Ones() : m_scaling_factor, + dont_mirror ? Vec3d::Ones() : m_mirror + ); + + m_flags.set(dont_translate, dont_rotate, dont_scale, dont_mirror); + m_dirty = false; + } + + return m_matrix; +} + +Transformation Transformation::operator * (const Transformation& other) const +{ + return Transformation(get_matrix() * other.get_matrix()); +} +#endif // ENABLE_MODELVOLUME_TRANSFORM + } } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 3c38d24cf..eba6b093f 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -115,9 +115,23 @@ void chained_path(const Points &points, std::vector &retval); template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); -double rad2deg(double angle); +template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); template T deg2rad(T angle) { return T(PI) * angle / T(180.0); } +template T angle_to_0_2PI(T angle) +{ + static const T TWO_PI = T(2) * T(PI); + while (angle < T(0)) + { + angle += TWO_PI; + } + while (TWO_PI < angle) + { + angle -= TWO_PI; + } + + return angle; +} void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); double linint(double value, double oldmin, double oldmax, double newmin, double newmax); @@ -127,6 +141,93 @@ bool arrange( // output Pointfs &positions); +// Sets the given transform by assembling the given transformations in the following order: +// 1) mirror +// 2) scale +// 3) rotate X +// 4) rotate Y +// 5) rotate Z +// 6) translate +void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); + +// Returns the transform obtained by assembling the given transformations in the following order: +// 1) mirror +// 2) scale +// 3) rotate X +// 4) rotate Y +// 5) rotate Z +// 6) translate +Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); + +// Returns the euler angles extracted from the given rotation matrix +// Warning -> The matrix should not contain any scale or shear !!! +Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix); + +// Returns the euler angles extracted from the given affine transform +// Warning -> The transform should not contain any shear !!! +Vec3d extract_euler_angles(const Transform3d& transform); + +#if ENABLE_MODELVOLUME_TRANSFORM +class Transformation +{ + struct Flags + { + bool dont_translate; + bool dont_rotate; + bool dont_scale; + bool dont_mirror; + + Flags(); + + bool needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const; + void set(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror); + }; + + Vec3d m_offset; // In unscaled coordinates + Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point + Vec3d m_scaling_factor; // Scaling factors along the three axes + Vec3d m_mirror; // Mirroring along the three axes + + mutable Transform3d m_matrix; + mutable Flags m_flags; + mutable bool m_dirty; + +public: + Transformation(); + explicit Transformation(const Transform3d& transform); + + const Vec3d& get_offset() const { return m_offset; } + double get_offset(Axis axis) const { return m_offset(axis); } + + void set_offset(const Vec3d& offset); + void set_offset(Axis axis, double offset); + + const Vec3d& get_rotation() const { return m_rotation; } + double get_rotation(Axis axis) const { return m_rotation(axis); } + + void set_rotation(const Vec3d& rotation); + void set_rotation(Axis axis, double rotation); + + Vec3d get_scaling_factor() const { return m_scaling_factor; } + double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor); + void set_scaling_factor(Axis axis, double scaling_factor); + + const Vec3d& get_mirror() const { return m_mirror; } + double get_mirror(Axis axis) const { return m_mirror(axis); } + + void set_mirror(const Vec3d& mirror); + void set_mirror(Axis axis, double mirror); + + void set_from_transform(const Transform3d& transform); + + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; + + Transformation operator * (const Transformation& other) const; +}; +#endif // ENABLE_MODELVOLUME_TRANSFORM + } } #endif diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index cc53a87ea..c587e81cf 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -17,6 +17,16 @@ Layer::~Layer() m_regions.clear(); } +// Test whether whether there are any slices assigned to this layer. +bool Layer::empty() const +{ + for (const LayerRegion *layerm : m_regions) + if (layerm != nullptr && ! layerm->slices.empty()) + // Non empty layer. + return false; + return true; +} + LayerRegion* Layer::add_region(PrintRegion* print_region) { m_regions.emplace_back(new LayerRegion(this, print_region)); @@ -32,9 +42,8 @@ void Layer::make_slices() slices = m_regions.front()->slices; } else { Polygons slices_p; - FOREACH_LAYERREGION(this, layerm) { - polygons_append(slices_p, to_polygons((*layerm)->slices)); - } + for (LayerRegion *layerm : m_regions) + polygons_append(slices_p, to_polygons(layerm->slices)); slices = union_ex(slices_p); } @@ -53,7 +62,7 @@ void Layer::make_slices() // populate slices vector for (size_t i : order) - this->slices.expolygons.push_back(STDMOVE(slices[i])); + this->slices.expolygons.push_back(std::move(slices[i])); } void Layer::merge_slices() @@ -63,10 +72,9 @@ void Layer::merge_slices() // but use the non-split islands of a layer. For a single region print, these shall be equal. m_regions.front()->slices.set(this->slices.expolygons, stInternal); } else { - FOREACH_LAYERREGION(this, layerm) { + for (LayerRegion *layerm : m_regions) // without safety offset, artifacts are generated (GH #2494) - (*layerm)->slices.set(union_ex(to_polygons(STDMOVE((*layerm)->slices.surfaces)), true), stInternal); - } + layerm->slices.set(union_ex(to_polygons(std::move(layerm->slices.surfaces)), true), stInternal); } } @@ -80,7 +88,7 @@ void Layer::make_perimeters() // keep track of regions whose perimeters we have already generated std::set done; - FOREACH_LAYERREGION(this, layerm) { + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) { size_t region_id = layerm - m_regions.begin(); if (done.find(region_id) != done.end()) continue; BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; @@ -138,7 +146,7 @@ void Layer::make_perimeters() // Separate the fill surfaces. ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices); (*l)->fill_expolygons = expp; - (*l)->fill_surfaces.set(STDMOVE(expp), fill_surfaces.surfaces.front()); + (*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front()); } } } @@ -155,7 +163,7 @@ void Layer::make_fills() layerm->fills.clear(); make_fill(*layerm, layerm->fills); #ifndef NDEBUG - for (size_t i = 0; i < layerm.fills.entities.size(); ++ i) + for (size_t i = 0; i < layerm->fills.entities.size(); ++ i) assert(dynamic_cast(layerm->fills.entities[i]) != NULL); #endif } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8dbe850cc..0f75cd39a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -35,6 +35,8 @@ public: // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. ExPolygons fill_expolygons; + // Unspecified fill polygons, used for interecting when we don't want the infill/perimeter overlap + ExPolygons fill_no_overlap_expolygons; // collection of surfaces for infill generation SurfaceCollection fill_surfaces; @@ -110,11 +112,12 @@ public: ExPolygonCollection slices; size_t region_count() const { return m_regions.size(); } - const LayerRegion* get_region(int idx) const { return m_regions.at(idx); } - LayerRegion* get_region(int idx) { return m_regions[idx]; } + const LayerRegion* get_region(size_t idx) const { return m_regions.at(idx); } + LayerRegion* get_region(size_t idx) { return m_regions[idx]; } LayerRegion* add_region(PrintRegion* print_region); const LayerRegionPtrs& regions() const { return m_regions; } - + // Test whether whether there are any slices assigned to this layer. + bool empty() const; void make_slices(); void merge_slices(); template bool any_internal_region_slice_contains(const T &item) const { diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 829344b40..7eaae5348 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -34,7 +34,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. -// Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces)); +// Polygons fill_boundaries = to_polygons(std::move(this->fill_surfaces)); Polygons fill_boundaries = to_polygons(this->fill_expolygons); // Collect polygons per surface type. std::vector polygons_by_surface; @@ -93,15 +93,15 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec void LayerRegion::process_external_surfaces(const Layer* lower_layer) { const Surfaces &surfaces = this->fill_surfaces.surfaces; - const bool has_infill = this->region()->config.fill_density.value > 0.; - coord_t margin = scale_(this->region()->config.external_infill_margin.getFloat()); - coord_t margin_bridged = scale_(this->region()->config.bridged_infill_margin.getFloat()); + const bool has_infill = this->region()->config().fill_density.value > 0.; + coord_t margin = scale_(this->region()->config().external_infill_margin.getFloat()); + coord_t margin_bridged = scale_(this->region()->config().bridged_infill_margin.getFloat()); //if no infill, reduce the margin for averythign to only the perimeter if (!has_infill) { - if ((this->region()->config.perimeters.value > 0)) { - const coord_t perimeter_width = scale_(this->region()->config.perimeter_extrusion_width.get_abs_value(this->layer()->object()->config.layer_height.value)); - const coord_t first_perimeter_width = scale_(this->region()->config.external_perimeter_extrusion_width.get_abs_value(this->layer()->object()->config.layer_height.value)); - margin = first_perimeter_width + perimeter_width * (this->region()->config.perimeters.value - 1); + if ((this->region()->config().perimeters > 0)) { + const coord_t perimeter_width = scale_(this->region()->config().perimeter_extrusion_width.get_abs_value(this->layer()->object()->config().layer_height.value)); + const coord_t first_perimeter_width = scale_(this->region()->config().external_perimeter_extrusion_width.get_abs_value(this->layer()->object()->config().layer_height.value)); + margin = first_perimeter_width + perimeter_width * (this->region()->config().perimeters.value - 1); } else margin = 0; margin_bridged = margin; } @@ -146,10 +146,10 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (!surface.is_external()) // Make a copy as the following line uses the move semantics. internal.push_back(surface); - polygons_append(fill_boundaries, STDMOVE(surface.expolygon)); + polygons_append(fill_boundaries, std::move(surface.expolygon)); } else{ if (!surface.is_external()) - internal.push_back(STDMOVE(surface)); + internal.push_back(std::move(surface)); //push surface as perimeter-only inside the fill_boundaries if (margin_bridged > 0) { ExPolygons peri_poly = diff_ex(ExPolygons() = { surface.expolygon }, offset_ex(surface.expolygon, -margin_bridged)); @@ -212,7 +212,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); } bridge_bboxes.push_back(get_extents(polys)); - bridges_grown.push_back(STDMOVE(polys)); + bridges_grown.push_back(std::move(polys)); } } @@ -263,7 +263,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] != group_id) continue; - initial.push_back(STDMOVE(bridges[i].expolygon)); + initial.push_back(std::move(bridges[i].expolygon)); polygons_append(grown, bridges_grown[i]); } // detect bridge direction before merging grown surfaces otherwise adjacent bridges @@ -289,7 +289,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } - fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex)); + fill_boundaries = std::move(to_polygons(fill_boundaries_ex)); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; } @@ -304,7 +304,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) Surfaces new_surfaces; { // Merge top and bottom in a single collection. - surfaces_append(top, STDMOVE(bottom)); + surfaces_append(top, std::move(bottom)); // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); for (size_t i = 0; i < top.size(); ++ i) { @@ -312,11 +312,11 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (s1.empty()) continue; Polygons polys; - polygons_append(polys, STDMOVE(s1)); + polygons_append(polys, std::move(s1)); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, STDMOVE(s2)); + polygons_append(polys, std::move(s2)); s2.clear(); } } @@ -326,7 +326,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. - STDMOVE(intersection_ex(polys, fill_boundaries, false)), + std::move(intersection_ex(polys, fill_boundaries, false)), s1); } } @@ -338,20 +338,20 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (s1.empty()) continue; Polygons polys; - polygons_append(polys, STDMOVE(s1)); + polygons_append(polys, std::move(s1)); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { - polygons_append(polys, STDMOVE(s2)); + polygons_append(polys, std::move(s2)); s2.clear(); } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); - surfaces_append(new_surfaces, STDMOVE(new_expolys), s1); + surfaces_append(new_surfaces, std::move(new_expolys), s1); } - this->fill_surfaces.surfaces = STDMOVE(new_surfaces); + this->fill_surfaces.surfaces = std::move(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 35cfa2b76..c66a8885d 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -34,23 +34,22 @@ bool Line::intersection_infinite(const Line &other, Point* point) const return true; } -/* distance to the closest point of line */ -double Line::distance_to(const Point &point) const +// Distance to the closest point of line. +double Line::distance_to_squared(const Point &point, const Point &a, const Point &b) { - const Line &line = *this; - const Vec2d v = (line.b - line.a).cast(); - const Vec2d va = (point - line.a).cast(); + const Vec2d v = (b - a).cast(); + const Vec2d va = (point - a).cast(); const double l2 = v.squaredNorm(); // avoid a sqrt if (l2 == 0.0) - // line.a == line.b case - return va.norm(); - // Consider the line extending the segment, parameterized as line.a + t (line.b - line.a). + // a == b case + return va.squaredNorm(); + // Consider the line extending the segment, parameterized as a + t (b - a). // We find projection of this point onto the line. - // It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2 + // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) return va.norm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - line.b).cast().norm(); // beyond the 'b' end of the segment - return (t * v - va).norm(); + if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment + else if (t > 1.0) return (point - b).cast().squaredNorm(); // beyond the 'b' end of the segment + return (t * v - va).squaredNorm(); } double Line::perp_distance_to(const Point &point) const @@ -116,4 +115,15 @@ Vec3d Linef3::intersect_plane(double z) const return Vec3d(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z); } +Point Line::point_at(double distance) const { + Point point; + double len = this->length(); + point = this->a; + if (this->a.x() != this->b.x()) + point.x() = this->a.x() + (this->b.x() - this->a.x()) * distance / len; + if (this->a.y() != this->b.y()) + point.y() = this->a.y() + (this->b.y() - this->a.y()) * distance / len; + return point; +} + } diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 36e02247c..a1f42d111 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -31,7 +31,8 @@ public: Point midpoint() const { return (this->a + this->b) / 2; } bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } - double distance_to(const Point &point) const; + double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } + double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); } @@ -43,6 +44,13 @@ public: bool intersection(const Line& line, Point* intersection) const; double ccw(const Point& point) const { return point.ccw(*this); } + static double distance_to_squared(const Point &point, const Point &a, const Point &b); + static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); } + Point point_at(double distance) const; + coord_t Line::dot(Line &l2) const { return vector().dot(l2.vector()); } + void extend_end(double distance) { Line line = *this; line.reverse(); this->b = line.point_at(-distance); } + void extend_start(double distance) { this->a = this->point_at(-distance); } + Point a; Point b; }; diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp new file mode 100644 index 000000000..1e0fb426a --- /dev/null +++ b/src/libslic3r/MTUtils.hpp @@ -0,0 +1,61 @@ +#ifndef MTUTILS_HPP +#define MTUTILS_HPP + +#include // for std::atomic_flag and memory orders +#include // for std::lock_guard +#include // for std::function +#include // for std::forward + +namespace Slic3r { + +/// Handy little spin mutex for the cached meshes. +/// Implements the "Lockable" concept +class SpinMutex { + std::atomic_flag m_flg; + static const /*constexpr*/ auto MO_ACQ = std::memory_order_acquire; + static const /*constexpr*/ auto MO_REL = std::memory_order_release; +public: + inline SpinMutex() { m_flg.clear(MO_REL); } + inline void lock() { while(m_flg.test_and_set(MO_ACQ)); } + inline bool try_lock() { return !m_flg.test_and_set(MO_ACQ); } + inline void unlock() { m_flg.clear(MO_REL); } +}; + +/// A wrapper class around arbitrary object that needs thread safe caching. +template class CachedObject { +public: + // Method type which refreshes the object when it has been invalidated + using Setter = std::function; +private: + T m_obj; // the object itself + bool m_valid; // invalidation flag + SpinMutex m_lck; // to make the caching thread safe + + // the setter will be called just before the object's const value is about + // to be retrieved. + std::function m_setter; +public: + + // Forwarded constructor + template inline CachedObject(Setter fn, Args&&...args): + m_obj(std::forward(args)...), m_valid(false), m_setter(fn) {} + + // invalidate the value of the object. The object will be refreshed at the + // next retrieval (Setter will be called). The data that is used in + // the setter function should be guarded as well during modification so the + // modification has to take place in fn. + inline void invalidate(std::function fn) { + std::lock_guard lck(m_lck); fn(); m_valid = false; + } + + // Get the const object properly updated. + inline const T& get() { + std::lock_guard lck(m_lck); + if(!m_valid) { m_setter(m_obj); m_valid = true; } + return m_obj; + } +}; + +} + +#endif // MTUTILS_HPP diff --git a/xs/src/libslic3r/MedialAxis.cpp b/src/libslic3r/MedialAxis.cpp similarity index 95% rename from xs/src/libslic3r/MedialAxis.cpp rename to src/libslic3r/MedialAxis.cpp index 18242b8ab..ebffc871e 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/src/libslic3r/MedialAxis.cpp @@ -344,10 +344,10 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); new_width += to_modify->width[idx_other] * (percent_dist); Point new_point; - new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); - new_point.x += (coord_t)((double)(to_modify->points[idx_other].x) * (percent_dist)); - new_point.y = (coord_t)((double)(to_modify->points[idx_other - 1].y) * (1 - percent_dist)); - new_point.y += (coord_t)((double)(to_modify->points[idx_other].y) * (percent_dist)); + new_point.x() = (coord_t)((double)(to_modify->points[idx_other - 1].x()) * (1 - percent_dist)); + new_point.x() += (coord_t)((double)(to_modify->points[idx_other].x()) * (percent_dist)); + new_point.y() = (coord_t)((double)(to_modify->points[idx_other - 1].y()) * (1 - percent_dist)); + new_point.y() += (coord_t)((double)(to_modify->points[idx_other].y()) * (percent_dist)); to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); } @@ -425,9 +425,11 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi double dot(Line l1, Line l2) { - Vectorf v_1 = normalize(Vectorf(l1.b.x - l1.a.x, l1.b.y - l1.a.y)); - Vectorf v_2 = normalize(Vectorf(l2.b.x - l2.a.x, l2.b.y - l2.a.y)); - return v_1.x*v_2.x + v_1.y*v_2.y; + Vec2d v_1(l1.b.x() - l1.a.x(), l1.b.y() - l1.a.y()); + v_1.normalize(); + Vec2d v_2(l2.b.x() - l2.a.x(), l2.b.y() - l2.a.y()); + v_2.normalize(); + return v_1.x()*v_2.x() + v_1.y()*v_2.y(); } void @@ -503,19 +505,19 @@ MedialAxis::fusion_curve(ThickPolylines &pp) //length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); ////compute dir - //Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); + //Vectorf pull_direction(polyline.points[1].x() - polyline.points[0].x(), polyline.points[1].y() - polyline.points[0].y()); //pull_direction = normalize(pull_direction); - //pull_direction.x *= length_pull; - //pull_direction.y *= length_pull; + //pull_direction.x() *= length_pull; + //pull_direction.y() *= length_pull; ////pull the points //Point &p1 = pp[crosspoint[0]].points[0]; - //p1.x = p1.x + (coord_t)pull_direction.x; - //p1.y = p1.y + (coord_t)pull_direction.y; + //p1.x() = p1.x() + (coord_t)pull_direction.x(); + //p1.y() = p1.y() + (coord_t)pull_direction.y(); //Point &p2 = pp[crosspoint[1]].points[0]; - //p2.x = p2.x + (coord_t)pull_direction.x; - //p2.y = p2.y + (coord_t)pull_direction.y; + //p2.x() = p2.x() + (coord_t)pull_direction.x(); + //p2.y() = p2.y() + (coord_t)pull_direction.y(); //delete the now unused polyline pp.erase(pp.begin() + i); @@ -579,19 +581,19 @@ MedialAxis::fusion_corners(ThickPolylines &pp) length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); //compute dir - Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); - pull_direction = normalize(pull_direction); - pull_direction.x *= length_pull; - pull_direction.y *= length_pull; + Vec2d pull_direction(polyline.points[1].x() - polyline.points[0].x(), polyline.points[1].y() - polyline.points[0].y()); + pull_direction.normalize(); + pull_direction.x() *= length_pull; + pull_direction.y() *= length_pull; //pull the points Point &p1 = pp[crosspoint[0]].points[0]; - p1.x = p1.x + pull_direction.x; - p1.y = p1.y + pull_direction.y; + p1.x() = p1.x() + pull_direction.x(); + p1.y() = p1.y() + pull_direction.y(); Point &p2 = pp[crosspoint[1]].points[0]; - p2.x = p2.x + pull_direction.x; - p2.y = p2.y + pull_direction.y; + p2.x() = p2.x() + pull_direction.x(); + p2.y() = p2.y() + pull_direction.y(); //delete the now unused polyline pp.erase(pp.begin() + i); @@ -711,10 +713,10 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con best_anchor = p_maybe_inside; } } - if (best_anchor.x != 0 && best_anchor.y != 0) { + if (best_anchor.x() != 0 && best_anchor.y() != 0) { Point p_obj = best_anchor + new_bound; - p_obj.x /= 2; - p_obj.y /= 2; + p_obj.x() /= 2; + p_obj.y() /= 2; Line l2 = Line(new_back, p_obj); l2.extend_end(max_width); (void)bounds.contour.first_intersection(l2, &new_bound); @@ -937,8 +939,8 @@ MedialAxis::main_fusion(ThickPolylines& pp) size_t idx_point = 1; while (idx_point < min(polyline.points.size(), best_candidate->points.size())) { //fusion - polyline.points[idx_point].x = polyline.points[idx_point].x * coeff_poly + best_candidate->points[idx_point].x * coeff_candi; - polyline.points[idx_point].y = polyline.points[idx_point].y * coeff_poly + best_candidate->points[idx_point].y * coeff_candi; + polyline.points[idx_point].x() = polyline.points[idx_point].x() * coeff_poly + best_candidate->points[idx_point].x() * coeff_candi; + polyline.points[idx_point].y() = polyline.points[idx_point].y() * coeff_poly + best_candidate->points[idx_point].y() * coeff_candi; // The width decrease with distance from the centerline. // This formula is what works the best, even if it's not perfect (created empirically). 0->3% error on a gap fill on some tests. @@ -1048,10 +1050,10 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.front().x = polyline.points.front().x + - (coord_t)((polyline.points[1].x - polyline.points.front().x) * (1 - percent_can_keep)); - polyline.points.front().y = polyline.points.front().y + - (coord_t)((polyline.points[1].y - polyline.points.front().y) * (1 - percent_can_keep)); + polyline.points.front().x() = polyline.points.front().x() + + (coord_t)((polyline.points[1].x() - polyline.points.front().x()) * (1 - percent_can_keep)); + polyline.points.front().y() = polyline.points.front().y() + + (coord_t)((polyline.points[1].y() - polyline.points.front().y()) * (1 - percent_can_keep)); polyline.width.front() = min_width; } else { /// almost 0-length, Remove @@ -1072,10 +1074,10 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.back().x = polyline.points.back().x + - (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * (1 - percent_can_keep)); - polyline.points.back().y = polyline.points.back().y + - (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * (1 - percent_can_keep)); + polyline.points.back().x() = polyline.points.back().x() + + (coord_t)((polyline.points[polyline.points.size() - 2].x() - polyline.points.back().x()) * (1 - percent_can_keep)); + polyline.points.back().y() = polyline.points.back().y() + + (coord_t)((polyline.points[polyline.points.size() - 2].y() - polyline.points.back().y()) * (1 - percent_can_keep)); polyline.width.back() = min_width; } else { /// almost 0-length, Remove @@ -1139,11 +1141,11 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) continue; } - Pointf v_poly(polyline.lines().back().vector().x, polyline.lines().back().vector().y); - v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y)); - Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y); - v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y)); - float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y; + Vec2d v_poly(polyline.lines().back().vector().x(), polyline.lines().back().vector().y()); + v_poly *= (1 / std::sqrt(v_poly.x()*v_poly.x() + v_poly.y()*v_poly.y())); + Vec2d v_other(other.lines().front().vector().x(), other.lines().front().vector().y()); + v_other *= (1 / std::sqrt(v_other.x()*v_other.x() + v_other.y()*v_other.y())); + float other_dot = v_poly.x()*v_other.x() + v_poly.y()*v_other.y(); if (other_dot > best_dot) { best_candidate = &other; best_idx = j; @@ -1306,8 +1308,8 @@ MedialAxis::simplify_polygon_frontier() const Point* closest = bounds.contour.closest_point(p_check); if (closest != nullptr && closest->distance_to(p_check) + SCALED_EPSILON < min(p_check.distance_to(simplified_poly.contour.points[prev_i]), p_check.distance_to(simplified_poly.contour.points[next_i])) / 2) { - p_check.x = closest->x; - p_check.y = closest->y; + p_check.x() = closest->x(); + p_check.y() = closest->y(); need_intersect = true; } else { simplified_poly.contour.points.erase(simplified_poly.contour.points.begin() + i); @@ -1553,7 +1555,7 @@ ExtrusionEntityCollection thin_variable_width(const ThickPolylines &polylines, E path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. - flow.width = unscale(w) + flow.height * (1. - 0.25 * PI); + flow.width = unscaled(w) + flow.height * (1. - 0.25 * PI); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif diff --git a/xs/src/libslic3r/MedialAxis.hpp b/src/libslic3r/MedialAxis.hpp similarity index 100% rename from xs/src/libslic3r/MedialAxis.hpp rename to src/libslic3r/MedialAxis.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 620ace560..07f1b98c1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -19,35 +19,68 @@ namespace Slic3r { - unsigned int Model::s_auto_extruder_id = 1; +unsigned int Model::s_auto_extruder_id = 1; -Model::Model(const Model &other) +size_t ModelBase::s_last_id = 0; + +Model& Model::assign_copy(const Model &rhs) { + this->copy_id(rhs); // copy materials - for (const auto &m : other.materials) - this->add_material(m.first, *m.second); + this->clear_materials(); + this->materials = rhs.materials; + for (std::pair &m : this->materials) { + // Copy including the ID and m_model. + m.second = new ModelMaterial(*m.second); + m.second->set_model(this); + } // copy objects - this->objects.reserve(other.objects.size()); - for (const ModelObject *o : other.objects) - this->add_object(*o, true); -} - -Model& Model::operator=(Model other) -{ - this->swap(other); + this->clear_objects(); + this->objects.reserve(rhs.objects.size()); + for (const ModelObject *model_object : rhs.objects) { + // Copy including the ID, leave ID set to invalid (zero). + auto mo = ModelObject::new_copy(*model_object); + mo->set_model(this); + this->objects.emplace_back(mo); + } return *this; } -void Model::swap(Model &other) +Model& Model::assign_copy(Model &&rhs) { - std::swap(this->materials, other.materials); - std::swap(this->objects, other.objects); + this->copy_id(rhs); + // Move materials, adjust the parent pointer. + this->clear_materials(); + this->materials = std::move(rhs.materials); + for (std::pair &m : this->materials) + m.second->set_model(this); + rhs.materials.clear(); + // Move objects, adjust the parent pointer. + this->clear_objects(); + this->objects = std::move(rhs.objects); + for (ModelObject *model_object : this->objects) + model_object->set_model(this); + rhs.objects.clear(); + return *this; } -Model Model::read_from_file(const std::string &input_file, bool add_default_instances) +void Model::assign_new_unique_ids_recursive() +{ + this->set_new_unique_id(); + for (std::pair &m : this->materials) + m.second->assign_new_unique_ids_recursive(); + for (ModelObject *model_object : this->objects) + model_object->assign_new_unique_ids_recursive(); +} + +Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) { Model model; - + + DynamicPrintConfig temp_config; + if (config == nullptr) + config = &temp_config; + bool result = false; if (boost::algorithm::iends_with(input_file, ".stl")) result = load_stl(input_file.c_str(), &model); @@ -55,11 +88,11 @@ Model Model::read_from_file(const std::string &input_file, bool add_default_inst result = load_obj(input_file.c_str(), &model); else if (!boost::algorithm::iends_with(input_file, ".zip.amf") && (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))) - result = load_amf(input_file.c_str(), nullptr, &model); -#ifdef SLIC3R_PRUS + result = load_amf(input_file.c_str(), config, &model); + else if (boost::algorithm::iends_with(input_file, ".3mf")) + result = load_3mf(input_file.c_str(), config, &model); else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); -#endif /* SLIC3R_PRUS */ else throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); @@ -78,15 +111,15 @@ Model Model::read_from_file(const std::string &input_file, bool add_default_inst return model; } -Model Model::read_from_archive(const std::string &input_file, PresetBundle* bundle, bool add_default_instances) +Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) { Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), bundle, &model); + result = load_3mf(input_file.c_str(), config, &model); else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), bundle, &model); + result = load_amf(input_file.c_str(), config, &model); else throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); @@ -113,6 +146,12 @@ Model Model::read_from_archive(const std::string &input_file, PresetBundle* bund return model; } +void Model::repair() +{ + for (ModelObject *o : this->objects) + o->repair(); +} + ModelObject* Model::add_object() { this->objects.emplace_back(new ModelObject(this)); @@ -143,9 +182,10 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh return new_object; } -ModelObject* Model::add_object(const ModelObject &other, bool copy_volumes) +ModelObject* Model::add_object(const ModelObject &other) { - ModelObject* new_object = new ModelObject(this, other, copy_volumes); + ModelObject* new_object = ModelObject::new_clone(other); + new_object->set_model(this); this->objects.push_back(new_object); return new_object; } @@ -157,21 +197,36 @@ void Model::delete_object(size_t idx) this->objects.erase(i); } -void Model::delete_object(ModelObject* object) +bool Model::delete_object(ModelObject* object) { - if (object == nullptr) - return; - - for (ModelObjectPtrs::iterator it = objects.begin(); it != objects.end(); ++it) - { - ModelObject* obj = *it; - if (obj == object) - { - delete obj; - objects.erase(it); - return; + if (object != nullptr) { + size_t idx = 0; + for (ModelObject *model_object : objects) { + if (model_object == object) { + delete model_object; + objects.erase(objects.begin() + idx); + return true; + } + ++ idx; } } + return false; +} + +bool Model::delete_object(ModelID id) +{ + if (id.id != 0) { + size_t idx = 0; + for (ModelObject *model_object : objects) { + if (model_object->id() == id) { + delete model_object; + objects.erase(objects.begin() + idx); + return true; + } + ++ idx; + } + } + return false; } void Model::clear_objects() @@ -199,6 +254,7 @@ void Model::clear_materials() ModelMaterial* Model::add_material(t_model_material_id material_id) { + assert(! material_id.empty()); ModelMaterial* material = this->get_material(material_id); if (material == nullptr) material = this->materials[material_id] = new ModelMaterial(this); @@ -207,11 +263,13 @@ ModelMaterial* Model::add_material(t_model_material_id material_id) ModelMaterial* Model::add_material(t_model_material_id material_id, const ModelMaterial &other) { + assert(! material_id.empty()); // delete existing material if any ModelMaterial* material = this->get_material(material_id); delete material; // set new material - material = new ModelMaterial(this, other); + material = new ModelMaterial(other); + material->set_model(this); this->materials[material_id] = material; return material; } @@ -235,28 +293,33 @@ BoundingBoxf3 Model::bounding_box() const return bb; } -void Model::center_instances_around_point(const Vec2d &point) +unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume) +{ + unsigned int num_printable = 0; + for (ModelObject *model_object : this->objects) + num_printable += model_object->check_instances_print_volume_state(print_volume); + return num_printable; +} + +bool Model::center_instances_around_point(const Vec2d &point) { BoundingBoxf3 bb; for (ModelObject *o : this->objects) for (size_t i = 0; i < o->instances.size(); ++ i) bb.merge(o->instance_bounding_box(i, false)); -#if ENABLE_MODELINSTANCE_3D_OFFSET Vec2d shift2 = point - to_2d(bb.center()); - Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0); -#else - Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - for (ModelObject *o : this->objects) { - for (ModelInstance *i : o->instances) -#if ENABLE_MODELINSTANCE_3D_OFFSET - i->set_offset(i->get_offset() + shift3); -#else - i->offset += shift; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - o->invalidate_bounding_box(); - } + if (std::abs(shift2(0)) < EPSILON && std::abs(shift2(1)) < EPSILON) + // No significant shift, don't do anything. + return false; + + Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0); + for (ModelObject *o : this->objects) { + for (ModelInstance *i : o->instances) + i->set_offset(i->get_offset() + shift3); + o->invalidate_bounding_box(); + } + return true; } // flattens everything to a single mesh @@ -320,12 +383,8 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) size_t idx = 0; for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) { -#if ENABLE_MODELINSTANCE_3D_OFFSET Vec2d offset_xy = positions[idx] - instance_centers[idx]; i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); -#else - i->offset = positions[idx] - instance_centers[idx]; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET ++idx; } o->invalidate_bounding_box(); @@ -350,11 +409,7 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) for (const ModelInstance *i : instances) { for (const Vec2d &pos : positions) { ModelInstance *instance = o->add_instance(*i); -#if ENABLE_MODELINSTANCE_3D_OFFSET instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0)); -#else - instance->offset += pos; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } o->invalidate_bounding_box(); @@ -384,21 +439,12 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) ModelObject* object = this->objects.front(); object->clear_instances(); -#if ENABLE_MODELINSTANCE_3D_OFFSET Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones(); -#else - Vec3d size = object->bounding_box().size(); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET for (size_t x_copy = 1; x_copy <= x; ++x_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) { ModelInstance* instance = object->add_instance(); -#if ENABLE_MODELINSTANCE_3D_OFFSET instance->set_offset(Vec3d(ext_size(0) * (double)(x_copy - 1), ext_size(1) * (double)(y_copy - 1), 0.0)); -#else - instance->offset(0) = (size(0) + dist) * (x_copy - 1); - instance->offset(1) = (size(1) + dist) * (y_copy - 1); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } } @@ -430,6 +476,8 @@ void Model::convert_multipart_object(unsigned int max_extruders) ModelObject* object = new ModelObject(this); object->input_file = this->objects.front()->input_file; + object->name = this->objects.front()->name; + //FIXME copy the config etc? reset_auto_extruder_id(); @@ -464,7 +512,7 @@ void Model::adjust_min_z() { coordf_t obj_min_z = obj->bounding_box().min(2); if (obj_min_z < 0.0) - obj->translate(0.0, 0.0, -obj_min_z); + obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z)); } } } @@ -478,7 +526,7 @@ unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) // to a profile with a lower number of extruders. reset_auto_extruder_id(); id = s_auto_extruder_id; - } else if (++s_auto_extruder_id > max_extruders) { + } else if (++ s_auto_extruder_id > max_extruders) { reset_auto_extruder_id(); } return id; @@ -496,49 +544,13 @@ void Model::reset_auto_extruder_id() s_auto_extruder_id = 1; } -ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : - name(other.name), - input_file(other.input_file), - instances(), - volumes(), - config(other.config), - layer_height_ranges(other.layer_height_ranges), - layer_height_profile(other.layer_height_profile), - layer_height_profile_valid(other.layer_height_profile_valid), - origin_translation(other.origin_translation), - m_bounding_box(other.m_bounding_box), - m_bounding_box_valid(other.m_bounding_box_valid), - m_model(model) +std::string Model::propose_export_file_name() const { - if (copy_volumes) { - this->volumes.reserve(other.volumes.size()); - for (ModelVolumePtrs::const_iterator i = other.volumes.begin(); i != other.volumes.end(); ++i) - this->add_volume(**i); - } - - this->instances.reserve(other.instances.size()); - for (ModelInstancePtrs::const_iterator i = other.instances.begin(); i != other.instances.end(); ++i) - this->add_instance(**i); -} - -ModelObject& ModelObject::operator=(ModelObject other) -{ - this->swap(other); - return *this; -} - -void ModelObject::swap(ModelObject &other) -{ - std::swap(this->input_file, other.input_file); - std::swap(this->instances, other.instances); - std::swap(this->volumes, other.volumes); - std::swap(this->config, other.config); - std::swap(this->layer_height_ranges, other.layer_height_ranges); - std::swap(this->layer_height_profile, other.layer_height_profile); - std::swap(this->layer_height_profile_valid, other.layer_height_profile_valid); - std::swap(this->origin_translation, other.origin_translation); - std::swap(m_bounding_box, other.m_bounding_box); - std::swap(m_bounding_box_valid, other.m_bounding_box_valid); + for (const ModelObject *model_object : this->objects) + for (ModelInstance *model_instance : model_object->instances) + if (model_instance->is_printable()) + return model_object->input_file; + return std::string(); } ModelObject::~ModelObject() @@ -547,6 +559,84 @@ ModelObject::~ModelObject() this->clear_instances(); } +// maintains the m_model pointer +ModelObject& ModelObject::assign_copy(const ModelObject &rhs) +{ + this->copy_id(rhs); + + this->name = rhs.name; + this->input_file = rhs.input_file; + this->config = rhs.config; + this->sla_support_points = rhs.sla_support_points; + this->layer_height_ranges = rhs.layer_height_ranges; + this->layer_height_profile = rhs.layer_height_profile; + this->layer_height_profile_valid = rhs.layer_height_profile_valid; + this->origin_translation = rhs.origin_translation; + m_bounding_box = rhs.m_bounding_box; + m_bounding_box_valid = rhs.m_bounding_box_valid; + + this->clear_volumes(); + this->volumes.reserve(rhs.volumes.size()); + for (ModelVolume *model_volume : rhs.volumes) { + this->volumes.emplace_back(new ModelVolume(*model_volume)); + this->volumes.back()->set_model_object(this); + } + this->clear_instances(); + this->instances.reserve(rhs.instances.size()); + for (const ModelInstance *model_instance : rhs.instances) { + this->instances.emplace_back(new ModelInstance(*model_instance)); + this->instances.back()->set_model_object(this); + } + + return *this; +} + +// maintains the m_model pointer +ModelObject& ModelObject::assign_copy(ModelObject &&rhs) +{ + this->copy_id(rhs); + + this->name = std::move(rhs.name); + this->input_file = std::move(rhs.input_file); + this->config = std::move(rhs.config); + this->sla_support_points = std::move(rhs.sla_support_points); + this->layer_height_ranges = std::move(rhs.layer_height_ranges); + this->layer_height_profile = std::move(rhs.layer_height_profile); + this->layer_height_profile_valid = std::move(rhs.layer_height_profile_valid); + this->origin_translation = std::move(rhs.origin_translation); + m_bounding_box = std::move(rhs.m_bounding_box); + m_bounding_box_valid = std::move(rhs.m_bounding_box_valid); + + this->clear_volumes(); + this->volumes = std::move(rhs.volumes); + rhs.volumes.clear(); + for (ModelVolume *model_volume : this->volumes) + model_volume->set_model_object(this); + this->clear_instances(); + this->instances = std::move(rhs.instances); + rhs.instances.clear(); + for (ModelInstance *model_instance : this->instances) + model_instance->set_model_object(this); + + return *this; +} + +void ModelObject::assign_new_unique_ids_recursive() +{ + this->set_new_unique_id(); + for (ModelVolume *model_volume : this->volumes) + model_volume->assign_new_unique_ids_recursive(); + for (ModelInstance *model_instance : this->instances) + model_instance->assign_new_unique_ids_recursive(); +} + +// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original. +// Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing. +//ModelObject* ModelObject::clone(Model *parent) +//{ +// return new ModelObject(parent, *this, true); +//} + ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); @@ -571,11 +661,37 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other) return v; } +ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&mesh) +{ + ModelVolume* v = new ModelVolume(this, other, std::move(mesh)); + this->volumes.push_back(v); + this->invalidate_bounding_box(); + return v; +} + void ModelObject::delete_volume(size_t idx) { ModelVolumePtrs::iterator i = this->volumes.begin() + idx; delete *i; this->volumes.erase(i); + + if (this->volumes.size() == 1) + { + // only one volume left + // center it and update the instances accordingly + // rationale: the volume may be shifted with respect to the object center and this may lead to wrong rotation and scaling + // when modifying the instance matrix of the derived GLVolume + ModelVolume* v = this->volumes.front(); + v->center_geometry(); + const Vec3d& vol_offset = v->get_offset(); + for (ModelInstance* inst : this->instances) + { + inst->set_offset(inst->get_offset() + inst->get_matrix(true) * vol_offset); + } + v->set_offset(Vec3d::Zero()); + v->set_new_unique_id(); + } + this->invalidate_bounding_box(); } @@ -603,6 +719,15 @@ ModelInstance* ModelObject::add_instance(const ModelInstance &other) return i; } +ModelInstance* ModelObject::add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation) +{ + auto *instance = add_instance(); + instance->set_offset(offset); + instance->set_scaling_factor(scaling_factor); + instance->set_rotation(rotation); + return instance; +} + void ModelObject::delete_instance(size_t idx) { ModelInstancePtrs::iterator i = this->instances.begin() + idx; @@ -632,8 +757,16 @@ const BoundingBoxf3& ModelObject::bounding_box() const BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) +#if ENABLE_MODELVOLUME_TRANSFORM + { + TriangleMesh m = v->mesh; + m.transform(v->get_matrix()); + raw_bbox.merge(m.bounding_box()); + } +#else // mesh.bounding_box() returns a cached value. raw_bbox.merge(v->mesh.bounding_box()); +#endif // ENABLE_MODELVOLUME_TRANSFORM BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) bb.merge(i->transform_bounding_box(raw_bbox)); @@ -664,7 +797,15 @@ TriangleMesh ModelObject::raw_mesh() const TriangleMesh mesh; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) - mesh.merge(v->mesh); +#if ENABLE_MODELVOLUME_TRANSFORM + { + TriangleMesh vol_mesh(v->mesh); + vol_mesh.transform(v->get_matrix()); + mesh.merge(vol_mesh); + } +#else + mesh.merge(v->mesh); +#endif // ENABLE_MODELVOLUME_TRANSFORM return mesh; } @@ -677,7 +818,14 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const if (v->is_model_part()) { if (this->instances.empty()) throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); - bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); + +#if ENABLE_MODELVOLUME_TRANSFORM + TriangleMesh vol_mesh(v->mesh); + vol_mesh.transform(v->get_matrix()); + bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); +#else + bb.merge(this->instances.front()->transform_mesh_bounding_box(v->mesh, true)); +#endif // ENABLE_MODELVOLUME_TRANSFORM } return bb; } @@ -686,9 +834,21 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; +#if ENABLE_MODELVOLUME_TRANSFORM + for (ModelVolume *v : this->volumes) + { + if (v->is_model_part()) + { + TriangleMesh mesh(v->mesh); + mesh.transform(v->get_matrix()); + bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate)); + } + } +#else for (ModelVolume *v : this->volumes) if (v->is_model_part()) bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); +#endif // ENABLE_MODELVOLUME_TRANSFORM return bb; } @@ -701,39 +861,47 @@ void ModelObject::center_around_origin() if (v->is_model_part()) bb.merge(v->mesh.bounding_box()); - // Shift is the vector from the center of the bottom face of the bounding box to the origin + // Shift is the vector from the center of the bounding box to the origin Vec3d shift = -bb.center(); - shift(2) = -bb.min(2); this->translate(shift); this->origin_translation += shift; -#if ENABLE_MODELINSTANCE_3D_OFFSET - // set z to zero, translation in z has already been done within the mesh - shift(2) = 0.0; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - +#if !ENABLE_MODELVOLUME_TRANSFORM if (!this->instances.empty()) { for (ModelInstance *i : this->instances) { - // apply rotation and scaling to vector as well before translating instance, - // in order to leave final position unaltered -#if ENABLE_MODELINSTANCE_3D_OFFSET - i->set_offset(i->get_offset() + i->transform_vector(-shift, true)); -#else - Vec3d i_shift = i->world_matrix(true) * shift; - i->offset -= to_2d(i_shift); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + i->set_offset(i->get_offset() - shift); } this->invalidate_bounding_box(); } +#endif // !ENABLE_MODELVOLUME_TRANSFORM } -void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z) +void ModelObject::ensure_on_bed() +{ + translate_instances(Vec3d(0.0, 0.0, -get_min_z())); +} + +void ModelObject::translate_instances(const Vec3d& vector) +{ + for (size_t i = 0; i < instances.size(); ++i) + { + translate_instance(i, vector); + } +} + +void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector) +{ + ModelInstance* i = instances[instance_idx]; + i->set_offset(i->get_offset() + vector); + invalidate_bounding_box(); +} + +void ModelObject::translate(double x, double y, double z) { for (ModelVolume *v : this->volumes) { - v->mesh.translate(float(x), float(y), float(z)); - v->m_convex_hull.translate(float(x), float(y), float(z)); + v->translate(x, y, z); } if (m_bounding_box_valid) @@ -744,51 +912,55 @@ void ModelObject::scale(const Vec3d &versor) { for (ModelVolume *v : this->volumes) { - v->mesh.scale(versor); - v->m_convex_hull.scale(versor); + v->scale(versor); } +#if !ENABLE_MODELVOLUME_TRANSFORM // reset origin translation since it doesn't make sense anymore this->origin_translation = Vec3d::Zero(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM this->invalidate_bounding_box(); } -void ModelObject::rotate(float angle, const Axis& axis) +void ModelObject::rotate(double angle, Axis axis) { for (ModelVolume *v : this->volumes) { - v->mesh.rotate(angle, axis); - v->m_convex_hull.rotate(angle, axis); + v->rotate(angle, axis); } center_around_origin(); +#if !ENABLE_MODELVOLUME_TRANSFORM this->origin_translation = Vec3d::Zero(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM this->invalidate_bounding_box(); } -void ModelObject::rotate(float angle, const Vec3d& axis) +void ModelObject::rotate(double angle, const Vec3d& axis) { for (ModelVolume *v : this->volumes) { - v->mesh.rotate(angle, axis); - v->m_convex_hull.rotate(angle, axis); + v->rotate(angle, axis); } center_around_origin(); +#if !ENABLE_MODELVOLUME_TRANSFORM this->origin_translation = Vec3d::Zero(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM this->invalidate_bounding_box(); } -void ModelObject::mirror(const Axis &axis) +void ModelObject::mirror(Axis axis) { for (ModelVolume *v : this->volumes) { - v->mesh.mirror(axis); - v->m_convex_hull.mirror(axis); + v->mirror(axis); } +#if !ENABLE_MODELVOLUME_TRANSFORM this->origin_translation = Vec3d::Zero(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM this->invalidate_bounding_box(); } @@ -817,45 +989,144 @@ bool ModelObject::needed_repair() const return false; } -void ModelObject::cut(coordf_t z, Model* model) const +ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) { - // clone this one to duplicate instances, materials etc. - ModelObject* upper = model->add_object(*this); - ModelObject* lower = model->add_object(*this); - upper->clear_volumes(); - lower->clear_volumes(); - upper->input_file = ""; - lower->input_file = ""; - - for (ModelVolume *volume : this->volumes) { + if (!keep_upper && !keep_lower) { return {}; } + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr; + ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr; + + if (keep_upper) { + upper->set_model(nullptr); + upper->sla_support_points.clear(); + upper->clear_volumes(); + upper->input_file = ""; + } + + if (keep_lower) { + lower->set_model(nullptr); + lower->sla_support_points.clear(); + lower->clear_volumes(); + lower->input_file = ""; + } + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // except for translation and Z-rotation on instances, which are preserved + // in the transformation matrix and not applied to the mesh transform. + + // const auto instance_matrix = instances[instance]->get_matrix(true); + const auto instance_matrix = Geometry::assemble_transform( + Vec3d::Zero(), // don't apply offset + instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 0.0)), // don't apply Z-rotation + instances[instance]->get_scaling_factor(), + instances[instance]->get_mirror() + ); + + z -= instances[instance]->get_offset()(2); + + // Lower part per-instance bounding boxes + std::vector lower_bboxes { instances.size() }; + + for (ModelVolume *volume : volumes) { + const auto volume_matrix = volume->get_matrix(); + if (! volume->is_model_part()) { - // don't cut modifiers - upper->add_volume(*volume); - lower->add_volume(*volume); + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + + volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); + + if (keep_upper) { upper->add_volume(*volume); } + if (keep_lower) { lower->add_volume(*volume); } } else { TriangleMesh upper_mesh, lower_mesh; + + // Transform the mesh by the combined transformation matrix + volume->mesh.transform(instance_matrix * volume_matrix); + + // Perform cut TriangleMeshSlicer tms(&volume->mesh); tms.cut(z, &upper_mesh, &lower_mesh); - upper_mesh.repair(); - lower_mesh.repair(); - upper_mesh.reset_repair_stats(); - lower_mesh.reset_repair_stats(); - - if (upper_mesh.facets_count() > 0) { + // Reset volume transformation except for offset + const Vec3d offset = volume->get_offset(); + volume->set_transformation(Geometry::Transformation()); + volume->set_offset(offset); + + if (keep_upper) { + upper_mesh.repair(); + upper_mesh.reset_repair_stats(); + } + if (keep_lower) { + lower_mesh.repair(); + lower_mesh.reset_repair_stats(); + } + + if (keep_upper && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } - if (lower_mesh.facets_count() > 0) { + if (keep_lower && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); + + // Compute the lower part instances' bounding boxes to figure out where to place + // the upper part + if (keep_upper) { + for (size_t i = 0; i < instances.size(); i++) { + lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true)); + } + } } } } + + ModelObjectPtrs res; + + if (keep_upper && upper->volumes.size() > 0) { + upper->invalidate_bounding_box(); + upper->center_around_origin(); + + // Reset instance transformation except offset and Z-rotation + for (size_t i = 0; i < instances.size(); i++) { + auto &instance = upper->instances[i]; + const Vec3d offset = instance->get_offset(); + const double rot_z = instance->get_rotation()(2); + // The upper part displacement is set to half of the lower part bounding box + // this is done in hope at least a part of the upper part will always be visible and draggable + const Vec3d displace = lower_bboxes[i].size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0)); + + instance->set_transformation(Geometry::Transformation()); + instance->set_offset(offset + displace); + instance->set_rotation(Vec3d(0.0, 0.0, rot_z)); + } + + res.push_back(upper); + } + if (keep_lower && lower->volumes.size() > 0) { + lower->invalidate_bounding_box(); + lower->center_around_origin(); + + // Reset instance transformation except offset and Z-rotation + for (auto *instance : lower->instances) { + const Vec3d offset = instance->get_offset(); + const double rot_z = instance->get_rotation()(2); + + instance->set_transformation(Geometry::Transformation()); + instance->set_offset(offset); + instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); + } + + res.push_back(lower); + } + + return res; } void ModelObject::split(ModelObjectPtrs* new_objects) @@ -863,55 +1134,132 @@ void ModelObject::split(ModelObjectPtrs* new_objects) if (this->volumes.size() > 1) { // We can't split meshes if there's more than one volume, because // we can't group the resulting meshes by object afterwards - new_objects->push_back(this); + new_objects->emplace_back(this); return; } ModelVolume* volume = this->volumes.front(); TriangleMeshPtrs meshptrs = volume->mesh.split(); for (TriangleMesh *mesh : meshptrs) { - // Snap the mesh to Z=0. - float z0 = FLT_MAX; - mesh->repair(); - ModelObject* new_object = m_model->add_object(*this, false); - new_object->input_file = ""; - ModelVolume* new_volume = new_object->add_volume(*mesh); - new_volume->name = volume->name; - new_volume->config = volume->config; - new_volume->set_type(volume->type()); - new_volume->material_id(volume->material_id()); - - new_objects->push_back(new_object); + // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? + ModelObject* new_object = m_model->add_object(); + new_object->name = this->name; + new_object->config = this->config; + new_object->instances.reserve(this->instances.size()); + for (const ModelInstance *model_instance : this->instances) + new_object->add_instance(*model_instance); +#if ENABLE_MODELVOLUME_TRANSFORM + ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + new_vol->center_geometry(); + + for (ModelInstance* model_instance : new_object->instances) + { + Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); + model_instance->set_offset(model_instance->get_offset() + shift); + } + + new_vol->set_offset(Vec3d::Zero()); +#else + new_object->add_volume(*volume, std::move(*mesh)); +#endif // ENABLE_MODELVOLUME_TRANSFORM + new_objects->emplace_back(new_object); delete mesh; } return; } -// Called by Print::validate() from the UI thread. -void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) +void ModelObject::repair() { - for (const ModelVolume* vol : this->volumes) - { - if (vol->is_model_part()) - { - for (ModelInstance* inst : this->instances) - { - BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(inst->world_matrix()); + for (ModelVolume *v : this->volumes) + v->mesh.repair(); +} - if (print_volume.contains(bb)) - inst->print_volume_state = ModelInstance::PVS_Inside; - else if (print_volume.intersects(bb)) - inst->print_volume_state = ModelInstance::PVS_Partly_Outside; - else - inst->print_volume_state = ModelInstance::PVS_Fully_Outside; - } +double ModelObject::get_min_z() const +{ + if (instances.empty()) + return 0.0; + else + { + double min_z = DBL_MAX; + for (size_t i = 0; i < instances.size(); ++i) + { + min_z = std::min(min_z, get_instance_min_z(i)); } + return min_z; } } +double ModelObject::get_instance_min_z(size_t instance_idx) const +{ + double min_z = DBL_MAX; + + ModelInstance* inst = instances[instance_idx]; + const Transform3d& mi = inst->get_matrix(true); + + for (const ModelVolume* v : volumes) + { + if (!v->is_model_part()) + continue; + +#if ENABLE_MODELVOLUME_TRANSFORM + Transform3d mv = mi * v->get_matrix(); + const TriangleMesh& hull = v->get_convex_hull(); + for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f) + { + const stl_facet* facet = hull.stl.facet_start + f; + min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast())); + min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast())); + min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast())); + } +#else + for (uint32_t f = 0; f < v->mesh.stl.stats.number_of_facets; ++f) + { + const stl_facet* facet = v->mesh.stl.facet_start + f; + min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[0].cast())); + min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[1].cast())); + min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[2].cast())); + } +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + + return min_z + inst->get_offset(Z); +} + +unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) +{ + unsigned int num_printable = 0; + enum { + INSIDE = 1, + OUTSIDE = 2 + }; + for (ModelInstance *model_instance : this->instances) { + unsigned int inside_outside = 0; + for (const ModelVolume *vol : this->volumes) + if (vol->is_model_part()) { +#if ENABLE_MODELVOLUME_TRANSFORM + BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix() * vol->get_matrix()); +#else + BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + if (print_volume.contains(bb)) + inside_outside |= INSIDE; + else if (print_volume.intersects(bb)) + inside_outside |= INSIDE | OUTSIDE; + else + inside_outside |= OUTSIDE; + } + model_instance->print_volume_state = + (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstance::PVS_Partly_Outside : + (inside_outside == INSIDE) ? ModelInstance::PVS_Inside : ModelInstance::PVS_Fully_Outside; + if (inside_outside == INSIDE) + ++ num_printable; + } + return num_printable; +} + void ModelObject::print_info() const { using namespace std; @@ -954,34 +1302,49 @@ void ModelObject::print_info() const cout << "volume = " << mesh.volume() << endl; } -void ModelVolume::material_id(t_model_material_id material_id) +void ModelVolume::set_material_id(t_model_material_id material_id) { - this->_material_id = material_id; - - // ensure this->_material_id references an existing material - (void)this->object->get_model()->add_material(material_id); + m_material_id = material_id; + // ensure m_material_id references an existing material + if (! material_id.empty()) + this->object->get_model()->add_material(material_id); } ModelMaterial* ModelVolume::material() const { - return this->object->get_model()->get_material(this->_material_id); + return this->object->get_model()->get_material(m_material_id); } void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material) { - this->_material_id = material_id; - (void)this->object->get_model()->add_material(material_id, material); + m_material_id = material_id; + if (! material_id.empty()) + this->object->get_model()->add_material(material_id, material); } -ModelMaterial* ModelVolume::assign_unique_material() +// Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. +int ModelVolume::extruder_id() const { - Model* model = this->get_object()->get_model(); - - // as material-id "0" is reserved by the AMF spec we start from 1 - this->_material_id = 1 + model->materials.size(); // watchout for implicit cast - return model->add_material(this->_material_id); + int extruder_id = -1; + if (this->is_model_part()) { + const ConfigOption *opt = this->config.option("extruder"); + if (opt == nullptr) + opt = this->object->config.option("extruder"); + extruder_id = (opt == nullptr) ? 0 : opt->getInt(); + } + return extruder_id; } +#if ENABLE_MODELVOLUME_TRANSFORM +void ModelVolume::center_geometry() +{ + Vec3d shift = -mesh.bounding_box().center(); + mesh.translate((float)shift(0), (float)shift(1), (float)shift(2)); + m_convex_hull.translate((float)shift(0), (float)shift(1), (float)shift(2)); + translate(-shift); +} +#endif // ENABLE_MODELVOLUME_TRANSFORM + void ModelVolume::calculate_convex_hull() { m_convex_hull = mesh.convex_hull_3d(); @@ -1040,16 +1403,28 @@ size_t ModelVolume::split(unsigned int max_extruders) std::string name = this->name; Model::reset_auto_extruder_id(); +#if ENABLE_MODELVOLUME_TRANSFORM + Vec3d offset = this->get_offset(); +#endif // ENABLE_MODELVOLUME_TRANSFORM for (TriangleMesh *mesh : meshptrs) { mesh->repair(); if (idx == 0) + { this->mesh = std::move(*mesh); + this->calculate_convex_hull(); + // Assign a new unique ID, so that a new GLVolume will be generated. + this->set_new_unique_id(); + } else - this->object->volumes.insert(this->object->volumes.begin() + (++ ivolume), new ModelVolume(object, *this, std::move(*mesh))); - char str_idx[64]; - sprintf(str_idx, "_%d", idx + 1); - this->object->volumes[ivolume]->name = name + str_idx; + this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); + +#if ENABLE_MODELVOLUME_TRANSFORM + this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); + this->object->volumes[ivolume]->center_geometry(); + this->object->volumes[ivolume]->translate(offset); +#endif // ENABLE_MODELVOLUME_TRANSFORM + this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); delete mesh; ++ idx; @@ -1058,33 +1433,160 @@ size_t ModelVolume::split(unsigned int max_extruders) return idx; } -void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const +void ModelVolume::translate(const Vec3d& displacement) { - mesh->transform(world_matrix(dont_translate).cast()); +#if ENABLE_MODELVOLUME_TRANSFORM + set_offset(get_offset() + displacement); +#else + mesh.translate((float)displacement(0), (float)displacement(1), (float)displacement(2)); + m_convex_hull.translate((float)displacement(0), (float)displacement(1), (float)displacement(2)); +#endif // ENABLE_MODELVOLUME_TRANSFORM } -BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const +void ModelVolume::scale(const Vec3d& scaling_factors) +{ +#if ENABLE_MODELVOLUME_TRANSFORM + set_scaling_factor(get_scaling_factor().cwiseProduct(scaling_factors)); +#else + mesh.scale(scaling_factors); + m_convex_hull.scale(scaling_factors); +#endif // ENABLE_MODELVOLUME_TRANSFORM +} + +void ModelVolume::rotate(double angle, Axis axis) +{ +#if ENABLE_MODELVOLUME_TRANSFORM + switch (axis) + { + case X: { rotate(angle, Vec3d::UnitX()); break; } + case Y: { rotate(angle, Vec3d::UnitY()); break; } + case Z: { rotate(angle, Vec3d::UnitZ()); break; } + } +#else + mesh.rotate(angle, axis); + m_convex_hull.rotate(angle, axis); +#endif // ENABLE_MODELVOLUME_TRANSFORM +} + +void ModelVolume::rotate(double angle, const Vec3d& axis) +{ +#if ENABLE_MODELVOLUME_TRANSFORM + set_rotation(get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix())); +#else + mesh.rotate(angle, axis); + m_convex_hull.rotate(angle, axis); +#endif // ENABLE_MODELVOLUME_TRANSFORM +} + +void ModelVolume::mirror(Axis axis) +{ +#if ENABLE_MODELVOLUME_TRANSFORM + Vec3d mirror = get_mirror(); + switch (axis) + { + case X: { mirror(0) *= -1.0; break; } + case Y: { mirror(1) *= -1.0; break; } + case Z: { mirror(2) *= -1.0; break; } + } + set_mirror(mirror); +#else + mesh.mirror(axis); + m_convex_hull.mirror(axis); +#endif // ENABLE_MODELVOLUME_TRANSFORM +} + +#if !ENABLE_MODELVOLUME_TRANSFORM +void ModelInstance::set_rotation(const Vec3d& rotation) +{ + set_rotation(X, rotation(0)); + set_rotation(Y, rotation(1)); + set_rotation(Z, rotation(2)); +} + +void ModelInstance::set_rotation(Axis axis, double rotation) +{ + static const double TWO_PI = 2.0 * (double)PI; + while (rotation < 0.0) + { + rotation += TWO_PI; + } + while (TWO_PI < rotation) + { + rotation -= TWO_PI; + } + m_rotation(axis) = rotation; +} + +void ModelInstance::set_scaling_factor(const Vec3d& scaling_factor) +{ + set_scaling_factor(X, scaling_factor(0)); + set_scaling_factor(Y, scaling_factor(1)); + set_scaling_factor(Z, scaling_factor(2)); +} + +void ModelInstance::set_scaling_factor(Axis axis, double scaling_factor) +{ + m_scaling_factor(axis) = std::abs(scaling_factor); +} + +void ModelInstance::set_mirror(const Vec3d& mirror) +{ + set_mirror(X, mirror(0)); + set_mirror(Y, mirror(1)); + set_mirror(Z, mirror(2)); +} + +void ModelInstance::set_mirror(Axis axis, double mirror) +{ + double abs_mirror = std::abs(mirror); + if (abs_mirror == 0.0) + mirror = 1.0; + else if (abs_mirror != 1.0) + mirror /= abs_mirror; + + m_mirror(axis) = mirror; +} +#endif // !ENABLE_MODELVOLUME_TRANSFORM + +void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const +{ + mesh->transform(get_matrix(dont_translate)); +} + +BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. - TriangleMesh copy(*mesh); - copy.transform(world_matrix(true, false, true).cast()); + TriangleMesh copy(mesh); + copy.transform(get_matrix(true, false, true, true)); BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { - // Scale the bounding box uniformly. - if (std::abs(this->scaling_factor - 1.) > EPSILON) { - bbox.min *= this->scaling_factor; - bbox.max *= this->scaling_factor; + // Scale the bounding box along the three axes. + for (unsigned int i = 0; i < 3; ++i) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON) + { + bbox.min(i) *= get_scaling_factor((Axis)i); + bbox.max(i) *= get_scaling_factor((Axis)i); +#else + if (std::abs(this->m_scaling_factor(i) - 1.0) > EPSILON) + { + bbox.min(i) *= this->m_scaling_factor(i); + bbox.max(i) *= this->m_scaling_factor(i); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } } + // Translate the bounding box. if (! dont_translate) { -#if ENABLE_MODELINSTANCE_3D_OFFSET +#if ENABLE_MODELVOLUME_TRANSFORM + bbox.min += get_offset(); + bbox.max += get_offset(); +#else bbox.min += this->m_offset; bbox.max += this->m_offset; -#else - Eigen::Map(bbox.min.data()) += this->offset; - Eigen::Map(bbox.max.data()) += this->offset; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET +#endif // ENABLE_MODELVOLUME_TRANSFORM } } return bbox; @@ -1092,38 +1594,151 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { - return bbox.transformed(world_matrix(dont_translate)); + return bbox.transformed(get_matrix(dont_translate)); } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { - return world_matrix(dont_translate) * v; + return get_matrix(dont_translate) * v; } void ModelInstance::transform_polygon(Polygon* polygon) const { - polygon->rotate(this->rotation); // rotate around polygon origin - polygon->scale(this->scaling_factor); // scale around polygon origin -} - -Transform3d ModelInstance::world_matrix(bool dont_translate, bool dont_rotate, bool dont_scale) const -{ - Transform3d m = Transform3d::Identity(); - - if (!dont_translate) -#if ENABLE_MODELINSTANCE_3D_OFFSET - m.translate(m_offset); +#if ENABLE_MODELVOLUME_TRANSFORM + // CHECK_ME -> Is the following correct or it should take in account all three rotations ? + polygon->rotate(get_rotation(Z)); // rotate around polygon origin + // CHECK_ME -> Is the following correct ? + polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin #else - m.translate(Vec3d(offset(0), offset(1), 0.0)); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - - if (!dont_rotate) - m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ())); - - if (!dont_scale) - m.scale(scaling_factor); - - return m; + // CHECK_ME -> Is the following correct or it should take in account all three rotations ? + polygon->rotate(this->m_rotation(2)); // rotate around polygon origin + // CHECK_ME -> Is the following correct ? + polygon->scale(this->m_scaling_factor(0), this->m_scaling_factor(1)); // scale around polygon origin +#endif // ENABLE_MODELVOLUME_TRANSFORM } +#if !ENABLE_MODELVOLUME_TRANSFORM +Transform3d ModelInstance::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const +{ + Vec3d translation = dont_translate ? Vec3d::Zero() : m_offset; + Vec3d rotation = dont_rotate ? Vec3d::Zero() : m_rotation; + Vec3d scale = dont_scale ? Vec3d::Ones() : m_scaling_factor; + Vec3d mirror = dont_mirror ? Vec3d::Ones() : m_mirror; + return Geometry::assemble_transform(translation, rotation, scale, mirror); +} +#endif // !ENABLE_MODELVOLUME_TRANSFORM + +// Test whether the two models contain the same number of ModelObjects with the same set of IDs +// ordered in the same order. In that case it is not necessary to kill the background processing. +bool model_object_list_equal(const Model &model_old, const Model &model_new) +{ + if (model_old.objects.size() != model_new.objects.size()) + return false; + for (size_t i = 0; i < model_old.objects.size(); ++ i) + if (model_old.objects[i]->id() != model_new.objects[i]->id()) + return false; + return true; +} + +// Test whether the new model is just an extension of the old model (new objects were added +// to the end of the original list. In that case it is not necessary to kill the background processing. +bool model_object_list_extended(const Model &model_old, const Model &model_new) +{ + if (model_old.objects.size() >= model_new.objects.size()) + return false; + for (size_t i = 0; i < model_old.objects.size(); ++ i) + if (model_old.objects[i]->id() != model_new.objects[i]->id()) + return false; + return true; +} + +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type) +{ + bool modifiers_differ = false; + size_t i_old, i_new; + for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + const ModelVolume &mv_new = *model_object_new.volumes[i_new]; + if (mv_old.type() != type) { + ++ i_old; + continue; + } + if (mv_new.type() != type) { + ++ i_new; + continue; + } + if (mv_old.id() != mv_new.id()) + return true; + //FIXME test for the content of the mesh! + +#if ENABLE_MODELVOLUME_TRANSFORM + if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) + return true; +#endif // ENABLE_MODELVOLUME_TRANSFORM + ++i_old; + ++ i_new; + } + for (; i_old < model_object_old.volumes.size(); ++ i_old) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + if (mv_old.type() == type) + // ModelVolume was deleted. + return true; + } + for (; i_new < model_object_new.volumes.size(); ++ i_new) { + const ModelVolume &mv_new = *model_object_new.volumes[i_new]; + if (mv_new.type() == type) + // ModelVolume was added. + return true; + } + return false; +} + +#ifdef _DEBUG +// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. +void check_model_ids_validity(const Model &model) +{ + std::set ids; + auto check = [&ids](ModelID id) { + assert(id.id > 0); + assert(ids.find(id) == ids.end()); + ids.insert(id); + }; + for (const ModelObject *model_object : model.objects) { + check(model_object->id()); + for (const ModelVolume *model_volume : model_object->volumes) + check(model_volume->id()); + for (const ModelInstance *model_instance : model_object->instances) + check(model_instance->id()); + } + for (const auto mm : model.materials) + check(mm.second->id()); +} + +void check_model_ids_equal(const Model &model1, const Model &model2) +{ + // Verify whether the IDs of model1 and model match. + assert(model1.objects.size() == model2.objects.size()); + for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) { + const ModelObject &model_object1 = *model1.objects[idx_model]; + const ModelObject &model_object2 = * model2.objects[idx_model]; + assert(model_object1.id() == model_object2.id()); + assert(model_object1.volumes.size() == model_object2.volumes.size()); + assert(model_object1.instances.size() == model_object2.instances.size()); + for (size_t i = 0; i < model_object1.volumes.size(); ++ i) + assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id()); + for (size_t i = 0; i < model_object1.instances.size(); ++ i) + assert(model_object1.instances[i]->id() == model_object2.instances[i]->id()); + } + assert(model1.materials.size() == model2.materials.size()); + { + auto it1 = model1.materials.begin(); + auto it2 = model2.materials.begin(); + for (; it1 != model1.materials.end(); ++ it1, ++ it2) { + assert(it1->first == it2->first); // compare keys + assert(it1->second->id() == it2->second->id()); + } + } +} +#endif /* _DEBUG */ + } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a91972fb5..b02862203 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -11,6 +11,9 @@ #include #include #include +#if ENABLE_MODELVOLUME_TRANSFORM +#include "Geometry.hpp" +#endif // ENABLE_MODELVOLUME_TRANSFORM namespace Slic3r { @@ -19,21 +22,107 @@ class ModelInstance; class ModelMaterial; class ModelObject; class ModelVolume; -class PresetBundle; +class Print; +class SLAPrint; typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; -typedef std::map t_model_material_attributes; +typedef std::map t_model_material_attributes; -typedef std::map ModelMaterialMap; +typedef std::map ModelMaterialMap; typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; -// Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial +// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial. +// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) +// Valid IDs are strictly positive (non zero). +// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t +// for parameter overload. +struct ModelID +{ + ModelID(size_t id) : id(id) {} + + bool operator==(const ModelID &rhs) const { return this->id == rhs.id; } + bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; } + bool operator< (const ModelID &rhs) const { return this->id < rhs.id; } + bool operator> (const ModelID &rhs) const { return this->id > rhs.id; } + bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; } + bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; } + + size_t id; +}; + +// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID +// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances +// are only instantiated from the main thread. +class ModelBase +{ +public: + ModelID id() const { return m_id; } + +protected: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + ModelBase() : m_id(generate_new_id()) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + ModelBase(int) : m_id(ModelID(0)) {} + + // Use with caution! + void set_new_unique_id() { m_id = generate_new_id(); } + void set_invalid_id() { m_id = 0; } + // Use with caution! + void copy_id(const ModelBase &rhs) { m_id = rhs.id(); } + + // Override this method if a ModelBase derived class owns other ModelBase derived instances. + void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + +private: + ModelID m_id; + + static inline ModelID generate_new_id() { return ModelID(++ s_last_id); } + static size_t s_last_id; +}; + +#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ + /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ + /* to make a private copy for background processing. */ \ + static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \ + static TYPE* new_copy(TYPE &&rhs) { return new TYPE(std::move(rhs)); } \ + static TYPE make_copy(const TYPE &rhs) { return TYPE(rhs); } \ + static TYPE make_copy(TYPE &&rhs) { return TYPE(std::move(rhs)); } \ + TYPE& assign_copy(const TYPE &rhs); \ + TYPE& assign_copy(TYPE &&rhs); \ + /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ + static TYPE* new_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + auto obj = new TYPE(-1); \ + obj->assign_clone(rhs); \ + return obj; \ + } \ + TYPE make_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + TYPE obj(-1); \ + obj.assign_clone(rhs); \ + return obj; \ + } \ + TYPE& assign_clone(const TYPE &rhs) { \ + this->assign_copy(rhs); \ + this->assign_new_unique_ids_recursive(); \ + return *this; \ + } + +#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ +private: \ + /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \ + explicit TYPE(int) : ModelBase(-1) {}; \ + void assign_new_unique_ids_recursive(); + +// Material, which may be shared across multiple ModelObjects of a single Model. +class ModelMaterial : public ModelBase { - friend class Model; public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; @@ -44,24 +133,34 @@ public: void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } +protected: + friend class Model; + // Constructor, which assigns a new unique ID. + ModelMaterial(Model *model) : m_model(model) {} + // Copy constructor copies the ID and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + private: // Parent, owning this material. Model *m_model; - ModelMaterial(Model *model) : m_model(model) {} - ModelMaterial(Model *model, const ModelMaterial &other) : attributes(other.attributes), config(other.config), m_model(model) {} + ModelMaterial() = delete; + ModelMaterial(ModelMaterial &&rhs) = delete; + ModelMaterial& operator=(const ModelMaterial &rhs) = delete; + ModelMaterial& operator=(ModelMaterial &&rhs) = delete; }; // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. -class ModelObject +class ModelObject : public ModelBase { friend class Model; public: std::string name; - std::string input_file; + std::string input_file; // XXX: consider fs::path // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. // Instances are owned by this ModelObject. ModelInstancePtrs instances; @@ -80,25 +179,33 @@ public: // and used subsequently by the PrintObject. bool layer_height_profile_valid; + // This vector holds position of selected support points for SLA. The data are + // saved in mesh coordinates to allow using them for several instances. + std::vector sla_support_points; + /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment when user expects that. */ Vec3d origin_translation; - - Model* get_model() const { return m_model; }; - - ModelVolume* add_volume(const TriangleMesh &mesh); - ModelVolume* add_volume(TriangleMesh &&mesh); - ModelVolume* add_volume(const ModelVolume &volume); - void delete_volume(size_t idx); - void clear_volumes(); - ModelInstance* add_instance(); - ModelInstance* add_instance(const ModelInstance &instance); - void delete_instance(size_t idx); - void delete_last_instance(); - void clear_instances(); + Model* get_model() { return m_model; }; + const Model* get_model() const { return m_model; }; + + ModelVolume* add_volume(const TriangleMesh &mesh); + ModelVolume* add_volume(TriangleMesh &&mesh); + ModelVolume* add_volume(const ModelVolume &volume); + ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); + void delete_volume(size_t idx); + void clear_volumes(); + bool is_multiparts() const { return volumes.size() > 1; } + + ModelInstance* add_instance(); + ModelInstance* add_instance(const ModelInstance &instance); + ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation); + void delete_instance(size_t idx); + void delete_last_instance(); + void clear_instances(); // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. @@ -117,55 +224,72 @@ public: // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; void center_around_origin(); + void ensure_on_bed(); + void translate_instances(const Vec3d& vector); + void translate_instance(size_t instance_idx, const Vec3d& vector); void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } - void translate(coordf_t x, coordf_t y, coordf_t z); + void translate(double x, double y, double z); void scale(const Vec3d &versor); - void rotate(float angle, const Axis &axis); - void rotate(float angle, const Vec3d& axis); - void mirror(const Axis &axis); + void scale(const double s) { this->scale(Vec3d(s, s, s)); } + void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; - void cut(coordf_t z, Model* model) const; + ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates void split(ModelObjectPtrs* new_objects); + void repair(); + + double get_min_z() const; + double get_instance_min_z(size_t instance_idx) const; // Called by Print::validate() from the UI thread. - void check_instances_print_volume_state(const BoundingBoxf3& print_volume); + unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume); // Print object statistics to console. void print_info() const; - -private: + +protected: + friend class Print; + friend class SLAPrint; + // Called by Print::apply() to set the model pointer after making a copy. + void set_model(Model *model) { m_model = model; } + +private: ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {} - ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true); - ModelObject& operator= (ModelObject other); - void swap(ModelObject &other); ~ModelObject(); - // Parent object, owning this ModelObject. - Model *m_model; - // Bounding box, cached. + /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ + /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ + ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } + explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } + ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; } + ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; } + MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) + MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) + + // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. + Model *m_model = nullptr; + + // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_valid; }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. -class ModelVolume +class ModelVolume : public ModelBase { - friend class ModelObject; - - // The convex hull of this model's mesh. - TriangleMesh m_convex_hull; - public: - std::string name; + std::string name; // The triangular model. - TriangleMesh mesh; + TriangleMesh mesh; // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - DynamicPrintConfig config; + DynamicPrintConfig config; enum Type { MODEL_TYPE_INVALID = -1, @@ -183,59 +307,141 @@ public: bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } - t_model_material_id material_id() const { return this->_material_id; } - void material_id(t_model_material_id material_id); + bool is_support_modifier() const { return m_type == SUPPORT_BLOCKER || m_type == SUPPORT_ENFORCER; } + t_model_material_id material_id() const { return m_material_id; } + void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); + // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. + // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). + int extruder_id() const; + // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. - size_t split(unsigned int max_extruders); + size_t split(unsigned int max_extruders); + void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } + void translate(const Vec3d& displacement); + void scale(const Vec3d& scaling_factors); + void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); } + void scale(double s) { scale(Vec3d(s, s, s)); } + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); - ModelMaterial* assign_unique_material(); - - void calculate_convex_hull(); +#if ENABLE_MODELVOLUME_TRANSFORM + // translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box + void center_geometry(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; - TriangleMesh& get_convex_hull(); // Helpers for loading / storing into AMF / 3MF files. static Type type_from_string(const std::string &s); static std::string type_to_string(const Type t); - // Helpers for loading / storing into AMF / 3MF files. - static Type type_from_string(const std::string &s); - static std::string type_to_string(const Type t); +#if ENABLE_MODELVOLUME_TRANSFORM + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + + const Vec3d& get_offset() const { return m_transformation.get_offset(); } + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#endif // ENABLE_MODELVOLUME_TRANSFORM + +protected: + friend class Print; + friend class SLAPrint; + friend class ModelObject; + + explicit ModelVolume(const ModelVolume &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } private: // Parent object owning this ModelVolume. ModelObject* object; // Is it an object to be printed, or a modifier volume? Type m_type; - t_model_material_id _material_id; - + t_model_material_id m_material_id; + // The convex hull of this model's mesh. + TriangleMesh m_convex_hull; +#if ENABLE_MODELVOLUME_TRANSFORM + Geometry::Transformation m_transformation; +#endif // ENABLE_MODELVOLUME_TRANSFORM + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : + mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} + +#if ENABLE_MODELVOLUME_TRANSFORM + // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) + ModelBase(other), // copy the ID + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { - this->material_id(other.material_id()); + this->set_material_id(other.material_id()); } + // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) + name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { - this->material_id(other.material_id()); + this->set_material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } +#else + // Copying an existing volume, therefore this volume will get a copy of the ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other) : + ModelBase(other), // copy the ID + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) + { + if (! other.material_id().empty()) + this->set_material_id(other.material_id()); + } + // Providing a new mesh, therefore this volume will get a new unique ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : + name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) + { + if (! other.material_id().empty()) + this->set_material_id(other.material_id()); + if (mesh.stl.stats.number_of_facets > 1) + calculate_convex_hull(); + } +#endif // ENABLE_MODELVOLUME_TRANSFORM + + ModelVolume& operator=(ModelVolume &rhs) = delete; }; // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance +class ModelInstance : public ModelBase { public: enum EPrintVolumeState : unsigned char @@ -246,38 +452,79 @@ public: Num_BedStates }; - friend class ModelObject; - -#if ENABLE_MODELINSTANCE_3D_OFFSET private: +#if ENABLE_MODELVOLUME_TRANSFORM + Geometry::Transformation m_transformation; +#else Vec3d m_offset; // in unscaled coordinates + Vec3d m_rotation; // Rotation around the three axes, in radians around mesh center point + Vec3d m_scaling_factor; // Scaling factors along the three axes + Vec3d m_mirror; // Mirroring along the three axes +#endif // ENABLE_MODELVOLUME_TRANSFORM public: -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - - double rotation; // Rotation around the Z axis, in radians around mesh center point - double scaling_factor; -#if !ENABLE_MODELINSTANCE_3D_OFFSET - Vec2d offset; // in unscaled coordinates -#endif // !ENABLE_MODELINSTANCE_3D_OFFSET - // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; ModelObject* get_object() const { return this->object; } -#if ENABLE_MODELINSTANCE_3D_OFFSET +#if ENABLE_MODELVOLUME_TRANSFORM + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + + const Vec3d& get_offset() const { return m_transformation.get_offset(); } + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } +#else const Vec3d& get_offset() const { return m_offset; } double get_offset(Axis axis) const { return m_offset(axis); } void set_offset(const Vec3d& offset) { m_offset = offset; } void set_offset(Axis axis, double offset) { m_offset(axis) = offset; } -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + + const Vec3d& get_rotation() const { return m_rotation; } + double get_rotation(Axis axis) const { return m_rotation(axis); } + + void set_rotation(const Vec3d& rotation); + void set_rotation(Axis axis, double rotation); + + Vec3d get_scaling_factor() const { return m_scaling_factor; } + double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor); + void set_scaling_factor(Axis axis, double scaling_factor); + + const Vec3d& get_mirror() const { return m_mirror; } + double get_mirror(Axis axis) const { return m_mirror(axis); } + + void set_mirror(const Vec3d& mirror); + void set_mirror(Axis axis, double mirror); +#endif // ENABLE_MODELVOLUME_TRANSFORM // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; // Calculate a bounding box of a transformed mesh. To be called on an external mesh. - BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate = false) const; + BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; // Transform an external bounding box. BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; // Transform an external vector. @@ -285,59 +532,90 @@ public: // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; - Transform3d world_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false) const; +#if ENABLE_MODELVOLUME_TRANSFORM + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#else + Transform3d get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; +#endif // ENABLE_MODELVOLUME_TRANSFORM bool is_printable() const { return print_volume_state == PVS_Inside; } +protected: + friend class Print; + friend class SLAPrint; + friend class ModelObject; + + explicit ModelInstance(const ModelInstance &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } + private: // Parent object, owning this instance. ModelObject* object; -#if ENABLE_MODELINSTANCE_3D_OFFSET - ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {} - ModelInstance(ModelObject *object, const ModelInstance &other) : - rotation(other.rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {} +#if ENABLE_MODELVOLUME_TRANSFORM + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {} + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject *object, const ModelInstance &other) : + m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {} #else - ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {} - ModelInstance(ModelObject *object, const ModelInstance &other) : - rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {} -#endif // ENABLE_MODELINSTANCE_3D_OFFSET -}; + explicit ModelInstance(ModelObject *object) : m_offset(Vec3d::Zero()), m_rotation(Vec3d::Zero()), m_scaling_factor(Vec3d::Ones()), m_mirror(Vec3d::Ones()), object(object), print_volume_state(PVS_Inside) {} + explicit ModelInstance(ModelObject *object, const ModelInstance &other) : + m_offset(other.m_offset), m_rotation(other.m_rotation), m_scaling_factor(other.m_scaling_factor), m_mirror(other.m_mirror), object(object), print_volume_state(PVS_Inside) {} +#endif // ENABLE_MODELVOLUME_TRANSFORM + ModelInstance() = delete; + explicit ModelInstance(ModelInstance &&rhs) = delete; + ModelInstance& operator=(const ModelInstance &rhs) = delete; + ModelInstance& operator=(ModelInstance &&rhs) = delete; +}; // The print bed content. // Description of a triangular model with multiple materials, multiple instances with various affine transformations // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model +class Model : public ModelBase { static unsigned int s_auto_extruder_id; public: // Materials are owned by a model and referenced by objects through t_model_material_id. // Single material may be shared by multiple models. - ModelMaterialMap materials; + ModelMaterialMap materials; // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). - ModelObjectPtrs objects; + ModelObjectPtrs objects; + // Default constructor assigns a new ID to the model. Model() {} - Model(const Model &other); - Model& operator= (Model other); - void swap(Model &other); ~Model() { this->clear_objects(); this->clear_materials(); } - static Model read_from_file(const std::string &input_file, bool add_default_instances = true); - static Model read_from_archive(const std::string &input_file, PresetBundle* bundle, bool add_default_instances = true); + /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ + /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ + Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); } + explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; } + MODELBASE_DERIVED_COPY_MOVE_CLONE(Model) + + static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); + static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); + + /// Repair the ModelObjects of the current Model. + /// This function calls repair function on each TriangleMesh of each model object volume + void repair(); + + // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); - ModelObject* add_object(const ModelObject &other, bool copy_volumes = true); - void delete_object(size_t idx); - void delete_object(ModelObject* object); - void clear_objects(); - + ModelObject* add_object(const ModelObject &other); + void delete_object(size_t idx); + bool delete_object(ModelID id); + bool delete_object(ModelObject* object); + void clear_objects(); + ModelMaterial* add_material(t_model_material_id material_id); ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); ModelMaterial* get_material(t_model_material_id material_id) { @@ -345,12 +623,16 @@ public: return (i == this->materials.end()) ? nullptr : i->second; } - void delete_material(t_model_material_id material_id); - void clear_materials(); - bool add_default_instances(); + void delete_material(t_model_material_id material_id); + void clear_materials(); + bool add_default_instances(); // Returns approximate axis aligned bounding box of this model BoundingBoxf3 bounding_box() const; - void center_instances_around_point(const Vec2d &point); + // Set the print_volume_state of PrintObject::instances, + // return total number of printable objects. + unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); + // Returns true if any ModelObject was modified. + bool center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); @@ -370,8 +652,35 @@ public: static unsigned int get_auto_extruder_id(unsigned int max_extruders); static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); static void reset_auto_extruder_id(); + + // Propose an output file name based on the first printable object's name. + std::string propose_export_file_name() const; + +private: + MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) }; +#undef MODELBASE_DERIVED_COPY_MOVE_CLONE +#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE + +// Test whether the two models contain the same number of ModelObjects with the same set of IDs +// ordered in the same order. In that case it is not necessary to kill the background processing. +extern bool model_object_list_equal(const Model &model_old, const Model &model_new); + +// Test whether the new model is just an extension of the old model (new objects were added +// to the end of the original list. In that case it is not necessary to kill the background processing. +extern bool model_object_list_extended(const Model &model_old, const Model &model_new); + +// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) +// than the old ModelObject. +extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type); + +#ifdef _DEBUG +// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. +void check_model_ids_validity(const Model &model); +void check_model_ids_equal(const Model &model1, const Model &model2); +#endif /* _DEBUG */ + } #endif diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp new file mode 100644 index 000000000..d182f1501 --- /dev/null +++ b/src/libslic3r/ModelArrange.cpp @@ -0,0 +1,768 @@ +#include "ModelArrange.hpp" +#include "Model.hpp" +#include "SVG.hpp" + +#include + +#include +#include + +#include + +namespace Slic3r { + +namespace arr { + +using namespace libnest2d; + +std::string toString(const Model& model, bool holes = true) { + std::stringstream ss; + + ss << "{\n"; + + for(auto objptr : model.objects) { + if(!objptr) continue; + + auto rmesh = objptr->raw_mesh(); + + for(auto objinst : objptr->instances) { + if(!objinst) continue; + + Slic3r::TriangleMesh tmpmesh = rmesh; + // CHECK_ME -> Is the following correct ? + tmpmesh.scale(objinst->get_scaling_factor()); + objinst->transform_mesh(&tmpmesh); + ExPolygons expolys = tmpmesh.horizontal_projection(); + for(auto& expoly_complex : expolys) { + + auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); + if(tmp.empty()) continue; + auto expoly = tmp.front(); + expoly.contour.make_clockwise(); + for(auto& h : expoly.holes) h.make_counter_clockwise(); + + ss << "\t{\n"; + ss << "\t\t{\n"; + + for(auto v : expoly.contour.points) ss << "\t\t\t{" + << v(0) << ", " + << v(1) << "},\n"; + { + auto v = expoly.contour.points.front(); + ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; + } + ss << "\t\t},\n"; + + // Holes: + ss << "\t\t{\n"; + if(holes) for(auto h : expoly.holes) { + ss << "\t\t\t{\n"; + for(auto v : h.points) ss << "\t\t\t\t{" + << v(0) << ", " + << v(1) << "},\n"; + { + auto v = h.points.front(); + ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; + } + ss << "\t\t\t},\n"; + } + ss << "\t\t},\n"; + + ss << "\t},\n"; + } + } + } + + ss << "}\n"; + + return ss.str(); +} + +void toSVG(SVG& svg, const Model& model) { + for(auto objptr : model.objects) { + if(!objptr) continue; + + auto rmesh = objptr->raw_mesh(); + + for(auto objinst : objptr->instances) { + if(!objinst) continue; + + Slic3r::TriangleMesh tmpmesh = rmesh; + tmpmesh.scale(objinst->get_scaling_factor()); + objinst->transform_mesh(&tmpmesh); + ExPolygons expolys = tmpmesh.horizontal_projection(); + svg.draw(expolys); + } + } +} + +namespace bgi = boost::geometry::index; + +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; +template +using TPacker = typename placers::_NofitPolyPlacer; + +const double BIG_ITEM_TRESHOLD = 0.02; + +Box boundingBox(const Box& pilebb, const Box& ibb ) { + auto& pminc = pilebb.minCorner(); + auto& pmaxc = pilebb.maxCorner(); + auto& iminc = ibb.minCorner(); + auto& imaxc = ibb.maxCorner(); + PointImpl minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return Box(minc, maxc); +} + +std::tuple +objfunc(const PointImpl& bincenter, + const shapelike::Shapes& merged_pile, + const Box& pilebb, + const ItemGroup& items, + const Item &item, + double bin_area, + double norm, // A norming factor for physical dimensions + // a spatial index to quickly get neighbors of the candidate item + const SpatIndex& spatindex, + const SpatIndex& smalls_spatindex, + const ItemGroup& remaining + ) +{ + using Coord = TCoord; + + static const double ROUNDNESS_RATIO = 0.5; + static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; + + // We will treat big items (compared to the print bed) differently + auto isBig = [bin_area](double a) { + return a/bin_area > BIG_ITEM_TRESHOLD ; + }; + + // Candidate item bounding box + auto ibb = sl::boundingBox(item.transformedShape()); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = boundingBox(pilebb, ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + if(isBig(item.area()) || spatindex.empty()) { + // This branch is for the bigger items.. + + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + // The smalles distance from the arranged pile center: + auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + dist = 0.8*dist + 0.2*bindist; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + if(remaining.empty()) { + + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); + + double circ = ec.circumference() / norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; + score = 0.5*circ + 0.5*bcirc; + + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the alignment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. + auto alignment_score = 1.0; + + density = std::sqrt((fullbb.width() / norm )* + (fullbb.height() / norm)); + auto querybb = item.boundingBox(); + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(spatindex.size()); + if(isBig(item.area())) { + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } else { + smalls_spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + Item& p = items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + } + + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neighbors + if(result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + } + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / norm; + } + + return std::make_tuple(score, fullbb); +} + +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations + // arranger.useMinimumBoundigBoxRotation(); + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.65f; + + pcfg.parallel = true; +} + +template +class AutoArranger {}; + +template +class _ArrBase { +protected: + + using Placer = TPacker; + using Selector = FirstFitSelection; + using Packer = Nester; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + using Pile = sl::Shapes; + + Packer m_pck; + PConfig m_pconf; // Placement configuration + double m_bin_area; + SpatIndex m_rtree; + SpatIndex m_smallsrtree; + double m_norm; + Pile m_merged_pile; + Box m_pilebb; + ItemGroup m_remaining; + ItemGroup m_items; +public: + + _ArrBase(const TBin& bin, Distance dist, + std::function progressind, + std::function stopcond): + m_pck(bin, dist), m_bin_area(sl::area(bin)), + m_norm(std::sqrt(sl::area(bin))) + { + fillConfig(m_pconf); + + m_pconf.before_packing = + [this](const Pile& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + m_items = items; + m_merged_pile = merged_pile; + m_remaining = remaining; + + m_pilebb = sl::boundingBox(merged_pile); + + m_rtree.clear(); + m_smallsrtree.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a/m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); + m_smallsrtree.insert({itm.boundingBox(), idx}); + } + }; + + m_pck.progressIndicator(progressind); + m_pck.stopCondition(stopcond); + } + + template inline IndexedPackGroup operator()(Args&&...args) { + m_rtree.clear(); + return m_pck.executeIndexed(std::forward(args)...); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const Box& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) + { + + m_pconf.object_function = [this, bin] (const Item &item) { + + auto result = objfunc(bin.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); + + double miss = Placer::overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +using lnCircle = libnest2d::_Circle; + +inline lnCircle to_lnCircle(const Circle& circ) { + return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); +} + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const lnCircle& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) { + + m_pconf.object_function = [this, &bin] (const Item &item) { + + auto result = objfunc(bin.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + + double score = std::get<0>(result); + + auto isBig = [this](const Item& itm) { + return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + if(isBig(item)) { + auto mp = m_merged_pile; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, bin); + if(miss < 0) miss = 0; + score += miss*miss; + } + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + AutoArranger(const PolygonImpl& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) + { + m_pconf.object_function = [this, &bin] (const Item &item) { + + auto binbb = sl::boundingBox(bin); + auto result = objfunc(binbb.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + double score = std::get<0>(result); + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +template<> // Specialization with no bin +class AutoArranger: public _ArrBase { +public: + + AutoArranger(Distance dist, std::function progressind, + std::function stopcond): + _ArrBase(Box(0, 0), dist, progressind, stopcond) + { + this->m_pconf.object_function = [this] (const Item &item) { + + auto result = objfunc({0, 0}, + m_merged_pile, + m_pilebb, + m_items, + item, + 0, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + return std::get<0>(result); + }; + + this->m_pck.configure(m_pconf); + } +}; + +// A container which stores a pointer to the 3D object and its projected +// 2D shape from top view. +using ShapeData2D = + std::vector>; + +ShapeData2D projectModelFromTop(const Slic3r::Model &model) { + ShapeData2D ret; + + auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), + [](size_t s, ModelObject* o){ + return s + o->instances.size(); + }); + + ret.reserve(s); + + for(ModelObject* objptr : model.objects) { + if(objptr) { + + TriangleMesh rmesh = objptr->raw_mesh(); + + ModelInstance * finst = objptr->instances.front(); + + // Object instances should carry the same scaling and + // x, y rotation that is why we use the first instance + rmesh.scale(finst->get_scaling_factor()); + rmesh.rotate_x(float(finst->get_rotation()(X))); + rmesh.rotate_y(float(finst->get_rotation()(Y))); + + // TODO export the exact 2D projection + auto p = rmesh.convex_hull(); + + p.make_clockwise(); + p.append(p.first_point()); + auto clpath = Slic3rMultiPoint_to_ClipperPath(p); + + for(ModelInstance* objinst : objptr->instances) { + if(objinst) { + ClipperLib::PolygonImpl pn; + pn.Contour = clpath; + + // Efficient conversion to item. + Item item(std::move(pn)); + + // Invalid geometries would throw exceptions when arranging + if(item.vertexCount() > 3) { + item.rotation(objinst->get_rotation(Z)); + item.translation({ + ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), + ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) + }); + ret.emplace_back(objinst, item); + } + } + } + } + } + + return ret; +} + +void applyResult( + IndexedPackGroup::value_type& group, + Coord batch_offset, + ShapeData2D& shapemap) +{ + for(auto& r : group) { + auto idx = r.first; // get the original item index + Item& item = r.second; // get the item itself + + // Get the model instance from the shapemap using the index + ModelInstance *inst_ptr = shapemap[idx].first; + + // Get the transformation data from the item object and scale it + // appropriately + auto off = item.translation(); + Radians rot = item.rotation(); + + Vec3d foff(off.X*SCALING_FACTOR + batch_offset, + off.Y*SCALING_FACTOR, + inst_ptr->get_offset()(Z)); + + // write the transformation data into the model instance + inst_ptr->set_rotation(Z, rot); + inst_ptr->set_offset(foff); + } +} + +BedShapeHint bedShape(const Polyline &bed) { + BedShapeHint ret; + + auto x = [](const Point& p) { return p(0); }; + auto y = [](const Point& p) { return p(1); }; + + auto width = [x](const BoundingBox& box) { + return x(box.max) - x(box.min); + }; + + auto height = [y](const BoundingBox& box) { + return y(box.max) - y(box.min); + }; + + auto area = [&width, &height](const BoundingBox& box) { + double w = width(box); + double h = height(box); + return w*h; + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto distance_to = [x, y](const Point& p1, const Point& p2) { + double dx = x(p2) - x(p1); + double dy = y(p2) - y(p1); + return std::sqrt(dx*dx + dy*dy); + }; + + auto bb = bed.bounding_box(); + + auto isCircle = [bb, distance_to](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + Circle ret(center, avg_dist); + for (auto el: vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + ret = Circle(); + break; + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { + ret.type = BedShapeType::BOX; + ret.shape.box = bb; + } + else if(auto c = isCircle(bed)) { + ret.type = BedShapeType::CIRCLE; + ret.shape.circ = c; + } else { + ret.type = BedShapeType::IRREGULAR; + ret.shape.polygon = bed; + } + + // Determine the bed shape by hand + return ret; +} + +bool arrange(Model &model, + coord_t min_obj_distance, + const Polyline &bed, + BedShapeHint bedhint, + bool first_bin_only, + std::function progressind, + std::function stopcondition) +{ + using ArrangeResult = _IndexedPackGroup; + + bool ret = true; + + // Get the 2D projected shapes with their 3D model instance pointers + auto shapemap = arr::projectModelFromTop(model); + + // Copy the references for the shapes only as the arranger expects a + // sequence of objects convertible to Item or ClipperPolygon + std::vector> shapes; + shapes.reserve(shapemap.size()); + std::for_each(shapemap.begin(), shapemap.end(), + [&shapes] (ShapeData2D::value_type& it) + { + shapes.push_back(std::ref(it.second)); + }); + + IndexedPackGroup result; + + // If there is no hint about the shape, we will try to guess + if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); + + BoundingBox bbb(bed); + + auto& cfn = stopcondition; + + auto binbb = Box({ + static_cast(bbb.min(0)), + static_cast(bbb.min(1)) + }, + { + static_cast(bbb.max(0)), + static_cast(bbb.max(1)) + }); + + switch(bedhint.type) { + case BedShapeType::BOX: { + + // Create the arranger for the box shaped bed + AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::CIRCLE: { + + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + + AutoArranger arrange(cc, min_obj_distance, progressind, cfn); + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::IRREGULAR: + case BedShapeType::WHO_KNOWS: { + + using P = libnest2d::PolygonImpl; + + auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); + P irrbed = sl::create(std::move(ctour)); + + AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + }; + + if(result.empty() || stopcondition()) return false; + + if(first_bin_only) { + applyResult(result.front(), 0, shapemap); + } else { + + const auto STRIDE_PADDING = 1.2; + + Coord stride = static_cast(STRIDE_PADDING* + binbb.width()*SCALING_FACTOR); + Coord batch_offset = 0; + + for(auto& group : result) { + applyResult(group, batch_offset, shapemap); + + // Only the first pack group can be placed onto the print bed. The + // other objects which could not fit will be placed next to the + // print bed + batch_offset += stride; + } + } + + for(auto objptr : model.objects) objptr->invalidate_bounding_box(); + + return ret && result.size() == 1; +} + +} +} diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 372689c39..d62e0df30 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -2,551 +2,13 @@ #define MODELARRANGE_HPP #include "Model.hpp" -#include "SVG.hpp" -#include - -#include -#include - -#include namespace Slic3r { + +class Model; + namespace arr { -using namespace libnest2d; - -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; - - ss << "{\n"; - - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->scaling_factor); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); - if(tmp.empty()) continue; - auto expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); - - ss << "\t{\n"; - ss << "\t\t{\n"; - - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; - - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; - - ss << "\t},\n"; - } - } - } - - ss << "}\n"; - - return ss.str(); -} - -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->scaling_factor); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} - -namespace bgi = boost::geometry::index; - -using SpatElement = std::pair; -using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; - -const double BIG_ITEM_TRESHOLD = 0.02; - -Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - PointImpl minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); -} - -std::tuple -objfunc(const PointImpl& bincenter, - const shapelike::Shapes& merged_pile, - const Box& pilebb, - const ItemGroup& items, - const Item &item, - double bin_area, - double norm, // A norming factor for physical dimensions - // a spatial index to quickly get neighbors of the candidate item - const SpatIndex& spatindex, - const SpatIndex& smalls_spatindex, - const ItemGroup& remaining - ) -{ - using Coord = TCoord; - - static const double ROUNDNESS_RATIO = 0.5; - static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; - - // We will treat big items (compared to the print bed) differently - auto isBig = [bin_area](double a) { - return a/bin_area > BIG_ITEM_TRESHOLD ; - }; - - // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(pilebb, ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - Box bigbb; - if(spatindex.empty()) bigbb = fullbb; - else { - auto boostbb = spatindex.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the alignment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = 1.0; - - density = std::sqrt((fullbb.width() / norm )* - (fullbb.height() / norm)); - auto querybb = item.boundingBox(); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - Item& p = items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neighbors - if(result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; - } - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - return std::make_tuple(score, fullbb); -} - -template -void fillConfig(PConf& pcfg) { - - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; - - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. - // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.65f; - - pcfg.parallel = true; -} - -template -class AutoArranger {}; - -template -class _ArrBase { -protected: - - using Placer = TPacker; - using Selector = FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; - using Distance = TCoord; - using Pile = sl::Shapes; - - Packer pck_; - PConfig pconf_; // Placement configuration - double bin_area_; - SpatIndex rtree_; - SpatIndex smallsrtree_; - double norm_; - Pile merged_pile_; - Box pilebb_; - ItemGroup remaining_; - ItemGroup items_; -public: - - _ArrBase(const TBin& bin, Distance dist, - std::function progressind, - std::function stopcond): - pck_(bin, dist), bin_area_(sl::area(bin)), - norm_(std::sqrt(sl::area(bin))) - { - fillConfig(pconf_); - - pconf_.before_packing = - [this](const Pile& merged_pile, // merged pile - const ItemGroup& items, // packed items - const ItemGroup& remaining) // future items to be packed - { - items_ = items; - merged_pile_ = merged_pile; - remaining_ = remaining; - - pilebb_ = sl::boundingBox(merged_pile); - - rtree_.clear(); - smallsrtree_.clear(); - - // We will treat big items (compared to the print bed) differently - auto isBig = [this](double a) { - return a/bin_area_ > BIG_ITEM_TRESHOLD ; - }; - - for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; - if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); - smallsrtree_.insert({itm.boundingBox(), idx}); - } - }; - - pck_.progressIndicator(progressind); - pck_.stopCondition(stopcond); - } - - template inline IndexedPackGroup operator()(Args&&...args) { - rtree_.clear(); - return pck_.executeIndexed(std::forward(args)...); - } -}; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const Box& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) - { - - pconf_.object_function = [this, bin] (const Item &item) { - - auto result = objfunc(bin.center(), - merged_pile_, - pilebb_, - items_, - item, - bin_area_, - norm_, - rtree_, - smallsrtree_, - remaining_); - - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - - double miss = Placer::overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += miss*miss; - - return score; - }; - - pck_.configure(pconf_); - } -}; - -using lnCircle = libnest2d::_Circle; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) { - - pconf_.object_function = [this, &bin] (const Item &item) { - - auto result = objfunc(bin.center(), - merged_pile_, - pilebb_, - items_, - item, - bin_area_, - norm_, - rtree_, - smallsrtree_, - remaining_); - - double score = std::get<0>(result); - - auto isBig = [this](const Item& itm) { - return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ; - }; - - if(isBig(item)) { - auto mp = merged_pile_; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, bin); - if(miss < 0) miss = 0; - score += miss*miss; - } - - return score; - }; - - pck_.configure(pconf_); - } -}; - -template<> -class AutoArranger: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) - { - pconf_.object_function = [this, &bin] (const Item &item) { - - auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), - merged_pile_, - pilebb_, - items_, - item, - bin_area_, - norm_, - rtree_, - smallsrtree_, - remaining_); - double score = std::get<0>(result); - - return score; - }; - - pck_.configure(pconf_); - } -}; - -template<> // Specialization with no bin -class AutoArranger: public _ArrBase { -public: - - AutoArranger(Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(Box(0, 0), dist, progressind, stopcond) - { - this->pconf_.object_function = [this] (const Item &item) { - - auto result = objfunc({0, 0}, - merged_pile_, - pilebb_, - items_, - item, - 0, - norm_, - rtree_, - smallsrtree_, - remaining_); - return std::get<0>(result); - }; - - this->pck_.configure(pconf_); - } -}; - -// A container which stores a pointer to the 3D object and its projected -// 2D shape from top view. -using ShapeData2D = - std::vector>; - -ShapeData2D projectModelFromTop(const Slic3r::Model &model) { - ShapeData2D ret; - - auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0, - [](size_t s, ModelObject* o){ - return s + o->instances.size(); - }); - - ret.reserve(s); - - for(auto objptr : model.objects) { - if(objptr) { - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(objinst) { - Slic3r::TriangleMesh tmpmesh = rmesh; - ClipperLib::PolygonImpl pn; - - tmpmesh.scale(objinst->scaling_factor); - - // TODO export the exact 2D projection - auto p = tmpmesh.convex_hull(); - - p.make_clockwise(); - p.append(p.first_point()); - pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); - - // Efficient conversion to item. - Item item(std::move(pn)); - - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - item.rotation(objinst->rotation); - item.translation( { -#if ENABLE_MODELINSTANCE_3D_OFFSET - ClipperLib::cInt(objinst->get_offset(X) / SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y) / SCALING_FACTOR) -#else - ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR), - ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR) -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - }); - ret.emplace_back(objinst, item); - } - } - } - } - } - - return ret; -} - class Circle { Point center_; double radius_; @@ -558,9 +20,6 @@ public: inline double radius() const { return radius_; } inline const Point& center() const { return center_; } inline operator bool() { return !std::isnan(radius_); } - inline operator lnCircle() { - return lnCircle({center_(0), center_(1)}, radius_); - } }; enum class BedShapeType { @@ -579,115 +38,7 @@ struct BedShapeHint { } shape; }; -BedShapeHint bedShape(const Polyline& bed) { - BedShapeHint ret; - - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w*h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - Circle ret(center, avg_dist); - for (auto el: vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - ret = Circle(); - break; - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; - } - else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; - } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; - } - - // Determine the bed shape by hand - return ret; -} - -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the tranformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); -#if ENABLE_MODELINSTANCE_3D_OFFSET - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR, 0.0); -#else - Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - - // write the tranformation data into the model instance - inst_ptr->rotation = rot; -#if ENABLE_MODELINSTANCE_3D_OFFSET - inst_ptr->set_offset(foff); -#else - inst_ptr->offset = foff; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - } -} - +BedShapeHint bedShape(const Polyline& bed); /** * \brief Arranges the model objects on the screen. @@ -695,7 +46,7 @@ void applyResult( * The arrangement considers multiple bins (aka. print beds) for placing all * the items provided in the model argument. If the items don't fit on one * print bed, the remaining will be placed onto newly created print beds. - * The first_bin_only parameter, if set to true, disables this behaviour and + * The first_bin_only parameter, if set to true, disables this behavior and * makes sure that only one print bed is filled and the remaining items will be * untouched. When set to false, the items which could not fit onto the * print bed will be placed next to the print bed so the user should see a @@ -715,111 +66,14 @@ void applyResult( * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Model &model, coordf_t min_obj_distance, +bool arrange(Model &model, coord_t min_obj_distance, const Slic3r::Polyline& bed, BedShapeHint bedhint, bool first_bin_only, std::function progressind, - std::function stopcondition) -{ - using ArrangeResult = _IndexedPackGroup; - - bool ret = true; - - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - std::vector> shapes; - shapes.reserve(shapemap.size()); - std::for_each(shapemap.begin(), shapemap.end(), - [&shapes] (ShapeData2D::value_type& it) - { - shapes.push_back(std::ref(it.second)); - }); - - IndexedPackGroup result; - - if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - auto& cfn = stopcondition; - - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = lnCircle(c); - - AutoArranger arrange(cc, min_obj_distance, progressind, cfn); - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - if(result.empty() || stopcondition()) return false; - - if(first_bin_only) { - applyResult(result.front(), 0, shapemap); - } else { - - const auto STRIDE_PADDING = 1.2; - - Coord stride = static_cast(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - applyResult(group, batch_offset, shapemap); - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } - - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - - return ret && result.size() == 1; -} + std::function stopcondition); } + } #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index ff3475ed8..198c39f31 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -297,7 +297,7 @@ void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) // Extend adjacency list until this start node. if (m_adjacency_list.size() < from + 1) { // Reserve in powers of two to avoid repeated reallocation. - m_adjacency_list.reserve(std::max(8, next_highest_power_of_2(from + 1))); + m_adjacency_list.reserve(std::max(8, next_highest_power_of_2((uint32_t)(from + 1)))); // Allocate new empty adjacency vectors. m_adjacency_list.resize(from + 1); } diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index f5a3c4bd0..7278a64f4 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -14,6 +14,15 @@ void MultiPoint::scale(double factor) pt *= factor; } +void MultiPoint::scale(double factor_x, double factor_y) +{ + for (Point &pt : points) + { + pt(0) *= factor_x; + pt(1) *= factor_y; + } +} + void MultiPoint::translate(double x, double y) { Vector v(x, y); @@ -171,13 +180,13 @@ Point MultiPoint::point_projection(const Point &point) const { dmin = d; proj = pt1; } - Pointf v1(coordf_t(pt1.x - pt0.x), coordf_t(pt1.y - pt0.y)); + Vec2d v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1))); coordf_t div = dot(v1); if (div > 0.) { - Pointf v2(coordf_t(point.x - pt0.x), coordf_t(point.y - pt0.y)); + Vec2d v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1))); coordf_t t = dot(v1, v2) / div; if (t > 0. && t < 1.) { - Point foot(coord_t(floor(coordf_t(pt0.x) + t * v1.x + 0.5)), coord_t(floor(coordf_t(pt0.y) + t * v1.y + 0.5))); + Point foot(coord_t(floor(coordf_t(pt0(0)) + t * v1(0) + 0.5)), coord_t(floor(coordf_t(pt0(1)) + t * v1(1) + 0.5))); d = foot.distance_to(point); if (d < dmin) { dmin = d; @@ -190,44 +199,170 @@ Point MultiPoint::point_projection(const Point &point) const { return proj; } -//FIXME This is very inefficient in term of memory use. -// The recursive algorithm shall run in place, not allocating temporary data in each recursion. -Points -MultiPoint::_douglas_peucker(const Points &points, const double tolerance) +std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { - assert(points.size() >= 2); - Points results; - double dmax = 0; - size_t index = 0; - Line full(points.front(), points.back()); - for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) { - // we use shortest distance, not perpendicular distance - double d = full.distance_to(*it); - if (d > dmax) { - index = it - points.begin(); - dmax = d; + std::vector result_pts; + if (! pts.empty()) { + const Point *anchor = &pts.front(); + size_t anchor_idx = 0; + const Point *floater = &pts.back(); + size_t floater_idx = pts.size() - 1; + result_pts.reserve(pts.size()); + result_pts.emplace_back(*anchor); + if (anchor_idx != floater_idx) { + assert(pts.size() > 1); + std::vector dpStack; + dpStack.reserve(pts.size()); + dpStack.emplace_back(floater_idx); + for (;;) { + double max_distSq = 0.0; + size_t furthest_idx = anchor_idx; + // find point furthest from line seg created by (anchor, floater) and note it + for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) { + double dist = Line::distance_to_squared(pts[i], *anchor, *floater); + if (dist > max_distSq) { + max_distSq = dist; + furthest_idx = i; + } + } + // remove point if less than tolerance + if (max_distSq <= tolerance) { + result_pts.emplace_back(*floater); + anchor_idx = floater_idx; + anchor = floater; + assert(dpStack.back() == floater_idx); + dpStack.pop_back(); + if (dpStack.empty()) + break; + floater_idx = dpStack.back(); + } else { + floater_idx = furthest_idx; + dpStack.emplace_back(floater_idx); + } + floater = &pts[floater_idx]; + } } } - if (dmax >= tolerance) { - Points dp0; - dp0.reserve(index + 1); - dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1); - // Recursive call. - Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); - results.reserve(results.size() + dp1.size() - 1); - results.insert(results.end(), dp1.begin(), dp1.end() - 1); - - dp0.clear(); - dp0.reserve(points.size() - index); - dp0.insert(dp0.end(), points.begin() + index, points.end()); - // Recursive call. - dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); - results.reserve(results.size() + dp1.size()); - results.insert(results.end(), dp1.begin(), dp1.end()); - } else { - results.push_back(points.front()); - results.push_back(points.back()); + return result_pts; +} + +// Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825 +// thanks to @fuchstraumer +/* + struct - vis_node + Used with the visivalignam simplification algorithm, which needs to be able to find a points + successors and predecessors to operate succesfully. Since this struct is only used in one + location, it could probably be dropped into a namespace to avoid polluting the slic3r namespace. + Source: https://github.com/shortsleeves/visvalingam_simplify + ^ Provided original algorithm implementation. I've only changed things a bit to "clean" them up + (i.e be more like my personal style), and managed to do this without requiring a binheap implementation + */ +struct vis_node{ + vis_node(const size_t& idx, const size_t& _prev_idx, const size_t& _next_idx, const double& _area) : pt_idx(idx), prev_idx(_prev_idx), next_idx(_next_idx), area(_area) {} + // Indices into a Points container, from which this object was constructed + size_t pt_idx, prev_idx, next_idx; + // Effective area of this "node" + double area; + // Overloaded operator used to sort the binheap + // Greater area = "more important" node. So, this node is less than the + // other node if it's area is less than the other node's area + bool operator<(const vis_node& other) { return (this->area < other.area); } +}; +Points MultiPoint::visivalingam(const Points& pts, const double& tolerance) +{ + // Make sure there's enough points in "pts" to bother with simplification. + assert(pts.size() >= 2); + // Result object + Points results; + // Lambda to calculate effective area spanned by a point and its immediate + // successor + predecessor. + auto effective_area = [pts](const size_t& curr_pt_idx, const size_t& prev_pt_idx, const size_t& next_pt_idx)->coordf_t { + const Point& curr = pts[curr_pt_idx]; + const Point& prev = pts[prev_pt_idx]; + const Point& next = pts[next_pt_idx]; + // Use point objects as vector-distances + const Vec2d curr_to_next = (next - curr).cast(); + const Vec2d prev_to_next = (prev - curr).cast(); + // Take cross product of these two vector distances + return 0.50 * abs(cross2(curr_to_next, prev_to_next)); + }; + // We store the effective areas for each node + std::vector areas; + areas.reserve(pts.size()); + // Construct the initial set of nodes. We will make a heap out of the "heap" vector using + // std::make_heap. node_list is used later. + std::vector node_list; + node_list.resize(pts.size()); + std::vector heap; + heap.reserve(pts.size()); + for (size_t i = 1; i < pts.size() - 1; ++ i) { + // Get effective area of current node. + coordf_t area = effective_area(i, i - 1, i + 1); + // If area is greater than some arbitrarily small value, use it. + node_list[i] = new vis_node(i, i - 1, i + 1, area); + heap.push_back(node_list[i]); } + // Call std::make_heap, which uses the < operator by default to make "heap" into + // a binheap, sorted by the < operator we defind in the vis_node struct + std::make_heap(heap.begin(), heap.end()); + // Start comparing areas. Set min_area to an outrageous value initially. + double min_area = -std::numeric_limits::max(); + while (!heap.empty()) { + // Get current node. + vis_node* curr = heap.front(); + // Pop node we just retrieved off the heap. pop_heap moves front element in vector + // to the back, so we can call pop_back() + std::pop_heap(heap.begin(), heap.end()); + heap.pop_back(); + // Sanity assert check + assert(curr == node_list[curr->pt_idx]); + // If the current pt'ss area is less than that of the previous pt's area + // use the last pt's area instead. This ensures we don't elimate the current + // point without eliminating the previous + min_area = std::max(min_area, curr->area); + // Update prev + vis_node* prev = node_list[curr->prev_idx]; + if(prev != nullptr){ + prev->next_idx = curr->next_idx; + prev->area = effective_area(prev->pt_idx, prev->prev_idx, prev->next_idx); + // For some reason, std::make_heap() is the fastest way to resort the heap. Probably needs testing. + std::make_heap(heap.begin(), heap.end()); + } + // Update next + vis_node* next = node_list[curr->next_idx]; + if(next != nullptr){ + next->prev_idx = curr->prev_idx; + next->area = effective_area(next->pt_idx, next->prev_idx, next->next_idx); + std::make_heap(heap.begin(), heap.end()); + } + areas[curr->pt_idx] = min_area; + node_list[curr->pt_idx] = nullptr; + delete curr; + } + // Clear node list and shrink_to_fit() (to free actual memory). Not necessary. Could be removed. + node_list.clear(); + node_list.shrink_to_fit(); + // This lambda is how we test whether or not to keep a point. + auto use_point = [areas, tolerance](const size_t& idx)->bool { + assert(idx < areas.size()); + // Return true at front/back of path/areas + if(idx == 0 || idx == areas.size() - 1){ + return true; + } + // Return true if area at idx is greater than minimum area to consider "valid" + else{ + return areas[idx] > tolerance; + } + }; + // Use previously defined lambda to build results. + for (size_t i = 0; i < pts.size(); ++i) { + if (use_point(i)){ + results.push_back(pts[i]); + } + } + // Check that results has at least two points + assert(results.size() >= 2); + // Return simplified vector of points return results; } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 51961d7a8..2440c3a49 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -26,6 +26,7 @@ public: MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); + void scale(double factor_x, double factor_y); void translate(double x, double y); void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } @@ -82,6 +83,7 @@ public: virtual Point point_projection(const Point &point) const; static Points _douglas_peucker(const Points &points, const double tolerance); + static Points visivalingam(const Points& pts, const double& tolerance); }; class MultiPoint3 @@ -112,7 +114,7 @@ inline double length(const Points &pts) { if (! pts.empty()) { auto it = pts.begin(); for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) - total += it->distance_to(*it_prev); + total += (*it - *it_prev).cast().norm(); } return total; } @@ -120,7 +122,7 @@ inline double length(const Points &pts) { inline double area(const Points &polygon) { double area = 0.; for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) - area += double(polygon[j].x + polygon[i].x) * double(polygon[i].y - polygon[j].y); + area += double(polygon[i](0) + polygon[j](0)) * double(polygon[i](1) - polygon[j](1)); return area; } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 188979ef1..637d0eec6 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -131,7 +131,7 @@ void PerimeterGenerator::process() //it's not dangerous as it will be intersected by 'unsupported' later //FIXME: add overlap in this->fill_surfaces->append // add overlap (perimeter_spacing/4 was good in test, ie 25%) - coord_t overlap = scale_(this->config->get_abs_value("infill_overlap", unscale(perimeter_spacing))); + coord_t overlap = scale_(this->config->get_abs_value("infill_overlap", unscale(perimeter_spacing))); unsupported_filtered = intersection_ex(unsupported_filtered, offset_ex(bridgeable_simplified, overlap)); } else { unsupported_filtered.clear(); @@ -675,7 +675,7 @@ PerimeterGenerator::_get_nearest_point(const PerimeterGeneratorLoops &children, const double dist = nearest_p.distance_to(p); //Try to find a point in the far side, aligning them if (dist + dist_cut / 20 < intersect.distance || - (config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter <0 || p.y > intersect.outter_best.y) + (config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter <0 || p.y() > intersect.outter_best.y()) && dist <= max_dist && intersect.distance + dist_cut / 20)) { //ok, copy the idx intersect.distance = (coord_t)nearest_p.distance_to(p); @@ -692,7 +692,7 @@ PerimeterGenerator::_get_nearest_point(const PerimeterGeneratorLoops &children, const Point &nearest_p = *child.polygon.closest_point(p); const double dist = nearest_p.distance_to(p); if (dist + SCALED_EPSILON < intersect.distance || - (config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter<0 || p.y < intersect.outter_best.y) + (config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter<0 || p.y() < intersect.outter_best.y()) && dist <= max_dist && intersect.distance + dist_cut / 20)) { //ok, copy the idx intersect.distance = (coord_t)nearest_p.distance_to(p); @@ -1131,8 +1131,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, travel_path_begin[2].extruder_id = -1; Line line(outer_start->polyline.points.back(), inner_start->polyline.points.front()); Point p_dist_cut_extrude = (line.b - line.a); - p_dist_cut_extrude.x = (coord_t)(p_dist_cut_extrude.x * ((double)max_width_extrusion) / (line.length() * 2)); - p_dist_cut_extrude.y = (coord_t)(p_dist_cut_extrude.y * ((double)max_width_extrusion) / (line.length() * 2)); + p_dist_cut_extrude.x() = (coord_t)(p_dist_cut_extrude.x() * ((double)max_width_extrusion) / (line.length() * 2)); + p_dist_cut_extrude.y() = (coord_t)(p_dist_cut_extrude.y() * ((double)max_width_extrusion) / (line.length() * 2)); //extrude a bit after the turn, to close the loop Point p_start_travel = line.a; p_start_travel += p_dist_cut_extrude; @@ -1168,8 +1168,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop, travel_path_end[2].extruder_id = -1; Line line(inner_end->polyline.points.back(), outer_end->polyline.points.front()); Point p_dist_cut_extrude = (line.b - line.a); - p_dist_cut_extrude.x = (coord_t)(p_dist_cut_extrude.x * ((double)max_width_extrusion) / (line.length() * 2)); - p_dist_cut_extrude.y = (coord_t)(p_dist_cut_extrude.y * ((double)max_width_extrusion) / (line.length() * 2)); + p_dist_cut_extrude.x() = (coord_t)(p_dist_cut_extrude.x() * ((double)max_width_extrusion) / (line.length() * 2)); + p_dist_cut_extrude.y() = (coord_t)(p_dist_cut_extrude.y() * ((double)max_width_extrusion) / (line.length() * 2)); //extrude a bit after the turn, to close the loop Point p_start_travel_2 = line.a; p_start_travel_2 += p_dist_cut_extrude; diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index cc6f1c75e..eae8f418b 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -94,27 +94,76 @@ void PlaceholderParser::update_timestamp(DynamicConfig &config) config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec)); } +// Ignore this key by the placeholder parser. +static inline bool placeholder_parser_ignore(const ConfigDef *def, const std::string &opt_key) +{ + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); + return (opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process"; +} + +static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConfig &config_new, const std::string &opt_key) +{ + const ConfigOption *opt_old = config_old.option(opt_key); + const ConfigOption *opt_new = config_new.option(opt_key); + assert(opt_new != nullptr); + if (opt_old == nullptr) + return false; + return (opt_new->type() == coFloatOrPercent) ? + dynamic_cast(opt_old)->value == config_new.get_abs_value(opt_key) : + *opt_new == *opt_old; +} + +std::vector PlaceholderParser::config_diff(const DynamicPrintConfig &rhs) +{ + const ConfigDef *def = rhs.def(); + std::vector diff_keys; + for (const t_config_option_key &opt_key : rhs.keys()) + if (! placeholder_parser_ignore(def, opt_key) && ! opts_equal(m_config, rhs, opt_key)) + diff_keys.emplace_back(opt_key); + return diff_keys; +} + // Scalar configuration values are stored into m_single, // vector configuration values are stored into m_multiple. // All vector configuration values stored into the PlaceholderParser // are expected to be addressed by the extruder ID, therefore // if a vector configuration value is addressed without an index, // a current extruder ID is used. -void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) +bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) { const ConfigDef *def = rhs.def(); + bool modified = false; for (const t_config_option_key &opt_key : rhs.keys()) { - const ConfigOptionDef *opt_def = def->get(opt_key); - if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process") + if (placeholder_parser_ignore(def, opt_key)) continue; - const ConfigOption *opt = rhs.option(opt_key); + if (! opts_equal(m_config, rhs, opt_key)) { + // Store a copy of the config option. + // Convert FloatOrPercent values to floats first. + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + const ConfigOption *opt_rhs = rhs.option(opt_key); + this->set(opt_key, (opt_rhs->type() == coFloatOrPercent) ? + new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : + opt_rhs->clone()); + modified = true; + } + } + return modified; +} + +void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vector &keys) +{ + for (const t_config_option_key &opt_key : keys) { + assert(! placeholder_parser_ignore(rhs.def(), opt_key)); // Store a copy of the config option. // Convert FloatOrPercent values to floats first. //FIXME there are some ratio_over chains, which end with empty ratio_with. // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. - this->set(opt_key, (opt->type() == coFloatOrPercent) ? + const ConfigOption *opt_rhs = rhs.option(opt_key); + this->set(opt_key, (opt_rhs->type() == coFloatOrPercent) ? new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : - opt->clone()); + opt_rhs->clone()); } } @@ -442,7 +491,7 @@ namespace client param1.data.d = d; param1.type = TYPE_DOUBLE; } else { - int i = 0.; + int i = 0; switch (fun) { case FUNCTION_MIN: i = std::min(param1.as_i(), param2.as_i()); break; case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 49d53ec9e..b5ed56fa1 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -14,7 +14,14 @@ class PlaceholderParser public: PlaceholderParser(); - void apply_config(const DynamicPrintConfig &config); + // Return a list of keys, which should be changed in m_config from rhs. + // This contains keys, which are found in rhs, but not in m_config. + std::vector config_diff(const DynamicPrintConfig &rhs); + // Return true if modified. + bool apply_config(const DynamicPrintConfig &config); + // To be called on the values returned by PlaceholderParser::config_diff(). + // The keys should already be valid. + void apply_only(const DynamicPrintConfig &config, const std::vector &keys); void apply_env_variables(); // Add new ConfigOption values to m_config. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index c2417d0dc..0e0b6e0ad 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -100,6 +100,29 @@ int Point::nearest_point_index(const PointConstPtrs &points) const return idx; } +/* distance to the closest point of line */ +double +Point::distance_to(const Line &line) const { + const double dx = line.b.x() - line.a.x(); + const double dy = line.b.y() - line.a.y(); + + const double l2 = dx*dx + dy*dy; // avoid a sqrt + if (l2 == 0.0) return this->distance_to(line.a); // line.a == line.b case + + // Consider the line extending the segment, parameterized as line.a + t (line.b - line.a). + // We find projection of this point onto the line. + // It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2 + const double t = ((this->x() - line.a.x()) * dx + (this->y() - line.a.y()) * dy) / l2; + if (t < 0.0) return this->distance_to(line.a); // beyond the 'a' end of the segment + else if (t > 1.0) return this->distance_to(line.b); // beyond the 'b' end of the segment + Point projection( + line.a.x() + t * dx, + line.a.y() + t * dy + ); + return this->distance_to(projection); +} + + int Point::nearest_point_index(const PointPtrs &points) const { PointConstPtrs p; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 6d9d82d25..4c6de13f1 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -51,11 +51,19 @@ inline coord_t cross2(const Vec2crd &v1, const Vec2crd &v2) { return v1(0) * v2( inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } +inline coordf_t dot(const Vec2d &v1, const Vec2d &v2) { return v1.x() * v2.x() + v1.y() * v2.y(); } +inline coordf_t dot(const Vec2d &v) { return v.x() * v.x() + v.y() * v.y(); } + inline Vec2crd to_2d(const Vec3crd &pt3) { return Vec2crd(pt3(0), pt3(1)); } inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } +inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } +inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } +inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); } +inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); } + inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); } @@ -113,6 +121,13 @@ public: double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + + double distance_to(const Point &point) const { return (point - *this).cast().norm(); } + double distance_to(const Line &line) const; + bool coincides_with(const Point &point) const { return this->x() == point.x() && this->y() == point.y(); } + bool coincides_with_epsilon(const Point &point) const { + return std::abs(this->x() - point.x()) < SCALED_EPSILON && std::abs(this->y() - point.y()) < SCALED_EPSILON; + } }; namespace int128 { @@ -182,6 +197,24 @@ public: m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); } + // Erase a data point equal to value. (ValueType has to declare the operator==). + // Returns true if the data point equal to value was found and removed. + bool erase(const ValueType &value) { + const Point *pt = m_point_accessor(value); + if (pt != nullptr) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Point((*pt)(0)>>m_grid_log2, (*pt)(1)>>m_grid_log2)); + // Remove the first item. + for (auto it = range.first; it != range.second; ++ it) { + if (it->second == value) { + m_map.erase(it); + return true; + } + } + } + return false; + } + // Return a pair of std::pair find(const Vec2crd &pt) { // Iterate over 4 closest grid cells around pt, @@ -209,7 +242,7 @@ public: } } } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ? + return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? std::make_pair(value_min, dist_min) : std::make_pair(nullptr, std::numeric_limits::max()); } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index e3ebc5ee6..1886d565c 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -26,7 +26,7 @@ public: Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } static Polyline new_scale(const std::vector &points) { - Polyline pl; + Polyline pl; pl.points.reserve(points.size()); for (const Vec2d &pt : points) pl.points.emplace_back(Point::new_scale(pt(0), pt(1))); @@ -138,6 +138,11 @@ bool remove_degenerate(Polylines &polylines); /// join something or is a dead-end. class ThickPolyline : public Polyline { public: + /// width size must be == point size + std::vector width; + /// if true => it's an endpoint, if false it join an other ThickPolyline. first is at front(), second is at back() + std::pair endpoints; + ThickPolyline() : endpoints(std::make_pair(false, false)) {} ThickLines thicklines() const; void reverse() { @@ -145,14 +150,6 @@ public: std::reverse(this->width.begin(), this->width.end()); std::swap(this->endpoints.first, this->endpoints.second); } - - /// width size must be == point size - std::vector width; - /// if true => it's an endpoint, if false it join an other ThickPolyline. first is at front(), second is at back() - std::pair endpoints; - ThickPolyline() : endpoints(std::make_pair(false, false)) {}; - ThickLines thicklines() const; - void reverse(); }; /// concatenate poylines if possible and refresh the endpoints diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 75a0574b9..49bcda294 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -10,12 +10,12 @@ #include "GCode/WipeTowerPrusaMM.hpp" #include #include -#include -#include #include #include "PrintExport.hpp" +#include + //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -25,33 +25,28 @@ namespace Slic3r { template class PrintState; template class PrintState; -void Print::clear_objects() +void Print::clear() { - tbb::mutex::scoped_lock lock(m_mutex); + tbb::mutex::scoped_lock lock(this->state_mutex()); + // The following call should stop background processing if it is running. + this->invalidate_all_steps(); for (PrintObject *object : m_objects) delete object; m_objects.clear(); for (PrintRegion *region : m_regions) delete region; m_regions.clear(); - this->invalidate_all_steps(); -} - -void Print::delete_object(size_t idx) -{ - tbb::mutex::scoped_lock lock(m_mutex); - // destroy object and remove it from our container - delete m_objects[idx]; - m_objects.erase(m_objects.begin() + idx); - this->invalidate_all_steps(); - // TODO: purge unused regions + m_model.clear_objects(); } +// Only used by the Perl test cases. void Print::reload_object(size_t /* idx */) { ModelObjectPtrs model_objects; { - tbb::mutex::scoped_lock lock(m_mutex); + tbb::mutex::scoped_lock lock(this->state_mutex()); + // The following call should stop background processing if it is running. + this->invalidate_all_steps(); /* TODO: this method should check whether the per-object config and per-material configs have changed in such a way that regions need to be rearranged or we can just apply the diff and invalidate something. Same logic as apply_config() @@ -68,33 +63,12 @@ void Print::reload_object(size_t /* idx */) for (PrintRegion *region : m_regions) delete region; m_regions.clear(); - this->invalidate_all_steps(); } // re-add model objects for (ModelObject *mo : model_objects) this->add_model_object(mo); } -// Reloads the model instances into the print class. -// The slicing shall not be running as the modified model instances at the print -// are used for the brim & skirt calculation. -// Returns true if the brim or skirt have been invalidated. -bool Print::reload_model_instances() -{ - tbb::mutex::scoped_lock lock(m_mutex); - bool invalidated = false; - for (PrintObject *object : m_objects) - invalidated |= object->reload_model_instances(); - return invalidated; -} - -PrintObjectPtrs Print::get_printable_objects() const -{ - PrintObjectPtrs printable_objects(m_objects); - printable_objects.erase(std::remove_if(printable_objects.begin(), printable_objects.end(), [](PrintObject* o) { return !o->is_printable(); }), printable_objects.end()); - return printable_objects; -} - PrintRegion* Print::add_region() { m_regions.emplace_back(new PrintRegion(this)); @@ -124,6 +98,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorstate_mutex()); for (const PrintObject *object : m_objects) - if (!object->m_state.is_done(step)) + if (! object->m_state.is_done_unguarded(step)) return false; return true; } @@ -308,16 +287,17 @@ bool Print::is_step_done(PrintObjectStep step) const std::vector Print::object_extruders() const { std::vector extruders; + extruders.reserve(m_regions.size() * 3); - for (PrintRegion* region : m_regions) { + for (const PrintRegion *region : m_regions) { // these checks reflect the same logic used in the GUI for enabling/disabling // extruder selection fields if (region->config().perimeters.value > 0 || m_config.brim_width.value > 0) - extruders.push_back(region->config().perimeter_extruder - 1); + extruders.emplace_back(region->config().perimeter_extruder - 1); if (region->config().fill_density.value > 0) - extruders.push_back(region->config().infill_extruder - 1); + extruders.emplace_back(region->config().infill_extruder - 1); if (region->config().top_solid_layers.value > 0 || region->config().bottom_solid_layers.value > 0) - extruders.push_back(region->config().solid_infill_extruder - 1); + extruders.emplace_back(region->config().solid_infill_extruder - 1); } sort_remove_duplicates(extruders); @@ -360,6 +340,14 @@ std::vector Print::extruders() const return extruders; } +unsigned int Print::num_object_instances() const +{ + unsigned int instances = 0; + for (const PrintObject *print_object : m_objects) + instances += print_object->copies().size(); + return instances; +} + void Print::_simplify_slices(double distance) { for (PrintObject *object : m_objects) { @@ -379,28 +367,74 @@ double Print::max_allowed_layer_height() const return nozzle_diameter_max; } +static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) +{ + if (opt.value > (int)num_extruders) + // assign the default extruder + opt.value = 1; +} + +static PrintObjectConfig object_config_from_model(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders) +{ + PrintObjectConfig config = default_object_config; + normalize_and_apply_config(config, object.config); + // Clamp invalid extruders to the default extruder (with index 1). + clamp_exturder_to_default(config.support_material_extruder, num_extruders); + clamp_exturder_to_default(config.support_material_interface_extruder, num_extruders); + return config; +} + +static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders) +{ + PrintRegionConfig config = default_region_config; + normalize_and_apply_config(config, volume.get_object()->config); + normalize_and_apply_config(config, volume.config); + if (! volume.material_id().empty()) + normalize_and_apply_config(config, volume.material()->config); + // Clamp invalid extruders to the default extruder (with index 1). + clamp_exturder_to_default(config.infill_extruder, num_extruders); + clamp_exturder_to_default(config.perimeter_extruder, num_extruders); + clamp_exturder_to_default(config.solid_infill_extruder, num_extruders); + return config; +} + // Caller is responsible for supplying models whose objects don't collide // and have explicit instance positions. void Print::add_model_object(ModelObject* model_object, int idx) { - tbb::mutex::scoped_lock lock(m_mutex); + tbb::mutex::scoped_lock lock(this->state_mutex()); + // Add a copy of this ModelObject to this Print. + m_model.objects.emplace_back(ModelObject::new_copy(*model_object)); + m_model.objects.back()->set_model(&m_model); // Initialize a new print object and store it at the given position. - PrintObject *object = new PrintObject(this, model_object, model_object->raw_bounding_box()); + PrintObject *object = new PrintObject(this, model_object, true); if (idx != -1) { delete m_objects[idx]; m_objects[idx] = object; } else m_objects.emplace_back(object); // Invalidate all print steps. - //FIXME lock mutex! this->invalidate_all_steps(); + // Set the transformation matrix without translation from the first instance. + if (! model_object->instances.empty()) { + // Trafo and bounding box, both in world coordinate system. + Transform3d trafo = model_object->instances.front()->get_matrix(); + BoundingBoxf3 bbox = model_object->instance_bounding_box(0); + // Now shift the object up to align it with the print bed. + trafo.data()[14] -= bbox.min(2); + // and reset the XY translation. + trafo.data()[12] = 0; + trafo.data()[13] = 0; + object->set_trafo(trafo); + } + size_t volume_id = 0; for (const ModelVolume *volume : model_object->volumes) { if (! volume->is_model_part() && ! volume->is_modifier()) continue; // Get the config applied to this volume. - PrintRegionConfig config = this->_region_config_from_model_volume(*volume); + PrintRegionConfig config = region_config_from_model_volume(m_default_region_config, *volume, 99999); // Find an existing print region with the same config. size_t region_id = size_t(-1); for (size_t i = 0; i < m_regions.size(); ++ i) @@ -427,39 +461,18 @@ void Print::add_model_object(ModelObject* model_object, int idx) object->config_apply(src_normalized, true); } - // update placeholders - { - // get the first input file name - std::string input_file; - std::vector v_scale; - for (const PrintObject *object : m_objects) { - const ModelObject &mobj = *object->model_object(); - v_scale.push_back(boost::lexical_cast(mobj.instances[0]->scaling_factor*100) + "%"); - if (input_file.empty()) - input_file = mobj.input_file; - } - - PlaceholderParser &pp = m_placeholder_parser; - pp.set("scale", v_scale); - if (! input_file.empty()) { - // get basename with and without suffix - const std::string input_basename = boost::filesystem::path(input_file).filename().string(); - pp.set("input_filename", input_basename); - const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); - pp.set("input_filename_base", input_basename_base); - } - } + this->update_object_placeholders(); } bool Print::apply_config(DynamicPrintConfig config) { - tbb::mutex::scoped_lock lock(m_mutex); + tbb::mutex::scoped_lock lock(this->state_mutex()); // we get a copy of the config object so we can modify it safely config.normalize(); // apply variables to placeholder parser - m_placeholder_parser.apply_config(config); + this->placeholder_parser().apply_config(config); // handle changes to print config t_config_option_keys print_diff = m_config.diff(config); @@ -474,14 +487,6 @@ bool Print::apply_config(DynamicPrintConfig config) PrintObjectConfig new_config = this->default_object_config(); // we override the new config with object-specific options normalize_and_apply_config(new_config, object->model_object()->config); - // Force a refresh of a variable layer height profile at the PrintObject if it is not valid. - if (! object->layer_height_profile_valid) { - // The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change). - // Invalidate the slicing step, which in turn invalidates everything. - object->invalidate_step(posSlice); - // Trigger recalculation. - invalidated = true; - } // check whether the new config is different from the current one t_config_option_keys diff = object->config().diff(new_config); object->config_apply_only(new_config, diff, true); @@ -510,12 +515,12 @@ bool Print::apply_config(DynamicPrintConfig config) // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. - if (! this_region_config.equals(this->_region_config_from_model_volume(volume))) { + if (! this_region_config.equals(region_config_from_model_volume(m_default_region_config, volume, 99999))) { rearrange_regions = true; goto exit_for_rearrange_regions; } } else { - this_region_config = this->_region_config_from_model_volume(volume); + this_region_config = region_config_from_model_volume(m_default_region_config, volume, 99999); this_region_config_set = true; } for (const PrintRegionConfig &cfg : other_region_configs) { @@ -553,7 +558,7 @@ exit_for_rearrange_regions: model_objects.reserve(m_objects.size()); for (PrintObject *object : m_objects) model_objects.push_back(object->model_object()); - this->clear_objects(); + this->clear(); for (ModelObject *mo : model_objects) this->add_model_object(mo); invalidated = true; @@ -561,12 +566,593 @@ exit_for_rearrange_regions: // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads. for (PrintObject *object : m_objects) - if (! object->layer_height_profile_valid) - object->update_layer_height_profile(); + object->update_layer_height_profile(); return invalidated; } +// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new +// in the exact order and with the same IDs. +// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. +void Print::model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) +{ + typedef std::pair ModelVolumeWithStatus; + std::vector old_volumes; + old_volumes.reserve(model_object_dst.volumes.size()); + for (const ModelVolume *model_volume : model_object_dst.volumes) + old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); + auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; + auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; + std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); + model_object_dst.volumes.clear(); + model_object_dst.volumes.reserve(model_object_new.volumes.size()); + for (const ModelVolume *model_volume_src : model_object_new.volumes) { + ModelVolumeWithStatus key(model_volume_src, false); + auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); + if (it != old_volumes.end() && model_volume_equal(*it, key)) { + // The volume was found in the old list. Just copy it. + assert(! it->second); // not consumed yet + it->second = true; + ModelVolume *model_volume_dst = const_cast(it->first); + assert(model_volume_dst->type() == model_volume_src->type()); + model_object_dst.volumes.emplace_back(model_volume_dst); + if (model_volume_dst->is_support_modifier()) + model_volume_dst->set_transformation(model_volume_src->get_transformation()); + assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); + } else { + // The volume was not found in the old list. Create a new copy. + assert(model_volume_src->is_support_modifier()); + model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); + model_object_dst.volumes.back()->set_model_object(&model_object_dst); + } + } + // Release the non-consumed old volumes (those were deleted from the new list). + for (ModelVolumeWithStatus &mv_with_status : old_volumes) + if (! mv_with_status.second) + delete mv_with_status.first; +} + +static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolume::Type type) +{ + size_t i_src, i_dst; + for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { + const ModelVolume &mv_src = *model_object_src.volumes[i_src]; + ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; + if (mv_src.type() != type) { + ++ i_src; + continue; + } + if (mv_dst.type() != type) { + ++ i_dst; + continue; + } + assert(mv_src.id() == mv_dst.id()); + // Copy the ModelVolume data. + mv_dst.name = mv_src.name; + mv_dst.config = mv_src.config; + //FIXME what to do with the materials? + // mv_dst.m_material_id = mv_src.m_material_id; + ++ i_src; + ++ i_dst; + } +} + +static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { + if (*lv < *rv) + return true; + else if (*lv > *rv) + return false; + } + return false; +} + +static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) + if (*lv != *rv) + return false; + return true; +} + +struct PrintInstances +{ + Transform3d trafo; + Points copies; + bool operator<(const PrintInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } +}; + +// Generate a list of trafos and XY offsets for instances of a ModelObject +static std::vector print_objects_from_model_object(const ModelObject &model_object) +{ + std::set trafos; + PrintInstances trafo; + trafo.copies.assign(1, Point()); + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + trafo.trafo = model_instance->get_matrix(); + // Set the Z axis of the transformation. + trafo.copies.front() = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); + trafo.trafo.data()[12] = 0; + trafo.trafo.data()[13] = 0; + auto it = trafos.find(trafo); + if (it == trafos.end()) + trafos.emplace(trafo); + else + const_cast(*it).copies.emplace_back(trafo.copies.front()); + } + return std::vector(trafos.begin(), trafos.end()); +} + +Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) +{ +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Make a copy of the config, normalize it. + DynamicPrintConfig config(config_in); + config.normalize(); + // Collect changes to print config. + t_config_option_keys print_diff = m_config.diff(config); + t_config_option_keys object_diff = m_default_object_config.diff(config); + t_config_option_keys region_diff = m_default_region_config.diff(config); + t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) + update_apply_status(false); + + // Grab the lock for the Print / PrintObject milestones. + tbb::mutex::scoped_lock lock(this->state_mutex()); + + // The following call may stop the background processing. + if (! print_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(print_diff)); + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, + // which should be stopped if print_diff is not empty. + if (! placeholder_parser_diff.empty()) { + update_apply_status(this->invalidate_step(psGCodeExport)); + PlaceholderParser &pp = this->placeholder_parser(); + pp.apply_only(config, placeholder_parser_diff); + // Set the profile aliases for the PrintBase::output_filename() + pp.set("print_preset", config_in.option("print_settings_id" )->clone()); + pp.set("filament_preset", config_in.option("filament_settings_id")->clone()); + pp.set("printer_preset", config_in.option("printer_settings_id" )->clone()); + } + + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(config, print_diff, true); + // Handle changes to object config defaults + m_default_object_config.apply_only(config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(config, region_diff, true); + + struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} + ModelID id; + Status status; + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } + }; + std::set model_object_status; + + // 1) Synchronize model objects. + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // Stop background processing. + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + for (PrintObject *object : m_objects) { + model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + delete object; + } + m_objects.clear(); + for (PrintRegion *region : m_regions) + delete region; + m_regions.clear(); + m_model.assign_copy(model); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::New); + } else { + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(psGCodeExport)); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); + m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancell_callback(); + update_apply_status(this->invalidate_step(psGCodeExport)); + // Second create a new list of objects. + std::vector model_objects_old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&m_model); + model_object_status.emplace(mobj->id(), ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status.emplace(mobj->id(), ModelObjectStatus::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) { + if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { + model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); + deleted_any = true; + } else + // Do not delete this ModelObject instance. + model_object = nullptr; + } + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + std::vector print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (PrintObject *print_object : print_objects_old) { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + if (it_status->status == ModelObjectStatus::Deleted) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; + } + } + } + + // 2) Map print objects including their transformation matrices. + struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + // ID of the ModelObject & PrintObject + ModelID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } + }; + std::multiset print_object_status; + for (PrintObject *print_object : m_objects) + print_object_status.emplace(PrintObjectStatus(print_object)); + + // 3) Synchronize ModelObjects & PrintObjects. + size_t num_extruders = m_config.nozzle_diameter.size(); + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + if (it_status->status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + // Check whether a model part volume was added or removed, their transformations or order changed. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART); + bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::PARAMETER_MODIFIER); + bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_BLOCKER); + bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_ENFORCER); + if (model_parts_differ || modifiers_differ || + model_object.origin_translation != model_object_new.origin_translation || + model_object.layer_height_ranges != model_object_new.layer_height_ranges || + model_object.layer_height_profile != model_object_new.layer_height_profile || + model_object.layer_height_profile_valid != model_object_new.layer_height_profile_valid) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) { + update_apply_status(it->print_object->invalidate_all_steps()); + const_cast(*it).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else if (support_blockers_differ || support_enforcers_differ) { + // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. + this->call_cancell_callback(); + update_apply_status(false); + // Invalidate just the supports step. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) + update_apply_status(it->print_object->invalidate_step(posSupportMaterial)); + // Copy just the support volumes. + model_volume_list_update_supports(model_object, model_object_new); + } + if (! model_parts_differ && ! modifiers_differ) { + // Synchronize Object's config. + bool object_config_changed = model_object.config != model_object_new.config; + if (object_config_changed) + model_object.config = model_object_new.config; + if (! object_diff.empty() || object_config_changed) { + PrintObjectConfig new_config = object_config_from_model(m_default_object_config, model_object, num_extruders); + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) { + t_config_option_keys diff = it->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it->print_object->invalidate_state_by_config_options(diff)); + it->print_object->config_apply_only(new_config, diff, true); + } + } + } + // Synchronize (just copy) the remaining data of ModelVolumes (name, config). + //FIXME What to do with m_material_id? + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::MODEL_PART); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::PARAMETER_MODIFIER); + // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } + } + + // 4) Generate PrintObjects from ModelObjects and their instances. + { + std::vector print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + // Walk over all new model objects and check, whether there are matching PrintObjects. + for (ModelObject *model_object : m_model.objects) { + auto range = print_object_status.equal_range(PrintObjectStatus(model_object->id())); + std::vector old; + if (range.first != range.second) { + old.reserve(print_object_status.count(PrintObjectStatus(model_object->id()))); + for (auto it = range.first; it != range.second; ++ it) + if (it->status != PrintObjectStatus::Deleted) + old.emplace_back(&(*it)); + } + // Generate a list of trafos and XY offsets for instances of a ModelObject + PrintObjectConfig config = object_config_from_model(m_default_object_config, *model_object, num_extruders); + std::vector new_print_instances = print_objects_from_model_object(*model_object); + if (old.empty()) { + // Simple case, just generate new instances. + for (const PrintInstances &print_instances : new_print_instances) { + PrintObject *print_object = new PrintObject(this, model_object, false); + print_object->set_trafo(print_instances.trafo); + print_object->set_copies(print_instances.copies); + print_object->config_apply(config); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + } + continue; + } + // Complex case, try to merge the two lists. + // Sort the old lexicographically by their trafos. + std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); + // Merge the old / new lists. + auto it_old = old.begin(); + for (const PrintInstances &new_instances : new_print_instances) { + for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); + if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { + // This is a new instance (or a set of instances with the same trafo). Just add it. + PrintObject *print_object = new PrintObject(this, model_object, false); + print_object->set_trafo(new_instances.trafo); + print_object->set_copies(new_instances.copies); + print_object->config_apply(config); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + if (it_old != old.end()) + const_cast(*it_old)->status = PrintObjectStatus::Deleted; + } else { + // The PrintObject already exists and the copies differ. + if ((*it_old)->print_object->copies().size() != new_instances.copies.size()) + update_apply_status(this->invalidate_step(psWipeTower)); + if ((*it_old)->print_object->set_copies(new_instances.copies)) { + // Invalidated + update_apply_status(this->invalidate_steps({ psSkirt, psBrim, psGCodeExport })); + } + print_objects_new.emplace_back((*it_old)->print_object); + const_cast(*it_old)->status = PrintObjectStatus::Reused; + } + } + } + if (m_objects != print_objects_new) { + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (auto &pos : print_object_status) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + // update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + if (new_objects || deleted_objects) + update_apply_status(this->invalidate_steps({ psSkirt, psBrim, psWipeTower, psGCodeExport })); + update_apply_status(new_objects); + } + print_object_status.clear(); + } + + // 5) Synchronize configs of ModelVolumes, synchronize AMF / 3MF materials (and their configs), refresh PrintRegions. + // Update reference counts of regions from the remaining PrintObjects and their volumes. + // Regions with zero references could and should be reused. + for (PrintRegion *region : m_regions) + region->m_refcnt = 0; + for (PrintObject *print_object : m_objects) { + int idx_region = 0; + for (const auto &volumes : print_object->region_volumes) { + if (! volumes.empty()) + ++ m_regions[idx_region]->m_refcnt; + ++ idx_region; + } + } + + // All regions now have distinct settings. + // Check whether applying the new region config defaults we'd get different regions. + for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { + PrintRegion ®ion = *m_regions[region_id]; + PrintRegionConfig this_region_config; + bool this_region_config_set = false; + for (PrintObject *print_object : m_objects) { + if (region_id < print_object->region_volumes.size()) { + for (int volume_id : print_object->region_volumes[region_id]) { + const ModelVolume &volume = *print_object->model_object()->volumes[volume_id]; + if (this_region_config_set) { + // If the new config for this volume differs from the other + // volume configs currently associated to this region, it means + // the region subdivision does not make sense anymore. + if (! this_region_config.equals(region_config_from_model_volume(m_default_region_config, volume, num_extruders))) + // Regions were split. Reset this print_object. + goto print_object_end; + } else { + this_region_config = region_config_from_model_volume(m_default_region_config, volume, num_extruders); + for (size_t i = 0; i < region_id; ++ i) + if (m_regions[i]->config().equals(this_region_config)) + // Regions were merged. Reset this print_object. + goto print_object_end; + this_region_config_set = true; + } + } + } + continue; + print_object_end: + update_apply_status(print_object->invalidate_all_steps()); + // Decrease the references to regions from this volume. + int ireg = 0; + for (const std::vector &volumes : print_object->region_volumes) { + if (! volumes.empty()) + -- m_regions[ireg]->m_refcnt; + ++ ireg; + } + print_object->region_volumes.clear(); + } + if (this_region_config_set) { + t_config_option_keys diff = region.config().diff(this_region_config); + if (! diff.empty()) { + region.config_apply_only(this_region_config, diff, false); + for (PrintObject *print_object : m_objects) + if (region_id < print_object->region_volumes.size() && ! print_object->region_volumes[region_id].empty()) + update_apply_status(print_object->invalidate_state_by_config_options(diff)); + } + } + } + + // Possibly add new regions for the newly added or resetted PrintObjects. + for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { + PrintObject &print_object0 = *m_objects[idx_print_object]; + const ModelObject &model_object = *print_object0.model_object(); + std::vector map_volume_to_region(model_object.volumes.size(), -1); + for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { + PrintObject &print_object = *m_objects[i]; + bool fresh = print_object.region_volumes.empty(); + unsigned int volume_id = 0; + for (const ModelVolume *volume : model_object.volumes) { + if (! volume->is_model_part() && ! volume->is_modifier()) + continue; + int region_id = -1; + if (&print_object == &print_object0) { + // Get the config applied to this volume. + PrintRegionConfig config = region_config_from_model_volume(m_default_region_config, *volume, num_extruders); + // Find an existing print region with the same config. + int idx_empty_slot = -1; + for (int i = 0; i < (int)m_regions.size(); ++ i) { + if (m_regions[i]->m_refcnt == 0) + idx_empty_slot = i; + else if (config.equals(m_regions[i]->config())) { + region_id = i; + break; + } + } + // If no region exists with the same config, create a new one. + if (region_id == -1) { + if (idx_empty_slot == -1) { + region_id = (int)m_regions.size(); + this->add_region(config); + } else { + region_id = idx_empty_slot; + m_regions[region_id]->set_config(std::move(config)); + } + } + map_volume_to_region[volume_id] = region_id; + } else + region_id = map_volume_to_region[volume_id]; + // Assign volume to a region. + if (fresh) { + if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) + ++ m_regions[region_id]->m_refcnt; + print_object.add_region_volume(region_id, volume_id); + } + ++ volume_id; + } + } + } + + // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads. + for (PrintObject *object : m_objects) + if (! object->layer_height_profile_valid) + // No need to call the next line as the step should already be invalidated above. + // update_apply_status(object->invalidate_step(posSlice)); + object->update_layer_height_profile(); + + //FIXME there may be a race condition with the G-code export running at the background thread. + this->update_object_placeholders(); + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); +} + bool Print::has_infinite_skirt() const { return (m_config.skirt_height == -1 && m_config.skirts > 0) @@ -579,25 +1165,10 @@ bool Print::has_skirt() const || this->has_infinite_skirt(); } +// Precondition: Print::validate() requires the Print::apply() to be called its invocation. std::string Print::validate() const { - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(m_config.bed_shape.values)); - BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(m_config.max_print_height))); - // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. - print_volume.min(2) = -1e10; - unsigned int printable_count = 0; - { - // Lock due to the po->reload_model_instances() - tbb::mutex::scoped_lock lock(m_mutex); - for (PrintObject *po : m_objects) { - po->model_object()->check_instances_print_volume_state(print_volume); - po->reload_model_instances(); - if (po->is_printable()) - ++ printable_count; - } - } - - if (printable_count == 0) + if (m_objects.empty()) return L("All objects are outside of the print volume."); if (m_config.complete_objects) { @@ -661,8 +1232,8 @@ std::string Print::validate() const } if (this->has_wipe_tower() && ! m_objects.empty()) { - if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin) - return L("The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors."); + if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) + return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters(); @@ -683,12 +1254,10 @@ std::string Print::validate() const return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); if (! equal_layering(slicing_params, slicing_params0)) return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); - bool was_layer_height_profile_valid = object->layer_height_profile_valid; - object->update_layer_height_profile(); - object->layer_height_profile_valid = was_layer_height_profile_valid; if ( m_config.variable_layer_height ) { // comparing layer height profiles bool failed = false; + // layer_height_profile should be set by Print::apply(). if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) { int i = 0; while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) { @@ -721,10 +1290,15 @@ std::string Print::validate() const for (unsigned int extruder_id : extruders) nozzle_diameters.push_back(m_config.nozzle_diameter.get_at(extruder_id)); double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); + +#if 0 + // We currently allow one to assign extruders with a higher index than the number + // of physical extruders the machine is equipped with, as the Printer::apply() clamps them. unsigned int total_extruders_count = m_config.nozzle_diameter.size(); for (const auto& extruder_idx : extruders) if ( extruder_idx >= total_extruders_count ) return L("One or more object were assigned an extruder that the printer does not have."); +#endif for (PrintObject *object : m_objects) { if ((object->config().support_material_extruder == -1 || object->config().support_material_interface_extruder == -1) && @@ -871,16 +1445,6 @@ Flow Print::skirt_flow() const ); } -PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume) -{ - PrintRegionConfig config = this->default_region_config(); - normalize_and_apply_config(config, volume.get_object()->config); - normalize_and_apply_config(config, volume.config); - if (! volume.material_id().empty()) - normalize_and_apply_config(config, volume.material()->config); - return config; -} - bool Print::has_support_material() const { for (const PrintObject *object : m_objects) @@ -912,16 +1476,12 @@ void Print::process() BOOST_LOG_TRIVIAL(info) << "Staring the slicing process."; for (PrintObject *obj : m_objects) obj->make_perimeters(); - this->throw_if_canceled(); this->set_status(70, "Infilling layers"); for (PrintObject *obj : m_objects) obj->infill(); - this->throw_if_canceled(); for (PrintObject *obj : m_objects) obj->generate_support_material(); - this->throw_if_canceled(); - if (! m_state.is_done(psSkirt)) { - this->set_started(psSkirt); + if (this->set_started(psSkirt)) { m_skirt.clear(); if (this->has_skirt()) { this->set_status(88, "Generating skirt"); @@ -929,9 +1489,7 @@ void Print::process() } this->set_done(psSkirt); } - this->throw_if_canceled(); - if (! m_state.is_done(psBrim)) { - this->set_started(psBrim); + if (this->set_started(psBrim)) { m_brim.clear(); if (m_config.brim_width > 0) { this->set_status(88, "Generating brim"); @@ -939,9 +1497,7 @@ void Print::process() } this->set_done(psBrim); } - this->throw_if_canceled(); - if (! m_state.is_done(psWipeTower)) { - this->set_started(psWipeTower); + if (this->set_started(psWipeTower)) { m_wipe_tower_data.clear(); if (this->has_wipe_tower()) { //this->set_status(95, "Generating wipe tower"); @@ -958,9 +1514,6 @@ void Print::process() // It is up to the caller to show an error message. void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) { - // prerequisites - this->process(); - // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. std::string path = this->output_filepath(path_template); @@ -989,8 +1542,7 @@ void Print::_make_skirt() // prepended to the first 'n' layers (with 'n' = skirt_height). // $skirt_height_z in this case is the highest possible skirt height for safety. coordf_t skirt_height_z = 0.; - PrintObjectPtrs printable_objects = get_printable_objects(); - for (const PrintObject *object : printable_objects) { + for (const PrintObject *object : m_objects) { size_t skirt_layers = this->has_infinite_skirt() ? object->layer_count() : std::min(size_t(m_config.skirt_height.value), object->layer_count()); @@ -999,7 +1551,7 @@ void Print::_make_skirt() // Collect points from all layers contained in skirt height. Points points; - for (const PrintObject *object : printable_objects) { + for (const PrintObject *object : m_objects) { Points object_points; // Get object layers up to skirt_height_z. for (const Layer *layer : object->m_layers) { @@ -1114,8 +1666,7 @@ void Print::_make_brim() // Brim is only printed on first layer and uses perimeter extruder. Flow flow = this->brim_flow(); Polygons islands; - PrintObjectPtrs printable_objects = get_printable_objects(); - for (PrintObject *object : printable_objects) { + for (PrintObject *object : m_objects) { Polygons object_islands; for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons) object_islands.push_back(expoly.contour); @@ -1221,7 +1772,8 @@ void Print::_make_wipe_tower() float(m_config.wipe_tower_width.value), float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), wipe_volumes, + float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), + m_config.high_current_on_filament_swap.value, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder(), float(m_config.first_layer_extrusion_width)); //wipe_tower.set_retract(); @@ -1308,60 +1860,6 @@ void Print::_make_wipe_tower() m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); } -std::string Print::output_filename() const -{ - DynamicConfig cfg_timestamp; - PlaceholderParser::update_timestamp(cfg_timestamp); - try { - return this->placeholder_parser().process(m_config.output_filename_format.value, 0, &cfg_timestamp); - } catch (std::runtime_error &err) { - throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); - } -} - -std::string Print::output_filepath(const std::string &path) const -{ - // if we were supplied no path, generate an automatic one based on our first object's input file - if (path.empty()) { - // get the first input file name - std::string input_file; - for (const PrintObject *object : m_objects) { - input_file = object->model_object()->input_file; - if (! input_file.empty()) - break; - } - return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); - } - - // if we were supplied a directory, use it and append our automatically generated filename - boost::filesystem::path p(path); - if (boost::filesystem::is_directory(p)) - return (p / this->output_filename()).make_preferred().string(); - - // if we were supplied a file which is not a directory, use it - return path; -} - -void Print::export_png(const std::string &dirpath) -{ -// size_t idx = 0; -// for (PrintObject *obj : m_objects) { -// obj->slice(); -// this->set_status(int(floor(idx * 100. / m_objects.size() + 0.5)), "Slicing..."); -// ++ idx; -// } -// this->set_status(90, "Exporting zipped archive..."); -// print_to(*this, -// dirpath, -// float(m_config.bed_size_x.value), -// float(m_config.bed_size_y.value), -// int(m_config.pixel_width.value), -// int(m_config.pixel_height.value), -// float(m_config.exp_time.value), -// float(m_config.exp_time_first.value)); -// this->set_status(100, "Done."); -} - // Returns extruder this eec should be printed with, according to PrintRegion config int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion) { @@ -1369,5 +1867,96 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion std::max(region.config().perimeter_extruder.value - 1, 0); } -} // namespace Slic3r +std::string Print::output_filename() const +{ + // Set the placeholders for the data know first after the G-code export is finished. + // These values will be just propagated into the output file name. + DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); + return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); +} +// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes +// and removing spaces. +static std::string short_time(const std::string &time) +{ + // Parse the dhms time format. + int days = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + if (time.find('d') != std::string::npos) + ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); + else if (time.find('h') != std::string::npos) + ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); + else if (time.find('m') != std::string::npos) + ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); + else if (time.find('s') != std::string::npos) + ::sscanf(time.c_str(), "%ds", &seconds); + // Round to full minutes. + if (days + hours + minutes > 0 && seconds >= 30) { + if (++ minutes == 60) { + minutes = 0; + if (++ hours == 24) { + hours = 0; + ++ days; + } + } + } + // Format the dhm time. + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); + else if (hours > 0) + ::sprintf(buffer, "%dh%dm", hours, minutes); + else if (minutes > 0) + ::sprintf(buffer, "%dm", minutes); + else + ::sprintf(buffer, "%ds", seconds); + return buffer; +} + +DynamicConfig PrintStatistics::config() const +{ + DynamicConfig config; + std::string normal_print_time = short_time(this->estimated_normal_print_time); + std::string silent_print_time = short_time(this->estimated_silent_print_time); + config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); + config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament)); + config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume)); + config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost)); + config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight)); + config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost)); + config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament)); + return config; +} + +DynamicConfig PrintStatistics::placeholders() +{ + DynamicConfig config; + for (const std::string &key : { + "print_time", "normal_print_time", "silent_print_time", + "used_filament", "extruded_volume", "total_cost", "total_weight", + "total_wipe_tower_cost", "total_wipe_tower_filament"}) + config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); + return config; +} + +std::string PrintStatistics::finalize_output_path(const std::string &path_in) const +{ + std::string final_path; + try { + boost::filesystem::path path(path_in); + DynamicConfig cfg = this->config(); + PlaceholderParser pp; + std::string new_stem = pp.process(path.stem().string(), 0, &cfg); + final_path = (path.parent_path() / (new_stem + path.extension().string())).string(); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); + final_path = path_in; + } + return final_path; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7f88110bc..62c42237c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1,30 +1,17 @@ #ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ -#include "libslic3r.h" -#include -#include -#include -#include -#include +#include "PrintBase.hpp" + #include "BoundingBox.hpp" #include "Flow.hpp" -#include "PrintConfig.hpp" #include "Point.hpp" #include "Layer.hpp" #include "Model.hpp" -#include "PlaceholderParser.hpp" #include "Slicing.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" -#include "tbb/atomic.h" -// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros. -#ifndef NOMINMAX - #define NOMINMAX -#endif -#include "tbb/mutex.h" - namespace Slic3r { class Print; @@ -42,89 +29,6 @@ enum PrintObjectStep { posInfill, posSupportMaterial, posCount, }; -class CanceledException : public std::exception { -public: - const char* what() const throw() { return "Background processing has been canceled"; } -}; - -// To be instantiated over PrintStep or PrintObjectStep enums. -template -class PrintState -{ -public: - PrintState() { for (size_t i = 0; i < COUNT; ++ i) m_state[i].store(INVALID, std::memory_order_relaxed); } - - enum State { - INVALID, - STARTED, - DONE, - }; - - // With full memory barrier. - bool is_done(StepType step) const { return m_state[step] == DONE; } - - // Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being - // modified by the UI thread. - // This is necessary to block until the Print::apply_config() updates its state, which may - // influence the processing step being entered. - void set_started(StepType step, tbb::mutex &mtx) { - mtx.lock(); - m_state[step].store(STARTED, std::memory_order_relaxed); - mtx.unlock(); - } - - // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being - // modified by the UI thread. - void set_done(StepType step, tbb::mutex &mtx) { - mtx.lock(); - m_state[step].store(DONE, std::memory_order_relaxed); - mtx.unlock(); - } - - // Make the step invalid. - // The provided mutex should be locked at this point, guarding access to m_state. - // In case the step has already been entered or finished, cancel the background - // processing by calling the cancel callback. - template - bool invalidate(StepType step, tbb::mutex &mtx, CancelationCallback &cancel) { - bool invalidated = m_state[step].load(std::memory_order_relaxed) != INVALID; - if (invalidated) { -#if 0 - if (mtx.state != mtx.HELD) { - printf("Not held!\n"); - } -#endif - mtx.unlock(); - cancel(); - mtx.lock(); - } - return invalidated; - } - - // Make all steps invalid. - // The provided mutex should be locked at this point, guarding access to m_state. - // In case any step has already been entered or finished, cancel the background - // processing by calling the cancel callback. - template - bool invalidate_all(tbb::mutex &mtx, CancelationCallback &cancel) { - bool invalidated = false; - for (size_t i = 0; i < COUNT; ++ i) - if (m_state[i].load(std::memory_order_relaxed) != INVALID) { - if (! invalidated) { - mtx.unlock(); - cancel(); - mtx.lock(); - invalidated = true; - } - m_state[i].store(INVALID, std::memory_order_relaxed); - } - return invalidated; - } - -private: - std::atomic m_state[COUNT]; -}; - // A PrintRegion object represents a group of volumes to print // sharing the same config (including the same assigned extruder(s)) class PrintRegion @@ -139,19 +43,25 @@ public: // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. - coordf_t bridging_height_avg(const PrintConfig &print_config) const; + coordf_t bridging_height_avg(const PrintConfig &print_config) const; // Methods modifying the PrintRegion's state: public: Print* print() { return m_print; } - void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } + void set_config(const PrintRegionConfig &config) { m_config = config; } + void set_config(PrintRegionConfig &&config) { m_config = std::move(config); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) + { this->m_config.apply_only(other, keys, ignore_nonexistent); } + +protected: + size_t m_refcnt; private: Print *m_print; PrintRegionConfig m_config; - PrintRegion(Print* print) : m_print(print) {} - PrintRegion(Print* print, const PrintRegionConfig &config) : m_print(print), m_config(config) {} + PrintRegion(Print* print) : m_refcnt(0), m_print(print) {} + PrintRegion(Print* print, const PrintRegionConfig &config) : m_refcnt(0), m_print(print), m_config(config) {} ~PrintRegion() {} }; @@ -160,14 +70,14 @@ typedef std::vector LayerPtrs; typedef std::vector SupportLayerPtrs; class BoundingBoxf3; // TODO: for temporary constructor parameter -class PrintObject +class PrintObject : public PrintObjectBaseWithState { - friend class Print; +private: // Prevents erroneous use by other classes. + typedef PrintObjectBaseWithState Inherited; public: // vector of (vectors of volume ids), indexed by region_id std::vector> region_volumes; - t_layer_height_ranges layer_height_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array to simplify handling by the Perl XS. @@ -185,22 +95,12 @@ public: Vec3crd size; // XYZ in scaled coordinates - Print* print() { return m_print; } - const Print* print() const { return m_print; } - ModelObject* model_object() { return m_model_object; } - const ModelObject* model_object() const { return m_model_object; } const PrintObjectConfig& config() const { return m_config; } - void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } - void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } const LayerPtrs& layers() const { return m_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; } + const Transform3d& trafo() const { return m_trafo; } + const Points& copies() const { return m_copies; } - const Points& copies() const { return m_copies; } - bool add_copy(const Vec2d &point); - bool delete_last_copy(); - bool delete_all_copies() { return this->set_copies(Points()); } - bool set_copies(const Points &points); - bool reload_model_instances(); // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } @@ -229,12 +129,6 @@ public: SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); - // methods for handling state - bool invalidate_state_by_config_options(const std::vector &opt_keys); - bool invalidate_step(PrintObjectStep step); - bool invalidate_all_steps(); - bool is_step_done(PrintObjectStep step) const { return m_state.is_done(step); } - // To be used over the layer_height_profile of both the PrintObject and ModelObject // to initialize the height profile with the height ranges. bool update_layer_height_profile(std::vector &layer_height_profile) const; @@ -260,6 +154,24 @@ public: std::vector slice_support_enforcers() const; std::vector slice_support_blockers() const; +protected: + // to be called from Print only. + friend class Print; + + PrintObject(Print* print, ModelObject* model_object, bool add_instances = true); + ~PrintObject() {} + + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } + void set_trafo(const Transform3d& trafo) { m_trafo = trafo; } + bool set_copies(const Points &points); + // Invalidates the step, and its depending steps in PrintObject and Print. + bool invalidate_step(PrintObjectStep step); + // Invalidates all PrintObject and Print steps. + bool invalidate_all_steps(); + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); + private: void make_perimeters(); void prepare_infill(); @@ -267,6 +179,7 @@ private: void generate_support_material(); void _slice(); + void _offsetHoles(float hole_delta, LayerRegion *layerm); std::string _fix_slicing_errors(); void _simplify_slices(double distance); void _make_perimeters(); @@ -275,16 +188,16 @@ private: void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); + void replaceSurfaceType(SurfaceType st_to_replace, SurfaceType st_replacement, SurfaceType st_under_it); void clip_fill_surfaces(); + void count_distance_solid(); void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - bool is_printable() const { return ! m_copies.empty(); } - - Print *m_print; - ModelObject *m_model_object; PrintObjectConfig m_config; + // Translation in Z + Rotation + Scaling / Mirroring. + Transform3d m_trafo = Transform3d::Identity(); // Slic3r::Point objects in scaled G-code coordinates Points m_copies; // scaled coordinates to add to copies (to compensate for the alignment @@ -295,15 +208,6 @@ private: LayerPtrs m_layers; SupportLayerPtrs m_support_layers; - PrintState m_state; - - // TODO: call model_object->get_bounding_box() instead of accepting - // parameter - PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); - ~PrintObject() {} - - void set_started(PrintObjectStep step); - void set_done(PrintObjectStep step); std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; }; @@ -348,6 +252,13 @@ struct PrintStatistics double total_wipe_tower_filament; std::map filament_stats; + // Config with the filled in print statistics. + DynamicConfig config() const; + // Config with the statistics keys populated with placeholder strings. + static DynamicConfig placeholders(); + // Replace the print statistics placeholders in the path. + std::string finalize_output_path(const std::string &path_in) const; + void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); @@ -365,39 +276,48 @@ typedef std::vector PrintObjectPtrs; typedef std::vector PrintRegionPtrs; // The complete print tray with possibly multiple objects. -class Print +class Print : public PrintBaseWithState { +private: // Prevents erroneous use by other classes. + typedef PrintBaseWithState Inherited; + public: - Print() { restart(); } - ~Print() { clear_objects(); } + Print() {} + virtual ~Print() { this->clear(); } + + PrinterTechnology technology() const noexcept { return ptFFF; } // Methods, which change the state of Print / PrintObject / PrintRegion. // The following methods are synchronized with process() and export_gcode(), // so that process() and export_gcode() may be called from a background thread. // In case the following methods need to modify data processed by process() or export_gcode(), // a cancellation callback is executed to stop the background processing before the operation. - void clear_objects(); - void delete_object(size_t idx); + void clear() override; + bool empty() const override { return m_objects.empty(); } + + ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + + // The following three methods are used by the Perl tests only. Get rid of them! void reload_object(size_t idx); - bool reload_model_instances(); void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); - void process(); + + void process() override; void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); - // SLA export, temporary. - void export_png(const std::string &dirpath); // methods for handling state - bool is_step_done(PrintStep step) const { return m_state.is_done(step); } + bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } + // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(PrintObjectStep step) const; + // Returns true if the last step was finished with success. + bool finished() const override { return this->is_step_done(psGCodeExport); } bool has_infinite_skirt() const; bool has_skirt() const; - PrintObjectPtrs get_printable_objects() const; float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; } // Returns an empty string if valid, otherwise returns an error message. - std::string validate() const; + std::string validate() const override; BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; @@ -416,12 +336,15 @@ public: const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } const PrintObjectPtrs& objects() const { return m_objects; } - const PrintObject* get_object(int idx) const { return m_objects[idx]; } + PrintObject* get_object(size_t idx) { return m_objects[idx]; } + const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } const PrintRegionPtrs& regions() const { return m_regions; } - const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } + // How many of PrintObject::copies() over all print objects are there? + // If zero, then the print is empty and the print shall not be executed. + unsigned int num_object_instances() const; // Returns extruder this eec should be printed with, according to PrintRegion config: - static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion); + static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion); const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } @@ -432,81 +355,36 @@ public: bool has_wipe_tower() const; const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; } - std::string output_filename() const; - std::string output_filepath(const std::string &path) const; - - typedef std::function status_callback_type; - // Default status console print out in the form of percent => message. - void set_status_default() { m_status_callback = nullptr; } - // No status output or callback whatsoever, useful mostly for automatic tests. - void set_status_silent() { m_status_callback = [](int, const std::string&){}; } - // Register a custom status callback. - void set_status_callback(status_callback_type cb) { m_status_callback = cb; } - // Calls a registered callback to update the status, or print out the default message. - void set_status(int percent, const std::string &message) { - if (m_status_callback) m_status_callback(percent, message); - else printf("%d => %s\n", percent, message.c_str()); - } - - typedef std::function cancel_callback_type; - // Various methods will call this callback to stop the background processing (the Print::process() call) - // in case a successive change of the Print / PrintObject / PrintRegion instances changed - // the state of the finished or running calculations. - void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; } - // Has the calculation been canceled? - bool canceled() const { return m_canceled; } - // Cancel the running computation. Stop execution of all the background threads. - void cancel() { m_canceled = true; } - // Cancel the running computation. Stop execution of all the background threads. - void restart() { m_canceled = false; } + std::string output_filename() const override; // Accessed by SupportMaterial const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; } protected: - void set_started(PrintStep step) { m_state.set_started(step, m_mutex); throw_if_canceled(); } - void set_done(PrintStep step) { m_state.set_done(step, m_mutex); throw_if_canceled(); } - bool invalidate_step(PrintStep step); - bool invalidate_all_steps() { return m_state.invalidate_all(m_mutex, m_cancel_callback); } - // methods for handling regions PrintRegion* get_region(size_t idx) { return m_regions[idx]; } PrintRegion* add_region(); PrintRegion* add_region(const PrintRegionConfig &config); + // Invalidates the step, and its depending steps in Print. + bool invalidate_step(PrintStep step); + private: bool invalidate_state_by_config_options(const std::vector &opt_keys); - PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); - - // If the background processing stop was requested, throw CanceledException. - // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. - void throw_if_canceled() const { if (m_canceled) throw CanceledException(); } void _make_skirt(); void _make_brim(); void _make_wipe_tower(); void _simplify_slices(double distance); - PrintState m_state; - // Mutex used for synchronization of the worker thread with the UI thread: - // The mutex will be used to guard the worker thread against entering a stage - // while the data influencing the stage is modified. - mutable tbb::mutex m_mutex; - - // Has the calculation been canceled? - tbb::atomic m_canceled; - // Callback to be evoked regularly to update state of the UI thread. - status_callback_type m_status_callback; - - // Callback to be evoked to stop the background processing before a state is updated. - cancel_callback_type m_cancel_callback = [](){}; + // Declared here to have access to Model / ModelObject / ModelInstance + static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); PrintConfig m_config; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; PrintObjectPtrs m_objects; PrintRegionPtrs m_regions; - PlaceholderParser m_placeholder_parser; // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; @@ -524,12 +402,6 @@ private: friend class PrintObject; }; - -#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator) -#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->m_objects, object) -#define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->m_layers, layer) -#define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->m_regions, layerm) - -} +} /* slic3r_Print_hpp_ */ #endif diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp new file mode 100644 index 000000000..1d078da30 --- /dev/null +++ b/src/libslic3r/PrintBase.cpp @@ -0,0 +1,104 @@ +#include "PrintBase.hpp" + +#include +#include + +#include "I18N.hpp" + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + +namespace Slic3r +{ + +size_t PrintStateBase::g_last_timestamp = 0; + +// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects. +void PrintBase::update_object_placeholders() +{ + // get the first input file name + std::string input_file; + std::vector v_scale; + for (const ModelObject *model_object : m_model.objects) { + ModelInstance *printable = nullptr; + for (ModelInstance *model_instance : model_object->instances) + if (model_instance->is_printable()) { + printable = model_instance; + break; + } + if (printable) { + // CHECK_ME -> Is the following correct ? + v_scale.push_back("x:" + boost::lexical_cast(printable->get_scaling_factor(X) * 100) + + "% y:" + boost::lexical_cast(printable->get_scaling_factor(Y) * 100) + + "% z:" + boost::lexical_cast(printable->get_scaling_factor(Z) * 100) + "%"); + if (input_file.empty()) + input_file = model_object->input_file; + } + } + + PlaceholderParser &pp = m_placeholder_parser; + pp.set("scale", v_scale); + if (! input_file.empty()) { + // get basename with and without suffix + const std::string input_basename = boost::filesystem::path(input_file).filename().string(); + pp.set("input_filename", input_basename); + const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); + pp.set("input_filename_base", input_basename_base); + } +} + +std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const +{ + DynamicConfig cfg; + if (config_override != nullptr) + cfg = *config_override; + PlaceholderParser::update_timestamp(cfg); + try { + boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg); + if (filename.extension().empty()) + filename = boost::filesystem::change_extension(filename, default_ext); + return filename.string(); + } catch (std::runtime_error &err) { + throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); + } +} + +std::string PrintBase::output_filepath(const std::string &path) const +{ + // if we were supplied no path, generate an automatic one based on our first object's input file + if (path.empty()) { + // get the first input file name + std::string input_file; + for (const ModelObject *model_object : m_model.objects) { + for (ModelInstance *model_instance : model_object->instances) + if (model_instance->is_printable()) { + input_file = model_object->input_file; + break; + } + if (! input_file.empty()) + break; + } + return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); + } + + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path p(path); + if (boost::filesystem::is_directory(p)) + return (p / this->output_filename()).make_preferred().string(); + + // if we were supplied a file which is not a directory, use it + return path; +} + +tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) +{ + return print->state_mutex(); +} + +std::function PrintObjectBase::cancel_callback(PrintBase *print) +{ + return print->cancel_callback(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp new file mode 100644 index 000000000..1a61921d6 --- /dev/null +++ b/src/libslic3r/PrintBase.hpp @@ -0,0 +1,399 @@ +#ifndef slic3r_PrintBase_hpp_ +#define slic3r_PrintBase_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include + +// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros. +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include "tbb/mutex.h" + +#include "Model.hpp" +#include "PlaceholderParser.hpp" +#include "PrintConfig.hpp" + +namespace Slic3r { + +class CanceledException : public std::exception { +public: + const char* what() const throw() { return "Background processing has been canceled"; } +}; + +class PrintStateBase { +public: + enum State { + INVALID, + STARTED, + DONE, + }; + + typedef size_t TimeStamp; + + // A new unique timestamp is being assigned to the step every time the step changes its state. + struct StateWithTimeStamp + { + StateWithTimeStamp() : state(INVALID), timestamp(0) {} + State state; + TimeStamp timestamp; + }; + +protected: + //FIXME last timestamp is shared between Print & SLAPrint, + // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp + // is not synchronized! + static size_t g_last_timestamp; +}; + +// To be instantiated over PrintStep or PrintObjectStep enums. +template +class PrintState : public PrintStateBase +{ +public: + PrintState() {} + + StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { + tbb::mutex::scoped_lock lock(mtx); + StateWithTimeStamp state = m_state[step]; + return state; + } + + bool is_done(StepType step, tbb::mutex &mtx) const { + return this->state_with_timestamp(step, mtx).state == DONE; + } + + StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const { + return m_state[step]; + } + + bool is_done_unguarded(StepType step) const { + return this->state_with_timestamp_unguarded(step).state == DONE; + } + + // Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being + // modified by the UI thread. + // This is necessary to block until the Print::apply_config() updates its state, which may + // influence the processing step being entered. + template + bool set_started(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { + tbb::mutex::scoped_lock lock(mtx); + // If canceled, throw before changing the step state. + throw_if_canceled(); + if (m_state[step].state == DONE) + return false; + m_state[step].state = STARTED; + m_state[step].timestamp = ++ g_last_timestamp; + return true; + } + + // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being + // modified by the UI thread. + template + TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { + tbb::mutex::scoped_lock lock(mtx); + // If canceled, throw before changing the step state. + throw_if_canceled(); + assert(m_state[step].state != DONE); + m_state[step].state = DONE; + m_state[step].timestamp = ++ g_last_timestamp; + return m_state[step].timestamp; + } + + // Make the step invalid. + // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state. + // In case the step has already been entered or finished, cancel the background + // processing by calling the cancel callback. + template + bool invalidate(StepType step, CancelationCallback cancel) { + bool invalidated = m_state[step].state != INVALID; + if (invalidated) { +#if 0 + if (mtx.state != mtx.HELD) { + printf("Not held!\n"); + } +#endif + m_state[step].state = INVALID; + m_state[step].timestamp = ++ g_last_timestamp; + // Raise the mutex, so that the following cancel() callback could cancel + // the background processing. + // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let + // the working thread to proceed. + cancel(); + } + return invalidated; + } + + template + bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, CancelationCallback cancel) { + bool invalidated = false; + for (StepTypeIterator it = step_begin; it != step_end; ++ it) { + StateWithTimeStamp &state = m_state[*it]; + if (state.state != INVALID) { + invalidated = true; + state.state = INVALID; + state.timestamp = ++ g_last_timestamp; + } + } + if (invalidated) { +#if 0 + if (mtx.state != mtx.HELD) { + printf("Not held!\n"); + } +#endif + // Raise the mutex, so that the following cancel() callback could cancel + // the background processing. + // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let + // the working thread to proceed. + cancel(); + } + return invalidated; + } + + // Make all steps invalid. + // PrintBase::m_state_mutex should be locked at this point, guarding access to m_state. + // In case any step has already been entered or finished, cancel the background + // processing by calling the cancel callback. + template + bool invalidate_all(CancelationCallback cancel) { + bool invalidated = false; + for (size_t i = 0; i < COUNT; ++ i) { + StateWithTimeStamp &state = m_state[i]; + if (state.state != INVALID) { + invalidated = true; + state.state = INVALID; + state.timestamp = ++ g_last_timestamp; + } + } + if (invalidated) + cancel(); + return invalidated; + } + +private: + StateWithTimeStamp m_state[COUNT]; +}; + +class PrintBase; + +class PrintObjectBase +{ +public: + const ModelObject* model_object() const { return m_model_object; } + ModelObject* model_object() { return m_model_object; } + +protected: + PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {} + virtual ~PrintObjectBase() {} + // Declared here to allow access from PrintBase through friendship. + static tbb::mutex& state_mutex(PrintBase *print); + static std::function cancel_callback(PrintBase *print); + + ModelObject *m_model_object; +}; + +/** + * @brief Printing involves slicing and export of device dependent instructions. + * + * Every technology has a potentially different set of requirements for + * slicing, support structures and output print instructions. The pipeline + * however remains roughly the same: + * slice -> convert to instructions -> send to printer + * + * The PrintBase class will abstract this flow for different technologies. + * + */ +class PrintBase +{ +public: + PrintBase() { this->restart(); } + inline virtual ~PrintBase() {} + + virtual PrinterTechnology technology() const noexcept = 0; + + // Reset the print status including the copy of the Model / ModelObject hierarchy. + virtual void clear() = 0; + // The Print is empty either after clear() or after apply() over an empty model, + // or after apply() over a model, where no object is printable (all outside the print volume). + virtual bool empty() const = 0; + + // Validate the print, return empty string if valid, return error if process() cannot (or should not) be started. + virtual std::string validate() const { return std::string(); } + + enum ApplyStatus { + // No change after the Print::apply() call. + APPLY_STATUS_UNCHANGED, + // Some of the Print / PrintObject / PrintObjectInstance data was changed, + // but no result was invalidated (only data influencing not yet calculated results were changed). + APPLY_STATUS_CHANGED, + // Some data was changed, which in turn invalidated already calculated steps. + APPLY_STATUS_INVALIDATED, + }; + virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; + const Model& model() const { return m_model; } + + virtual void process() = 0; + + struct SlicingStatus { + SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} + int percent; + std::string text; + // Bitmap of flags. + enum FlagBits { + DEFAULT, + NO_RELOAD_SCENE = 0, + RELOAD_SCENE = 1, + }; + // Bitmap of FlagBits + unsigned int flags; + }; + typedef std::function status_callback_type; + // Default status console print out in the form of percent => message. + void set_status_default() { m_status_callback = nullptr; } + // No status output or callback whatsoever, useful mostly for automatic tests. + void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; } + // Register a custom status callback. + void set_status_callback(status_callback_type cb) { m_status_callback = cb; } + // Calls a registered callback to update the status, or print out the default message. + void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT) { + if (m_status_callback) m_status_callback(SlicingStatus(percent, message, flags)); + else printf("%d => %s\n", percent, message.c_str()); + } + + typedef std::function cancel_callback_type; + // Various methods will call this callback to stop the background processing (the Print::process() call) + // in case a successive change of the Print / PrintObject / PrintRegion instances changed + // the state of the finished or running calculations. + void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; } + // Has the calculation been canceled? + enum CancelStatus { + // No cancelation, background processing should run. + NOT_CANCELED = 0, + // Canceled by user from the user interface (user pressed the "Cancel" button or user closed the application). + CANCELED_BY_USER = 1, + // Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps(). + CANCELED_INTERNAL = 2 + }; + CancelStatus cancel_status() const { return m_cancel_status; } + // Has the calculation been canceled? + bool canceled() const { return m_cancel_status != NOT_CANCELED; } + // Cancel the running computation. Stop execution of all the background threads. + void cancel() { m_cancel_status = CANCELED_BY_USER; } + void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; } + // Cancel the running computation. Stop execution of all the background threads. + void restart() { m_cancel_status = NOT_CANCELED; } + // Returns true if the last step was finished with success. + virtual bool finished() const = 0; + + const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } + PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + + virtual std::string output_filename() const = 0; + std::string output_filepath(const std::string &path) const; + +protected: + friend class PrintObjectBase; + friend class BackgroundSlicingProcess; + + tbb::mutex& state_mutex() const { return m_state_mutex; } + std::function cancel_callback() { return m_cancel_callback; } + void call_cancell_callback() { m_cancel_callback(); } + + // If the background processing stop was requested, throw CanceledException. + // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. + void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); } + + // To be called by this->output_filename() with the format string pulled from the configuration layer. + std::string output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const; + // Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects. + void update_object_placeholders(); + + Model m_model; + +private: + tbb::atomic m_cancel_status; + // Callback to be evoked regularly to update state of the UI thread. + status_callback_type m_status_callback; + + // Callback to be evoked to stop the background processing before a state is updated. + cancel_callback_type m_cancel_callback = [](){}; + + // Mutex used for synchronization of the worker thread with the UI thread: + // The mutex will be used to guard the worker thread against entering a stage + // while the data influencing the stage is modified. + mutable tbb::mutex m_state_mutex; + + PlaceholderParser m_placeholder_parser; +}; + +template +class PrintBaseWithState : public PrintBase +{ +public: + bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); } + PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); } + +protected: + bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } + bool invalidate_step(PrintStepEnum step) + { return m_state.invalidate(step, this->cancel_callback()); } + template + bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) + { return m_state.invalidate_multiple(step_begin, step_end, this->cancel_callback()); } + bool invalidate_steps(std::initializer_list il) + { return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_callback()); } + bool invalidate_all_steps() + { return m_state.invalidate_all(this->cancel_callback()); } + +private: + PrintState m_state; +}; + +template +class PrintObjectBaseWithState : public PrintObjectBase +{ +public: + PrintType* print() { return m_print; } + const PrintType* print() const { return m_print; } + + typedef PrintState PrintObjectState; + bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); } + PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } + +protected: + PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} + + bool set_started(PrintObjectStepEnum step) + { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) + { return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } + + bool invalidate_step(PrintObjectStepEnum step) + { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); } + template + bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end) + { return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_callback(m_print)); } + bool invalidate_steps(std::initializer_list il) + { return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_callback(m_print)); } + bool invalidate_all_steps() + { return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); } + +protected: + // If the background processing stop was requested, throw CanceledException. + // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. + void throw_if_canceled() { if (m_print->canceled()) throw CanceledException(); } + + friend PrintType; + PrintType *m_print; + +private: + PrintState m_state; +}; + +} // namespace Slic3r + +#endif /* slic3r_PrintBase_hpp_ */ diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c251ad1cb..8d8b1cb7b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1,7 +1,6 @@ #include "PrintConfig.hpp" #include "I18N.hpp" -#include #include #include #include @@ -40,6 +39,7 @@ void PrintConfigDef::init_common_params() def = this->add("bed_shape", coPoints); def->label = L("Bed shape"); + def->mode = comAdvanced; def->default_value = new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }; def = this->add("layer_height", coFloat); @@ -57,6 +57,7 @@ void PrintConfigDef::init_common_params() def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); def->sidetext = L("mm"); def->cli = "max-print-height=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(200.0); } @@ -74,6 +75,7 @@ void PrintConfigDef::init_fff_params() "This is mostly useful with Bowden extruders which suffer from oozing. " "This feature slows down both the print and the G-code generation."); def->cli = "avoid-crossing-perimeters!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("remove_small_gaps", coBool); @@ -82,12 +84,9 @@ void PrintConfigDef::init_fff_params() "are very confident on your model, or you want to print an item with a geometry " "designed for vase mode."); def->cli = "remove-small-gaps!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); - def = this->add("bed_shape", coPoints); - def->label = L("Bed shape"); - def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; - def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " @@ -107,6 +106,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 50; + def->mode = comExpert; def->default_value = new ConfigOptionString(""); def = this->add("between_objects_gcode", coString); @@ -116,6 +116,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 120; + def->mode = comExpert; def->default_value = new ConfigOptionString(""); def = this->add("bottom_solid_layers", coInt); @@ -134,6 +135,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->cli = "bridge-acceleration=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("bridge_angle", coFloat); @@ -145,6 +147,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("°"); def->cli = "bridge-angle=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.); def = this->add("bridge_fan_speed", coInts); @@ -154,7 +157,8 @@ void PrintConfigDef::init_fff_params() def->cli = "bridge-fan-speed=i@"; def->min = 0; def->max = 100; - def->default_value = new ConfigOptionInts{ 100 }; + def->mode = comExpert; + def->default_value = new ConfigOptionInts { 100 }; def = this->add("top_fan_speed", coInts); def->label = L("Top fan speed"); @@ -163,6 +167,7 @@ void PrintConfigDef::init_fff_params() def->cli = "top-fan-speed=i@"; def->min = 0; def->max = 100; + def->mode = comAdvanced; def->default_value = new ConfigOptionInts{ 100 }; def = this->add("bridge_flow_ratio", coFloat); @@ -175,6 +180,7 @@ void PrintConfigDef::init_fff_params() def->cli = "bridge-flow-ratio=f"; def->min = 0; def->max = 2; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1); def = this->add("over_bridge_flow_ratio", coFloat); @@ -187,7 +193,8 @@ void PrintConfigDef::init_fff_params() "It's useful if you want to have a nice flat top layer."); def->cli = "over-bridge-flow-ratio=f"; def->min = 0; - def->default_value = new ConfigOptionFloat(1.15); + def->mode = comAdvanced; + def->default_value = new ConfigOptionFloat(1); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); @@ -205,6 +212,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "brim-width=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); def = this->add("clip_multipart_objects", coBool); @@ -213,10 +221,18 @@ void PrintConfigDef::init_fff_params() "to clip the overlapping object parts one by the other " "(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."); def->cli = "clip-multipart-objects!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); + def = this->add("colorprint_heights", coFloats); + def->label = L("Colorprint height"); + def->tooltip = L("Heights at which a filament change is to occur. "); + def->cli = "colorprint-heights=f@"; + def->default_value = new ConfigOptionFloats { }; + def = this->add("compatible_printers", coStrings); def->label = L("Compatible printers"); + def->mode = comAdvanced; def->default_value = new ConfigOptionStrings(); def = this->add("compatible_printers_condition", coString); @@ -224,12 +240,28 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("A boolean expression using the configuration values of an active printer profile. " "If this expression evaluates to true, this profile is considered compatible " "with the active printer profile."); + def->mode = comExpert; + def->default_value = new ConfigOptionString(); + + def = this->add("compatible_prints", coStrings); + def->label = L("Compatible print profiles"); + def->mode = comAdvanced; + def->default_value = new ConfigOptionStrings(); + + def = this->add("compatible_prints_condition", coString); + def->label = L("Compatible print profiles condition"); + def->tooltip = L("A boolean expression using the configuration values of an active print profile. " + "If this expression evaluates to true, this profile is considered compatible " + "with the active print profile."); + def->mode = comExpert; def->default_value = new ConfigOptionString(); // The following value is to be stored into the project file (AMF, 3MF, Config ...) // and it contains a sum of "compatible_printers_condition" values over the print and filament profiles. def = this->add("compatible_printers_condition_cummulative", coStrings); def->default_value = new ConfigOptionStrings(); + def = this->add("compatible_prints_condition_cummulative", coStrings); + def->default_value = new ConfigOptionStrings(); def = this->add("complete_objects", coBool); def->label = L("Complete individual objects"); @@ -238,6 +270,7 @@ void PrintConfigDef::init_fff_params() "This feature is useful to avoid the risk of ruined prints. " "Slic3r should warn and prevent you from extruder collisions, but beware."); def->cli = "complete-objects!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("cooling", coBools); @@ -253,6 +286,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "cooling_tube_retraction=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(91.5f); def = this->add("cooling_tube_length", coFloat); @@ -261,6 +295,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "cooling_tube_length=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(5.f); def = this->add("default_acceleration", coFloat); @@ -271,6 +306,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->cli = "default-acceleration=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("default_filament_profile", coStrings); @@ -293,6 +329,7 @@ void PrintConfigDef::init_fff_params() def->cli = "disable-fan-first-layers=i@"; def->min = 0; def->max = 1000; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 3 }; def = this->add("dont_support_bridges", coBool); @@ -301,6 +338,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Experimental option for preventing support material from being generated " "under bridged areas."); def->cli = "dont-support-bridges!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("duplicate_distance", coFloat); @@ -319,6 +357,7 @@ void PrintConfigDef::init_fff_params() "to compensate for the 1st layer squish aka an Elephant Foot effect. (should be negative = inwards)"); def->sidetext = L("mm"); def->cli = "elefant-foot-compensation=f"; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("end_gcode", coString); @@ -329,6 +368,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 120; + def->mode = comExpert; def->default_value = new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"); def = this->add("end_filament_gcode", coStrings); @@ -340,6 +380,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 120; + def->mode = comExpert; def->default_value = new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" }; def = this->add("ensure_vertical_shell_thickness", coBool); @@ -348,6 +389,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " "(top+bottom solid layers)."); def->cli = "ensure-vertical-shell-thickness!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("top_fill_pattern", coEnum); @@ -394,6 +436,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(ipRectilinear); def = this->add("enforce_full_fill_volume", coBool); @@ -404,6 +447,7 @@ void PrintConfigDef::init_fff_params() "but it can go as high as +50% for infill in very small areas where rectilinear doesn't have good coverage). It has the advantage " "to remove the over-extrusion seen in thin infill areas, from the overlap ratio"); def->cli = "enforce-full-fill-volume!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); def = this->add("external_infill_margin", coFloat); @@ -413,6 +457,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "top-layer-anchor=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(1.5); def = this->add("bridged_infill_margin", coFloat); @@ -422,6 +467,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "top-layer-anchor=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(2); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -432,6 +478,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 200%), it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "external-perimeter-extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("external_perimeter_speed", coFloatOrPercent); @@ -452,6 +499,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Print contour perimeters from the outermost one to the innermost one " "instead of the default inverse order."); def->cli = "external-perimeters-first!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("perimeter_loop", coBool); @@ -460,6 +508,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Join the perimeters to create only one continuous extrusion without any z-hop." " Long inside travel (from external to holes) are not extruded to give some space to the infill."); def->cli = "loop-perimeter!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("perimeter_loop_seam", coEnum); @@ -472,15 +521,17 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("rear"); def->enum_labels.push_back(L("Nearest")); def->enum_labels.push_back(L("Rear")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(spRear); - def = this->add("extra_perimeters", coBool); + def = this->add("extra_perimeters", coBool);avoid def->label = L("Extra perimeters if needed"); def->category = L("Layers and Perimeters"); def->tooltip = L("Add more perimeters when needed for avoiding gaps in sloping walls. " "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above " "is supported."); def->cli = "extra-perimeters!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("only_one_perimeter_top", coBool); @@ -515,6 +566,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "extruder-clearance-height=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(20); def = this->add("extruder_clearance_radius", coFloat); @@ -526,6 +578,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "extruder-clearance-radius=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(20); def = this->add("extruder_colour", coStrings); @@ -544,6 +597,7 @@ void PrintConfigDef::init_fff_params() "from the XY coordinate)."); def->sidetext = L("mm"); def->cli = "extruder-offset=s@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionPoints { Vec2d(0,0) }; def = this->add("extrusion_axis", coString); @@ -560,6 +614,7 @@ void PrintConfigDef::init_fff_params() "Usual values are between 0.9 and 1.1. If you think you need to change this more, " "check filament diameter and your firmware E steps."); def->cli = "extrusion-multiplier=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 1. }; def = this->add("extrusion_width", coFloatOrPercent); @@ -571,6 +626,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example: 230%), it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for auto)"); def->cli = "extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("fan_always_on", coBools); @@ -589,6 +645,7 @@ void PrintConfigDef::init_fff_params() def->width = 60; def->min = 0; def->max = 1000; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 60 }; def = this->add("filament_colour", coStrings); @@ -605,6 +662,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 130; + def->mode = comAdvanced; def->default_value = new ConfigOptionStrings { "" }; def = this->add("filament_max_volumetric_speed", coFloats); @@ -615,6 +673,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm³/s"); def->cli = "filament-max-volumetric-speed=f@"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_loading_speed", coFloats); @@ -623,6 +682,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "filament-loading-speed=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 28. }; def = this->add("filament_loading_speed_start", coFloats); @@ -631,6 +691,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "filament-loading-speed-start=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 3. }; def = this->add("filament_unloading_speed", coFloats); @@ -640,6 +701,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "filament-unloading-speed=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 90. }; def = this->add("filament_unloading_speed_start", coFloats); @@ -648,6 +710,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "filament-unloading-speed-start=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 100. }; def = this->add("filament_toolchange_delay", coFloats); @@ -658,6 +721,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("s"); def->cli = "filament-toolchange-delay=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_cooling_moves", coInts); @@ -667,6 +731,7 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-cooling-moves=i@"; def->max = 0; def->max = 20; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 4 }; def = this->add("filament_cooling_initial_speed", coFloats); @@ -675,6 +740,7 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-cooling-initial-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 2.2f }; def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); @@ -686,6 +752,7 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-minimal-purge-on-wipe-tower=f@"; def->sidetext = L("mm³"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 15.f }; def = this->add("filament_cooling_final_speed", coFloats); @@ -694,6 +761,7 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-cooling-final-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 3.4f }; def = this->add("filament_load_time", coFloats); @@ -702,12 +770,14 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-load-time=i@"; def->sidetext = L("s"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0.0f }; def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters "); def->cli = "filament-ramming-parameters=s@"; + def->mode = comExpert; def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }; @@ -717,6 +787,7 @@ void PrintConfigDef::init_fff_params() def->cli = "filament-unload-time=i@"; def->sidetext = L("s"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0.0f }; def = this->add("filament_diameter", coFloats); @@ -782,6 +853,7 @@ void PrintConfigDef::init_fff_params() def->cli = "fill-angle=f"; def->min = 0; def->max = 360; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(45); def = this->add("fill_density", coPercent); @@ -865,7 +937,6 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("cubic"); def->enum_values.push_back("line"); def->enum_values.push_back("concentric"); - def->enum_values.push_back("concentricgapfill"); def->enum_values.push_back("honeycomb"); def->enum_values.push_back("3dhoneycomb"); def->enum_values.push_back("gyroid"); @@ -879,7 +950,6 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Cubic")); def->enum_labels.push_back(L("Line")); def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Concentric (filled)")); def->enum_labels.push_back(L("Honeycomb")); def->enum_labels.push_back(L("3D Honeycomb")); def->enum_labels.push_back(L("Gyroid")); @@ -895,6 +965,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->cli = "first-layer-acceleration=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("first_layer_bed_temperature", coInts); @@ -916,6 +987,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "first-layer-extrusion-width=s"; def->ratio_over = "first_layer_height"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(200, true); def = this->add("first_layer_height", coFloatOrPercent); @@ -929,7 +1001,7 @@ void PrintConfigDef::init_fff_params() def->cli = "first-layer-height=s"; def->ratio_over = "layer_height"; def->default_value = new ConfigOptionFloatOrPercent(0.35, false); - + def = this->add("first_layer_speed", coFloatOrPercent); def->label = L("Default"); def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves " @@ -939,6 +1011,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s or %"); def->cli = "first-layer-speed=s"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(30, false); def = this->add("first_layer_infill_speed", coFloatOrPercent); @@ -950,6 +1023,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s or %"); def->cli = "first-layer-infill-speed=s"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(30, false); def = this->add("first_layer_temperature", coInts); @@ -985,6 +1059,7 @@ void PrintConfigDef::init_fff_params() "If you print from SD card, the additional weight of the file could make your firmware " "slow down."); def->cli = "gcode-comments!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(0); def = this->add("label_printed_objects", coBool); @@ -1020,8 +1095,18 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Machinekit"); def->enum_labels.push_back("Smoothie"); def->enum_labels.push_back(L("No extrusion")); + def->mode = comExpert; def->default_value = new ConfigOptionEnum(gcfRepRap); + def = this->add("high_current_on_filament_swap", coBool); + def->label = L("High extruder current on filament swap"); + def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" + " sequence to allow for rapid ramming feed rates and to overcome resistance when loading" + " a filament with an ugly shaped tip."); + def->cli = "high-current-on-filament-swap!"; + def->mode = comExpert; + def->default_value = new ConfigOptionBool(0); + def = this->add("infill_acceleration", coFloat); def->label = L("Infill"); def->tooltip = L("This is the acceleration your printer will use for infill. Set zero to disable " @@ -1029,6 +1114,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->cli = "infill-acceleration=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("infill_every_layers", coInt); @@ -1040,6 +1126,7 @@ void PrintConfigDef::init_fff_params() def->cli = "infill-every-layers=i"; def->full_label = L("Combine infill every n layers"); def->min = 1; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("infill_dense", coBool); @@ -1048,6 +1135,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Enables the creation of a support layer under the first solid layer. This allows you to use a lower infill ratio without compromising the top quality." " The dense infill is laid out with a 50% infill density."); def->cli = "infill-dense!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("infill_not_connected", coBool); @@ -1055,6 +1143,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("If checked, the infill algorithm will try to not connect the lines near the infill. Can be useful for art or with high infill/perimeter overlap."); def->cli = "infill-not-connected!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("infill_dense_algo", coEnum); @@ -1070,6 +1159,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Automatic")); def->enum_labels.push_back(L("Automatic, only for small areas")); def->enum_labels.push_back(L("Anchored")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(dfaAutomatic); def = this->add("infill_extruder", coInt); @@ -1078,6 +1168,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The extruder to use when printing infill."); def->cli = "infill-extruder=i"; def->min = 1; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("infill_extrusion_width", coFloatOrPercent); @@ -1089,6 +1180,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "infill-extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("infill_first", coBool); @@ -1096,6 +1188,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("This option will switch the print order of perimeters and infill, making the latter first."); def->cli = "infill-first!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("infill_only_where_needed", coBool); @@ -1105,6 +1198,7 @@ void PrintConfigDef::init_fff_params() "(it will act as internal support material). If enabled, slows down the G-code generation " "due to the multiple checks involved."); def->cli = "infill-only-where-needed!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("infill_overlap", coFloatOrPercent); @@ -1116,6 +1210,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or %"); def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; + def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(25, true); def = this->add("infill_speed", coFloat); @@ -1147,6 +1242,7 @@ void PrintConfigDef::init_fff_params() "support material."); def->cli = "interface-shells!"; def->category = L("Layers and Perimeters"); + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("layer_gcode", coString); @@ -1158,6 +1254,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 50; + def->mode = comExpert; def->default_value = new ConfigOptionString(""); def = this->add("exact_last_layer_height", coBool); @@ -1173,11 +1270,13 @@ void PrintConfigDef::init_fff_params() " intervals into the G-code to let the firmware show accurate remaining time." " As of now only the Prusa i3 MK3 firmware recognizes M73." " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode."); + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("silent_mode", coBool); def->label = L("Supports silent mode"); def->tooltip = L("Set silent mode for the G-code flavor"); + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); const int machine_limits_opt_width = 70; @@ -1205,6 +1304,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats(axis.max_feedrate); // Add the machine acceleration limits for XYZE axes (M201) def = this->add("machine_max_acceleration_" + axis.name, coFloats); @@ -1214,6 +1314,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats(axis.max_acceleration); // Add the machine jerk limits for XYZE axes (M205) def = this->add("machine_max_jerk_" + axis.name, coFloats); @@ -1223,6 +1324,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats(axis.max_jerk); } } @@ -1235,6 +1337,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats{ 0., 0. }; // M205 T... [mm/sec] @@ -1245,6 +1348,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats{ 0., 0. }; // M204 S... [mm/sec^2] @@ -1255,6 +1359,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats{ 1500., 1250. }; // M204 T... [mm/sec^2] @@ -1265,6 +1370,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s²"); def->min = 0; def->width = machine_limits_opt_width; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats{ 1500., 1250. }; def = this->add("max_fan_speed", coInts); @@ -1274,6 +1380,7 @@ void PrintConfigDef::init_fff_params() def->cli = "max-fan-speed=i@"; def->min = 0; def->max = 100; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 100 }; def = this->add("max_layer_height", coFloats); @@ -1285,6 +1392,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "max-layer-height=f@"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("max_print_speed", coFloat); @@ -1295,6 +1403,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "max-print-speed=f"; def->min = 1; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(80); def = this->add("max_volumetric_speed", coFloat); @@ -1304,6 +1413,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm³/s"); def->cli = "max-volumetric-speed=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); @@ -1315,6 +1425,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm³/s²"); def->cli = "max-volumetric-extrusion-rate-slope-positive=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); @@ -1326,6 +1437,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm³/s²"); def->cli = "max-volumetric-extrusion-rate-slope-negative=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("min_fan_speed", coInts); @@ -1335,6 +1447,7 @@ void PrintConfigDef::init_fff_params() def->cli = "min-fan-speed=i@"; def->min = 0; def->max = 100; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 35 }; def = this->add("min_layer_height", coFloats); @@ -1344,6 +1457,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "min-layer-height=f@"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0.07 }; def = this->add("min_print_speed", coFloats); @@ -1352,6 +1466,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->cli = "min-print-speed=f@"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 10. }; def = this->add("min_skirt_length", coFloat); @@ -1362,6 +1477,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "min-skirt-length=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("notes", coString); @@ -1372,6 +1488,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 130; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("nozzle_diameter", coFloats); @@ -1391,6 +1508,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("duet"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(htOctoPrint); def = this->add("printhost_apikey", coString); @@ -1398,13 +1516,15 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " "the API Key or the password required for authentication."); def->cli = "printhost-apikey=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("printhost_cafile", coString); - def->label = "HTTPS CA file"; + def->label = "HTTPS CA File"; def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " "If left blank, the default OS CA certificate repository is used."; def->cli = "printhost-cafile=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("print_host", coString); @@ -1412,6 +1532,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " "the hostname, IP address or URL of the printer host instance."); def->cli = "print-host=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); @@ -1419,6 +1540,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters " "(and thus any ooze will be probably invisible)."); def->cli = "only-retract-when-crossing-perimeters!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); def = this->add("ooze_prevention", coBool); @@ -1427,6 +1549,7 @@ void PrintConfigDef::init_fff_params() "It will enable a tall skirt automatically and move extruders outside such " "skirt when changing temperatures."); def->cli = "ooze-prevention!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("output_filename_format", coString); @@ -1437,7 +1560,8 @@ void PrintConfigDef::init_fff_params() "[input_filename_base]."); def->cli = "output-filename-format=s"; def->full_width = true; - def->default_value = new ConfigOptionString("[input_filename_base].gcode"); + def->mode = comExpert; + def->default_value = new ConfigOptionString("[input_filename_base]"); def = this->add("overhangs", coBool); def->label = L("Detect bridging perimeters"); @@ -1445,6 +1569,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan."); def->cli = "overhangs!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("no_perimeter_unsupported", coBool); @@ -1452,6 +1577,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Experimental option to remove perimeters where there is nothing under it and where a bridged infill should be better. Computationally intensive!"); def->cli = "no-perimeter-unsupported!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("min_perimeter_unsupported", coInt); @@ -1460,6 +1586,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Number of perimeters exluded from this option."); def->cli = "min-perimeter-unsupported=i"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionInt(0); def = this->add("noperi_bridge_only", coBool); @@ -1467,6 +1594,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Only remove perimeters over areas marked as 'bridge'. Can be useful to let perimeter run over overhangs, but it's not very reliable."); def->cli = "noperi-bridge-only!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); def = this->add("parking_pos_retraction", coFloat); @@ -1476,6 +1604,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "parking_pos_retraction=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(92.f); def = this->add("extra_loading_move", coFloat); @@ -1485,6 +1614,7 @@ void PrintConfigDef::init_fff_params() " if negative, the loading move is shorter than unloading. "); def->sidetext = L("mm"); def->cli = "extra_loading_move=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(-2.f); def = this->add("perimeter_acceleration", coFloat); @@ -1494,6 +1624,7 @@ void PrintConfigDef::init_fff_params() "Set zero to disable acceleration control for perimeters."); def->sidetext = L("mm/s²"); def->cli = "perimeter-acceleration=f"; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("perimeter_extruder", coInt); @@ -1503,6 +1634,7 @@ void PrintConfigDef::init_fff_params() def->cli = "perimeter-extruder=i"; def->aliases = { "perimeters_extruder" }; def->min = 1; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("perimeter_extrusion_width", coFloatOrPercent); @@ -1515,6 +1647,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "perimeter-extrusion-width=s"; def->aliases = { "perimeters_extrusion_width" }; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("perimeter_speed", coFloat); @@ -1551,6 +1684,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 60; + def->mode = comExpert; def->default_value = new ConfigOptionStrings(); def = this->add("printer_model", coString); @@ -1565,6 +1699,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 130; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("printer_vendor", coString); @@ -1588,6 +1723,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Use a solid layer instead of a raft for the layer that touch the build plate."); def->cli = "support-material-solid-first-layer!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("raft_layers", coInt); @@ -1598,6 +1734,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("layers"); def->cli = "raft-layers=i"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(0); def = this->add("resolution", coFloat); @@ -1609,6 +1746,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "resolution=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("retract_before_travel", coFloats); @@ -1616,6 +1754,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Retraction is not triggered when travel moves are shorter than this length."); def->sidetext = L("mm"); def->cli = "retract-before-travel=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_before_wipe", coPercents); @@ -1624,12 +1763,14 @@ void PrintConfigDef::init_fff_params() "before doing the wipe movement."); def->sidetext = L("%"); def->cli = "retract-before-wipe=s@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionPercents { 0. }; def = this->add("retract_layer_change", coBools); def->label = L("Retract on layer change"); def->tooltip = L("This flag enforces a retraction whenever a Z move is done."); def->cli = "retract-layer-change!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBools { false }; def = this->add("retract_length", coFloats); @@ -1649,6 +1790,7 @@ void PrintConfigDef::init_fff_params() "the extruder)."); def->sidetext = L("mm (zero to disable)"); def->cli = "retract-length-toolchange=f@"; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 10. }; def = this->add("retract_lift", coFloats); @@ -1667,6 +1809,7 @@ void PrintConfigDef::init_fff_params() "absolute Z. You can tune this setting for skipping lift on the first layers."); def->sidetext = L("mm"); def->cli = "retract-lift-above=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_below", coFloats); @@ -1677,6 +1820,7 @@ void PrintConfigDef::init_fff_params() "to the first layers."); def->sidetext = L("mm"); def->cli = "retract-lift-below=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra", coFloats); @@ -1685,6 +1829,7 @@ void PrintConfigDef::init_fff_params() "this additional amount of filament. This setting is rarely needed."); def->sidetext = L("mm"); def->cli = "retract-restart-extra=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra_toolchange", coFloats); @@ -1693,6 +1838,7 @@ void PrintConfigDef::init_fff_params() "this additional amount of filament."); def->sidetext = L("mm"); def->cli = "retract-restart-extra-toolchange=f@"; + def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_speed", coFloats); @@ -1701,6 +1847,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The speed for retractions (it only applies to the extruder motor)."); def->sidetext = L("mm/s"); def->cli = "retract-speed=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 40. }; def = this->add("deretract_speed", coFloats); @@ -1710,6 +1857,7 @@ void PrintConfigDef::init_fff_params() "(it only applies to the extruder motor). If left to zero, the retraction speed is used."); def->sidetext = L("mm/s"); def->cli = "retract-speed=f@"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("seam_position", coEnum); @@ -1728,6 +1876,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Aligned")); def->enum_labels.push_back(L("Rear")); def->enum_labels.push_back(L("Hidden")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(spAligned); def = this->add("seam_travel", coBool); @@ -1735,6 +1884,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Add a big cost to travel paths when possible (when going into a loop), so it will prefer a less optimal seam posistion if it's nearer."); def->cli = "seam-travel!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); #if 0 @@ -1797,6 +1947,7 @@ void PrintConfigDef::init_fff_params() "as a shield against drafts."); def->sidetext = L("layers"); def->cli = "skirt-height=i"; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("skirts", coInt); @@ -1807,6 +1958,7 @@ void PrintConfigDef::init_fff_params() "to disable skirt completely."); def->cli = "skirts=i"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("slowdown_below_layer_time", coInts); @@ -1818,6 +1970,7 @@ void PrintConfigDef::init_fff_params() def->width = 60; def->min = 0; def->max = 1000; + def->mode = comExpert; def->default_value = new ConfigOptionInts { 5 }; def = this->add("small_perimeter_speed", coFloatOrPercent); @@ -1839,6 +1992,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm²"); def->cli = "solid-infill-below-area=f"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(70); def = this->add("solid_infill_extruder", coInt); @@ -1847,6 +2001,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The extruder to use when printing solid infill."); def->cli = "solid-infill-extruder=i"; def->min = 1; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("solid_infill_every_layers", coInt); @@ -1859,6 +2014,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("layers"); def->cli = "solid-infill-every-layers=i"; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionInt(0); def = this->add("solid_infill_extrusion_width", coFloatOrPercent); @@ -1869,6 +2025,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "solid-infill-extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("solid_infill_speed", coFloatOrPercent); @@ -1910,6 +2067,7 @@ void PrintConfigDef::init_fff_params() def->cli = "standby-temperature-delta=i"; def->min = -max_temp; def->max = max_temp; + def->mode = comExpert; def->default_value = new ConfigOptionInt(-5); def = this->add("start_gcode", coString); @@ -1925,6 +2083,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 120; + def->mode = comExpert; def->default_value = new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"); def = this->add("start_filament_gcode", coStrings); @@ -1941,18 +2100,21 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 120; + def->mode = comExpert; def->default_value = new ConfigOptionStrings { "; Filament gcode\n" }; def = this->add("single_extruder_multi_material", coBool); def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); def->cli = "single-extruder-multi-material!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("single_extruder_multi_material_priming", coBool); def->label = L("Prime all printing extruders"); def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."); def->cli = "single-extruder-multi-material-priming!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("support_material", coBool); @@ -1968,6 +2130,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\ " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only."); def->cli = "support-material-auto!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("support_material_xy_spacing", coFloatOrPercent); @@ -1979,6 +2142,7 @@ void PrintConfigDef::init_fff_params() def->cli = "support-material-xy-spacing=s"; def->ratio_over = "external_perimeter_extrusion_width"; def->min = 0; + def->mode = comAdvanced; // Default is half the external perimeter width. def->default_value = new ConfigOptionFloatOrPercent(50, true); @@ -1990,6 +2154,7 @@ void PrintConfigDef::init_fff_params() def->cli = "support-material-angle=f"; def->min = 0; def->max = 359; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("support_material_buildplate_only", coBool); @@ -1997,6 +2162,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); def->cli = "support-material-buildplate-only!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_contact_distance", coFloat); @@ -2013,6 +2179,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("0.2"); def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str()); + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.2); def = this->add("support_material_enforce_layers", coInt); @@ -2026,6 +2193,7 @@ void PrintConfigDef::init_fff_params() def->cli = "support-material-enforce-layers=f"; def->full_label = L("Enforce support for the first n layers"); def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionInt(0); def = this->add("support_material_extruder", coInt); @@ -2035,6 +2203,7 @@ void PrintConfigDef::init_fff_params() "(1+, 0 to use the current extruder to minimize tool changes)."); def->cli = "support-material-extruder=i"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("support_material_extrusion_width", coFloatOrPercent); @@ -2045,6 +2214,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "support-material-extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("support_material_interface_contact_loops", coBool); @@ -2052,6 +2222,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Cover the top contact layer of the supports with loops. Disabled by default."); def->cli = "support-material-interface-contact-loops!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_interface_extruder", coInt); @@ -2061,6 +2232,7 @@ void PrintConfigDef::init_fff_params() "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too."); def->cli = "support-material-interface-extruder=i"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); def = this->add("support_material_interface_layers", coInt); @@ -2070,6 +2242,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("layers"); def->cli = "support-material-interface-layers=i"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(3); def = this->add("support_material_interface_spacing", coFloat); @@ -2079,6 +2252,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "support-material-interface-spacing=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); def = this->add("support_material_interface_speed", coFloatOrPercent); @@ -2104,6 +2278,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Rectilinear grid")); def->enum_labels.push_back(L("Honeycomb")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(smpRectilinear); def = this->add("support_material_spacing", coFloat); @@ -2113,6 +2288,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->cli = "support-material-spacing=f"; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(2.5); def = this->add("support_material_speed", coFloat); @@ -2130,6 +2306,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Synchronize support layers with the object print layers. This is useful " "with multi-material printers, where the extruder switch is expensive."); def->cli = "support-material-synchronize-layers!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_threshold", coInt); @@ -2144,6 +2321,7 @@ void PrintConfigDef::init_fff_params() def->cli = "support-material-threshold=i"; def->min = 0; def->max = 90; + def->mode = comAdvanced; def->default_value = new ConfigOptionInt(0); def = this->add("support_material_with_sheath", coBool); @@ -2152,6 +2330,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove."); def->cli = "support-material-with-sheath!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); def = this->add("temperature", coInts); @@ -2170,6 +2349,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)."); def->cli = "thin-walls!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); def = this->add("threads", coInt); @@ -2193,6 +2373,7 @@ void PrintConfigDef::init_fff_params() def->multiline = true; def->full_width = true; def->height = 50; + def->mode = comExpert; def->default_value = new ConfigOptionString(""); def = this->add("top_infill_extrusion_width", coFloatOrPercent); @@ -2204,6 +2385,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 90%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "top-infill-extrusion-width=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("top_solid_infill_speed", coFloatOrPercent); @@ -2236,6 +2418,7 @@ void PrintConfigDef::init_fff_params() def->cli = "travel-speed=f"; def->aliases = { "travel_feed_rate" }; def->min = 1; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(130); def = this->add("use_firmware_retraction", coBool); @@ -2243,6 +2426,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin."); def->cli = "use-firmware-retraction!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("use_relative_e_distances", coBool); @@ -2250,6 +2434,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("If your firmware requires relative E values, check this, " "otherwise leave it unchecked. Most firmwares use absolute values."); def->cli = "use-relative-e-distances!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("use_volumetric_e", coBool); @@ -2261,6 +2446,7 @@ void PrintConfigDef::init_fff_params() "diameter associated to the filament selected in Slic3r. This is only supported " "in recent Marlin."); def->cli = "use-volumetric-e!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("variable_layer_height", coBool); @@ -2268,6 +2454,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Some printers or printer setups may have difficulties printing " "with a variable layer height. Enabled by default."); def->cli = "variable-layer-height!"; + def->mode = comExpert; def->default_value = new ConfigOptionBool(true); def = this->add("wipe", coBools); @@ -2275,6 +2462,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This flag will move the nozzle while retracting to minimize the possible blob " "on leaky extruders."); def->cli = "wipe!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBools { false }; def = this->add("wipe_tower", coBool); @@ -2282,6 +2470,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Multi material printers may need to prime or purge extruders on tool changes. " "Extrude the excess material into the wipe tower."); def->cli = "wipe-tower!"; + def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("wiping_volumes_extruders", coFloats); @@ -2308,6 +2497,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("X coordinate of the left front corner of a wipe tower"); def->sidetext = L("mm"); def->cli = "wipe-tower-x=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(180.); def = this->add("wipe_tower_y", coFloat); @@ -2315,6 +2505,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); def->sidetext = L("mm"); def->cli = "wipe-tower-y=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(140.); def = this->add("wipe_tower_width", coFloat); @@ -2322,6 +2513,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Width of a wipe tower"); def->sidetext = L("mm"); def->cli = "wipe-tower-width=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(60.); def = this->add("wipe_tower_rotation_angle", coFloat); @@ -2329,8 +2521,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Wipe tower rotation angle with respect to x-axis "); def->sidetext = L("degrees"); def->cli = "wipe-tower-rotation-angle=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.); - + def = this->add("wipe_into_infill", coBool); def->category = L("Extruders"); def->label = L("Wipe into this object's infill"); @@ -2354,6 +2547,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximal distance between supports on sparse infill sections. "); def->sidetext = L("mm"); def->cli = "wipe-tower-bridging=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(10.); def = this->add("xy_size_compensation", coFloat); @@ -2364,6 +2558,7 @@ void PrintConfigDef::init_fff_params() "for fine-tuning sizes."); def->sidetext = L("mm"); def->cli = "xy-size-compensation=f"; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("hole_size_compensation", coFloat); @@ -2374,6 +2569,7 @@ void PrintConfigDef::init_fff_params() " This might be useful for fine-tuning hole sizes."); def->sidetext = L("mm"); def->cli = "hole-size-compensation=f"; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("z_offset", coFloat); @@ -2384,6 +2580,7 @@ void PrintConfigDef::init_fff_params() "from the print bed, set this to -0.3 (or fix your endstop)."); def->sidetext = L("mm"); def->cli = "z-offset=f"; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); def = this->add("bed_size_x", coFloat); @@ -2406,6 +2603,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("px"); def->cli = "pixel-width=i"; def->min = 1; + def->mode = comExpert; def->default_value = new ConfigOptionInt(1440); def = this->add("pixel_height", coInt); @@ -2444,14 +2642,14 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Width of the display"); def->cli = "display-width=f"; def->min = 1; - def->default_value = new ConfigOptionFloat(150.); + def->default_value = new ConfigOptionFloat(120.); def = this->add("display_height", coFloat); def->label = L("Display height"); def->tooltip = L("Height of the display"); def->cli = "display-height=f"; def->min = 1; - def->default_value = new ConfigOptionFloat(100.); + def->default_value = new ConfigOptionFloat(68.); def = this->add("display_pixels_x", coInt); def->full_label = L("Number of pixels in"); @@ -2459,14 +2657,25 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Number of pixels in X"); def->cli = "display-pixels-x=i"; def->min = 100; - def->default_value = new ConfigOptionInt(2000); + def->default_value = new ConfigOptionInt(2560); def = this->add("display_pixels_y", coInt); def->label = ("Y"); def->tooltip = L("Number of pixels in Y"); def->cli = "display-pixels-y=i"; def->min = 100; - def->default_value = new ConfigOptionInt(1000); + def->default_value = new ConfigOptionInt(1440); + + def = this->add("display_orientation", coEnum); + def->label = L("Display orientation"); + def->tooltip = L("Display orientation"); + def->cli = "display-orientation=s"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("landscape"); + def->enum_values.push_back("portrait"); + def->enum_labels.push_back(L("Landscape")); + def->enum_labels.push_back(L("Portrait")); + def->default_value = new ConfigOptionEnum(sladoPortrait); def = this->add("printer_correction", coFloats); def->full_label = L("Printer scaling correction"); @@ -2518,6 +2727,7 @@ void PrintConfigDef::init_sla_params() def->multiline = true; def->full_width = true; def->height = 130; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("default_sla_material_profile", coString); @@ -2528,6 +2738,188 @@ void PrintConfigDef::init_sla_params() def = this->add("sla_material_settings_id", coString); def->default_value = new ConfigOptionString(""); + + + def = this->add("default_sla_print_profile", coString); + def->label = L("Default SLA material profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); + + def = this->add("sla_print_settings_id", coString); + def->default_value = new ConfigOptionString(""); + + def = this->add("supports_enable", coBool); + def->label = L("Generate supports"); + def->category = L("Supports"); + def->tooltip = L("Generate supports for the models"); + def->sidetext = L(""); + def->cli = ""; + def->default_value = new ConfigOptionBool(true); + + def = this->add("support_head_front_diameter", coFloat); + def->label = L("Support head front diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter of the pointing side of the head"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.4); + + def = this->add("support_head_penetration", coFloat); + def->label = L("Support head penetration"); + def->category = L("Supports"); + def->tooltip = L("How much the pinhead has to penetrate the model surface"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.2); + + def = this->add("support_head_width", coFloat); + def->label = L("Support head width"); + def->category = L("Supports"); + def->tooltip = L("Width from the back sphere center to the front sphere center"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(1.0); + + def = this->add("support_pillar_diameter", coFloat); + def->label = L("Support pillar diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the support pillars"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(1.0); + + def = this->add("support_pillar_widening_factor", coFloat); + def->label = L("Pillar widening factor"); + def->category = L("Supports"); + def->tooltip = L("Merging bridges or pillars into another pillars can " + "increase the radius. Zero means no increase, one means " + "full increase."); + def->sidetext = L(""); + def->cli = ""; + def->min = 0; + def->max = 1; + def->default_value = new ConfigOptionFloat(0.0); + + def = this->add("support_base_diameter", coFloat); + def->label = L("Support base diameter"); + def->category = L("Supports"); + def->tooltip = L("Diameter in mm of the pillar base"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(4.0); + + def = this->add("support_base_height", coFloat); + def->label = L("Support base height"); + def->category = L("Supports"); + def->tooltip = L("The height of the pillar base cone"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(1.0); + + def = this->add("support_critical_angle", coFloat); + def->label = L("Critical angle"); + def->category = L("Supports"); + def->tooltip = L("The default angle for connecting support sticks and junctions."); + def->sidetext = L("°"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(45); + + def = this->add("support_max_bridge_length", coFloat); + def->label = L("Max bridge length"); + def->category = L("Supports"); + def->tooltip = L("The max length of a bridge"); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(15.0); + + def = this->add("support_object_elevation", coFloat); + def->label = L("Object elevation"); + def->category = L("Supports"); + def->tooltip = L("How much the supports should lift up the supported object."); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(5.0); + + def = this->add("support_density_at_horizontal", coInt); + def->label = L("Density on horizontal surfaces"); + def->category = L("Supports"); + def->tooltip = L("How many support points (approximately) should be placed on horizontal surface."); + def->sidetext = L("points per square dm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionInt(500); + + def = this->add("support_density_at_45", coInt); + def->label = L("Density on surfaces at 45 degrees"); + def->category = L("Supports"); + def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees."); + def->sidetext = L("points per square dm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionInt(250); + + def = this->add("support_minimal_z", coFloat); + def->label = L("Minimal support point height"); + def->category = L("Supports"); + def->tooltip = L("No support points will be placed lower than this value from the bottom."); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.f); + + def = this->add("pad_enable", coBool); + def->label = L("Use pad"); + def->category = L("Pad"); + def->tooltip = L("Add a pad underneath the supported model"); + def->sidetext = L(""); + def->cli = ""; + def->default_value = new ConfigOptionBool(true); + + def = this->add("pad_wall_thickness", coFloat); + def->label = L("Pad wall thickness"); + def->category = L("Pad"); + def->tooltip = L(""); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(2.0); + + def = this->add("pad_wall_height", coFloat); + def->label = L("Pad wall height"); + def->category = L("Pad"); + def->tooltip = L(""); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(5.0); + + def = this->add("pad_max_merge_distance", coFloat); + def->label = L("Max merge distance"); + def->category = L("Pad"); + def->tooltip = L(""); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(50.0); + + def = this->add("pad_edge_radius", coFloat); + def->label = L("Pad edge radius"); + def->category = L("Pad"); + def->tooltip = L(""); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(1.0); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -2601,7 +2993,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } } -PrintConfigDef print_config_def; +const PrintConfigDef print_config_def; DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() { @@ -2659,28 +3051,20 @@ void DynamicPrintConfig::normalize() std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. - FullPrintConfig fpc; - fpc.apply(*this, true); - // Verify this print options through the FullPrintConfig. - return fpc.validate(); -} - -size_t DynamicPrintConfig::remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message) -{ - size_t n_removed_keys = 0; - for (const std::string &key : this->keys()) { - if (! default_config.has(key)) { - if (removed_keys_message.empty()) - removed_keys_message = key; - else { - removed_keys_message += ", "; - removed_keys_message += key; - } - this->erase(key); - ++ n_removed_keys; - } + const ConfigOption *opt = this->option("printer_technology", false); + auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); + switch (printer_technology) { + case ptFFF: + { + FullPrintConfig fpc; + fpc.apply(*this, true); + // Verify this print options through the FullPrintConfig. + return fpc.validate(); + } + default: + //FIXME no validation on SLA data? + return std::string(); } - return n_removed_keys; } double PrintConfig::min_object_distance() const @@ -2885,8 +3269,163 @@ StaticPrintConfig::StaticCache PrintConfig::s_c StaticPrintConfig::StaticCache HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; -StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; -StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; -StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; +StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; +StaticPrintConfig::StaticCache SLAPrintConfig::s_cache_SLAPrintConfig; +StaticPrintConfig::StaticCache SLAPrintObjectConfig::s_cache_SLAPrintObjectConfig; +StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; +StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; + +CLIConfigDef::CLIConfigDef() +{ + ConfigOptionDef *def; + + def = this->add("cut", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Z."); + def->cli = "cut"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("dont_arrange", coBool); + def->label = L("Dont arrange"); + def->tooltip = L("Don't arrange the objects on the build plate. The model coordinates " + "define the absolute positions on the build plate. " + "The option --center will be ignored."); + def->cli = "dont-arrange"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("datadir", coString); + def->label = L("User data directory"); + def->tooltip = L("Load and store settings at the given directory. " + "This is useful for maintaining different profiles or including " + "configurations from a network storage."); + def->cli = "datadir"; + def->default_value = new ConfigOptionString(); + + def = this->add("export_3mf", coBool); + def->label = L("Export 3MF"); + def->tooltip = L("Slice the model and export slices as 3MF."); + def->cli = "export-3mf"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("slice", coBool); + def->label = L("Slice"); + def->tooltip = L("Slice the model and export gcode."); + def->cli = "slice"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("help", coBool); + def->label = L("Help"); + def->tooltip = L("Show this help."); + def->cli = "help"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("gui", coBool); + def->label = L("Use GUI"); + def->tooltip = L("Forces the GUI launch instead of command line slicing " + "(if you supply a model file, it will be loaded into the plater)"); + def->cli = "gui"; + def->default_value = new ConfigOptionBool(true); + + def = this->add("info", coBool); + def->label = L("Output Model Info"); + def->tooltip = L("Write information about the model to the console."); + def->cli = "info"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("load", coStrings); + def->label = L("Load config file"); + def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); + def->cli = "load"; + def->default_value = new ConfigOptionStrings(); + + def = this->add("no_gui", coBool); + def->label = L("Do not use GUI"); + def->tooltip = L("Forces the command line slicing instead of gui. This takes precedence over --gui if both are present."); + def->cli = "no-gui"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("output", coString); + def->label = L("Output File"); + def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); + def->cli = "output"; + def->default_value = new ConfigOptionString(""); + + def = this->add("rotate", coFloat); + def->label = L("Rotate"); + def->tooltip = L("Rotation angle around the Z axis in degrees (0-360, default: 0)."); + def->cli = "rotate"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_x", coFloat); + def->label = L("Rotate around X"); + def->tooltip = L("Rotation angle around the X axis in degrees (0-360, default: 0)."); + def->cli = "rotate-x"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_y", coFloat); + def->label = L("Rotate around Y"); + def->tooltip = L("Rotation angle around the Y axis in degrees (0-360, default: 0)."); + def->cli = "rotate-y"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("save", coString); + def->label = L("Save config file"); + def->tooltip = L("Save configuration to the specified file."); + def->cli = "save"; + def->default_value = new ConfigOptionString(); + + def = this->add("scale", coFloat); + def->label = L("Scale"); + def->tooltip = L("Scaling factor (default: 1)."); + def->cli = "scale"; + def->default_value = new ConfigOptionFloat(1); + +/* + def = this->add("scale_to_fit", coPoint3); + def->label = L("Scale to Fit"); + def->tooltip = L("Scale to fit the given volume."); + def->cli = "scale-to-fit"; + def->default_value = new ConfigOptionPoint3(Pointf3(0,0,0)); +*/ + + def = this->add("print_center", coPoint); + def->label = L("Print center"); + def->tooltip = L("Center the print around the given center (default: 100, 100)."); + def->cli = "print-center"; + def->default_value = new ConfigOptionPoint(Vec2d(100,100)); +} + +const CLIConfigDef cli_config_def; +DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; + +std::ostream& print_cli_options(std::ostream& out) +{ + for (const auto& opt : cli_config_def.options) { + if (opt.second.cli.size() != 0) { + out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; + out << "\t" << opt.second.tooltip << "\n"; + if (opt.second.default_value != nullptr) + out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; + out << "\n"; + } + } + std::cerr << std::endl; + return out; +} + +std::ostream& print_print_options(std::ostream& out) +{ + for (const auto& opt : print_config_def.options) { + if (opt.second.cli.size() != 0) { + out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; + out << "\t" << opt.second.tooltip << "\n"; + if (opt.second.default_value != nullptr) + out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; + out << "\n"; + } + } + std::cerr << std::endl; + return out; +} } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4c7fb7713..74bdaab7d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -31,7 +31,7 @@ enum PrinterTechnology }; enum GCodeFlavor { - gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, + gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; @@ -61,7 +61,12 @@ enum DenseInfillAlgo { dfaAutomatic, dfaAutoNotFull, dfaEnlarged, }; -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +enum SLADisplayOrientation { + sladoLandscape, + sladoPortrait +}; + +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["FFF"] = ptFFF; @@ -159,13 +164,21 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::ge return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["automatic"] = dfaAutomatic; - keys_map["autosmall"] = dfaAutoNotFull; - keys_map["enlarged"] = dfaEnlarged; - } +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static const t_config_enum_values keys_map = { + { "automatic", dfaAutomatic }, + { "autosmall", dfaAutoNotFull }, + { "enlarged", dfaEnlarged } + }; + return keys_map; +} + +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static const t_config_enum_values keys_map = { + { "landscape", sladoLandscape}, + { "portrait", sladoPortrait} + }; + return keys_map; } @@ -186,9 +199,9 @@ private: // The one and only global definition of SLic3r configuration options. // This definition is constant. -extern PrintConfigDef print_config_def; +extern const PrintConfigDef print_config_def; -// Slic3r dynamic configuration, used to override the configuration +// Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, // so the modified configuration values may be diffed against the active configuration @@ -211,10 +224,6 @@ public: // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); - // Remove all keys not in "valid_keys", return number of removed keys and add the list of keys to "removed_keys_message. - // valid_keys has to be sorted lexicographically. - size_t remove_keys_not_in(const DynamicPrintConfig &default_config, std::string &removed_keys_message); - // Verify whether the opt_key has not been obsoleted or renamed. // Both opt_key and value may be modified by handle_legacy(). // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). @@ -514,7 +523,7 @@ public: ConfigOptionInt top_solid_layers; ConfigOptionFloatOrPercent top_solid_infill_speed; ConfigOptionBool wipe_into_infill; - + protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -681,6 +690,7 @@ public: ConfigOptionBool variable_layer_height; ConfigOptionFloat cooling_tube_retraction; ConfigOptionFloat cooling_tube_length; + ConfigOptionBool high_current_on_filament_swap; ConfigOptionFloat parking_pos_retraction; ConfigOptionBool remaining_times; ConfigOptionBool silent_mode; @@ -750,6 +760,7 @@ protected: OPT_PTR(variable_layer_height); OPT_PTR(cooling_tube_retraction); OPT_PTR(cooling_tube_length); + OPT_PTR(high_current_on_filament_swap); OPT_PTR(parking_pos_retraction); OPT_PTR(remaining_times); OPT_PTR(silent_mode); @@ -774,6 +785,7 @@ public: ConfigOptionInts top_fan_speed; ConfigOptionFloat brim_width; ConfigOptionBool complete_objects; + ConfigOptionFloats colorprint_heights; ConfigOptionBools cooling; ConfigOptionFloat default_acceleration; ConfigOptionInts disable_fan_first_layers; @@ -837,7 +849,7 @@ public: ConfigOptionInt pixel_height; ConfigOptionFloat exp_time; ConfigOptionFloat exp_time_first; - + protected: PrintConfig(int) : GCodeConfig(1) {} void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -852,6 +864,7 @@ protected: OPT_PTR(top_fan_speed); OPT_PTR(brim_width); OPT_PTR(complete_objects); + OPT_PTR(colorprint_heights); OPT_PTR(cooling); OPT_PTR(default_acceleration); OPT_PTR(disable_fan_first_layers); @@ -928,7 +941,7 @@ public: ConfigOptionString printhost_cafile; ConfigOptionString serial_port; ConfigOptionInt serial_speed; - + protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -942,8 +955,8 @@ protected: }; // This object is mapped to Perl as Slic3r::Config::Full. -class FullPrintConfig : - public PrintObjectConfig, +class FullPrintConfig : + public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig @@ -967,11 +980,116 @@ protected: } }; +// This object is mapped to Perl as Slic3r::Config::PrintRegion. +class SLAPrintConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAPrintConfig) +public: + ConfigOptionString output_filename_format; + +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(output_filename_format); + } +}; + +class SLAPrintObjectConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAPrintObjectConfig) +public: + ConfigOptionFloat layer_height; + + // Enabling or disabling support creation + ConfigOptionBool supports_enable; + + // Diameter in mm of the pointing side of the head. + ConfigOptionFloat support_head_front_diameter /*= 0.2*/; + + // How much the pinhead has to penetrate the model surface + ConfigOptionFloat support_head_penetration /*= 0.2*/; + + // Width in mm from the back sphere center to the front sphere center. + ConfigOptionFloat support_head_width /*= 1.0*/; + + // Radius in mm of the support pillars. + ConfigOptionFloat support_pillar_diameter /*= 0.8*/; + + // TODO: unimplemented at the moment. This coefficient will have an impact + // when bridges and pillars are merged. The resulting pillar should be a bit + // thicker than the ones merging into it. How much thicker? I don't know + // but it will be derived from this value. + ConfigOptionFloat support_pillar_widening_factor; + + // Radius in mm of the pillar base. + ConfigOptionFloat support_base_diameter /*= 2.0*/; + + // The height of the pillar base cone in mm. + ConfigOptionFloat support_base_height /*= 1.0*/; + + // The default angle for connecting support sticks and junctions. + ConfigOptionFloat support_critical_angle /*= 45*/; + + // The max length of a bridge in mm + ConfigOptionFloat support_max_bridge_length /*= 15.0*/; + + // The elevation in Z direction upwards. This is the space between the pad + // and the model object's bounding box bottom. Units in mm. + ConfigOptionFloat support_object_elevation /*= 5.0*/; + + /////// Following options influence automatic support points placement: + ConfigOptionInt support_density_at_horizontal; + ConfigOptionInt support_density_at_45; + ConfigOptionFloat support_minimal_z; + + // Now for the base pool (pad) ///////////////////////////////////////////// + + // Enabling or disabling support creation + ConfigOptionBool pad_enable; + + // The thickness of the pad walls + ConfigOptionFloat pad_wall_thickness /*= 2*/; + + // The height of the pad from the bottom to the top not considering the pit + ConfigOptionFloat pad_wall_height /*= 5*/; + + // The greatest distance where two individual pads are merged into one. The + // distance is measured roughly from the centroids of the pads. + ConfigOptionFloat pad_max_merge_distance /*= 50*/; + + // The smoothing radius of the pad edges + ConfigOptionFloat pad_edge_radius /*= 1*/; + +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(layer_height); + OPT_PTR(supports_enable); + OPT_PTR(support_head_front_diameter); + OPT_PTR(support_head_penetration); + OPT_PTR(support_head_width); + OPT_PTR(support_pillar_diameter); + OPT_PTR(support_pillar_widening_factor); + OPT_PTR(support_base_diameter); + OPT_PTR(support_base_height); + OPT_PTR(support_critical_angle); + OPT_PTR(support_max_bridge_length); + OPT_PTR(support_density_at_horizontal); + OPT_PTR(support_density_at_45); + OPT_PTR(support_minimal_z); + OPT_PTR(support_object_elevation); + OPT_PTR(pad_enable); + OPT_PTR(pad_wall_thickness); + OPT_PTR(pad_wall_height); + OPT_PTR(pad_max_merge_distance); + OPT_PTR(pad_edge_radius); + } +}; + class SLAMaterialConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) public: - ConfigOptionFloat layer_height; ConfigOptionFloat initial_layer_height; ConfigOptionFloat exposure_time; ConfigOptionFloat initial_exposure_time; @@ -980,7 +1098,6 @@ public: protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { - OPT_PTR(layer_height); OPT_PTR(initial_layer_height); OPT_PTR(exposure_time); OPT_PTR(initial_exposure_time); @@ -1000,6 +1117,7 @@ public: ConfigOptionFloat display_height; ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_y; + ConfigOptionEnum display_orientation; ConfigOptionFloats printer_correction; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1011,14 +1129,15 @@ protected: OPT_PTR(display_height); OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_y); + OPT_PTR(display_orientation); OPT_PTR(printer_correction); } }; -class SLAFullPrintConfig : public SLAPrinterConfig, public SLAMaterialConfig +class SLAFullPrintConfig : public SLAPrinterConfig, public SLAPrintConfig, public SLAPrintObjectConfig, public SLAMaterialConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig) - SLAFullPrintConfig() : SLAPrinterConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } + SLAFullPrintConfig() : SLAPrinterConfig(0), SLAPrintConfig(0), SLAPrintObjectConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } public: // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned. @@ -1026,11 +1145,13 @@ public: protected: // Protected constructor to be called to initialize ConfigCache::m_default. - SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAMaterialConfig(0) {} + SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAPrintConfig(0), SLAPrintObjectConfig(0), SLAMaterialConfig(0) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { - this->SLAPrinterConfig ::initialize(cache, base_ptr); - this->SLAMaterialConfig::initialize(cache, base_ptr); + this->SLAPrinterConfig ::initialize(cache, base_ptr); + this->SLAPrintConfig ::initialize(cache, base_ptr); + this->SLAPrintObjectConfig::initialize(cache, base_ptr); + this->SLAMaterialConfig ::initialize(cache, base_ptr); } }; @@ -1039,6 +1160,102 @@ protected: #undef STATIC_PRINT_CONFIG_CACHE_DERIVED #undef OPT_PTR -} +class CLIConfigDef : public ConfigDef +{ +public: + CLIConfigDef(); +}; + +extern const CLIConfigDef cli_config_def; + +#define OPT_PTR(KEY) if (opt_key == #KEY) return &this->KEY + +class CLIConfig : public virtual ConfigBase, public StaticConfig +{ +public: + ConfigOptionFloat cut; + ConfigOptionString datadir; + ConfigOptionBool dont_arrange; + ConfigOptionBool export_3mf; + ConfigOptionBool gui; + ConfigOptionBool info; + ConfigOptionBool help; + ConfigOptionStrings load; + ConfigOptionBool no_gui; + ConfigOptionString output; + ConfigOptionPoint print_center; + ConfigOptionFloat rotate; + ConfigOptionFloat rotate_x; + ConfigOptionFloat rotate_y; + ConfigOptionString save; + ConfigOptionFloat scale; +// ConfigOptionPoint3 scale_to_fit; + ConfigOptionBool slice; + + CLIConfig() : ConfigBase(), StaticConfig() + { + this->set_defaults(); + }; + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef* def() const override { return &cli_config_def; } + t_config_option_keys keys() const override { return cli_config_def.keys(); } + + ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override + { + OPT_PTR(cut); + OPT_PTR(datadir); + OPT_PTR(dont_arrange); + OPT_PTR(export_3mf); + OPT_PTR(gui); + OPT_PTR(help); + OPT_PTR(info); + OPT_PTR(load); + OPT_PTR(no_gui); + OPT_PTR(output); + OPT_PTR(print_center); + OPT_PTR(rotate); + OPT_PTR(rotate_x); + OPT_PTR(rotate_y); + OPT_PTR(save); + OPT_PTR(scale); +// OPT_PTR(scale_to_fit); + OPT_PTR(slice); + return NULL; + } +}; + +#undef OPT_PTR + +class DynamicPrintAndCLIConfig : public DynamicPrintConfig +{ +public: + DynamicPrintAndCLIConfig() { this->apply(FullPrintConfig::defaults()); this->apply(CLIConfig()); } + DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef* def() const override { return &s_def; } + t_config_option_keys keys() const override { return s_def.keys(); } + +private: + class PrintAndCLIConfigDef : public ConfigDef + { + public: + PrintAndCLIConfigDef() { + this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); + this->options.insert(cli_config_def.options.begin(), cli_config_def.options.end()); + } + // Do not release the default values, they are handled by print_config_def & cli_config_def. + ~PrintAndCLIConfigDef() { this->options.clear(); } + }; + static PrintAndCLIConfigDef s_def; +}; + +/// Iterate through all of the print options and write them to a stream. +std::ostream& print_print_options(std::ostream& out); +/// Iterate through all of the CLI options and write them to a stream. +std::ostream& print_cli_options(std::ostream& out); + +} // namespace Slic3r #endif diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 7c3871251..5cfb55217 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -1,26 +1,21 @@ #ifndef PRINTEXPORT_HPP #define PRINTEXPORT_HPP -#include "Print.hpp" - // For png export of the sliced model #include #include - -#include -#include -#include +#include #include #include "Rasterizer/Rasterizer.hpp" -#include -#include //#include "tbb/mutex.h" +//#include +//#include //#include "tbb/mutex.h" namespace Slic3r { enum class FilePrinterFormat { - PNG, + SLA_PNGZIP, SVG }; @@ -36,10 +31,8 @@ template class FilePrinter { public: - void printConfig(const Print&); - // Draw an ExPolygon which is a polygon inside a slice on the specified layer. - void drawPolygon(const ExPolygon& p, unsigned lyr); + void draw_polygon(const ExPolygon& p, unsigned lyr); // Tell the printer how many layers should it consider. void layers(unsigned layernum); @@ -51,33 +44,60 @@ public: * specified layer number than an appropriate number of layers will be * allocated in the printer. */ - void beginLayer(unsigned layer); + void begin_layer(unsigned layer); // Allocate a new layer on top of the last and switch to it. - void beginLayer(); + void begin_layer(); /* * Finish the selected layer. It means that no drawing is allowed on that * layer anymore. This fact can be used to prepare the file system output * data like png comprimation and so on. */ - void finishLayer(unsigned layer); + void finish_layer(unsigned layer); // Finish the top layer. - void finishLayer(); + void finish_layer(); // Save all the layers into the file (or dir) specified in the path argument void save(const std::string& path); // Save only the selected layer to the file specified in path argument. - void saveLayer(unsigned lyr, const std::string& path); + void save_layer(unsigned lyr, const std::string& path); +}; + +// Provokes static_assert in the right way. +template struct VeryFalse { static const bool value = false; }; + +// This has to be explicitly implemented in the gui layer or a default zlib +// based implementation is needed. I don't have time for that and I'm delegating +// the implementation to the gui layer where the gui toolkit can cover this. +template class LayerWriter { +public: + + LayerWriter(const std::string& /*zipfile_path*/) { + static_assert(VeryFalse::value, + "No layer writer implementation provided!"); + } + + void next_entry(const std::string& /*fname*/) {} + + std::string get_name() { return ""; } + + bool is_ok() { return false; } + + template LayerWriter& operator<<(const T& /*arg*/) { + return *this; + } + + void close() {} }; // Implementation for PNG raster output // Be aware that if a large number of layers are allocated, it can very well // exhaust the available memory especially on 32 bit platform. -template<> class FilePrinter { - +template<> class FilePrinter +{ struct Layer { Raster first; std::stringstream second; @@ -91,22 +111,21 @@ template<> class FilePrinter { // We will save the compressed PNG data into stringstreams which can be done // in parallel. Later we can write every layer to the disk sequentially. - std::vector layers_rst_; - Raster::Resolution res_; - Raster::PixelDim pxdim_; - const Print *print_ = nullptr; - double exp_time_s_ = .0, exp_time_first_s_ = .0; + std::vector m_layers_rst; + Raster::Resolution m_res; + Raster::PixelDim m_pxdim; + double m_exp_time_s = .0, m_exp_time_first_s = .0; + double m_layer_height = .0; + Raster::Origin m_o = Raster::Origin::TOP_LEFT; std::string createIniContent(const std::string& projectname) { - double layer_height = print_? - print_->default_object_config().layer_height.getFloat() : - 0.05; + double layer_height = m_layer_height; using std::string; using std::to_string; - auto expt_str = to_string(exp_time_s_); - auto expt_first_str = to_string(exp_time_first_s_); + auto expt_str = to_string(m_exp_time_s); + auto expt_first_str = to_string(m_exp_time_first_s); auto stepnum_str = to_string(static_cast(800*layer_height)); auto layerh_str = to_string(layer_height); @@ -127,99 +146,122 @@ template<> class FilePrinter { +layerh_str+"+printer=DWARF3\n"; } - // Change this to TOP_LEFT if you want correct PNG orientation - static const Raster::Origin ORIGIN = Raster::Origin::BOTTOM_LEFT; - public: + + enum RasterOrientation { + RO_LANDSCAPE, + RO_PORTRAIT + }; + + // We will play with the raster's coordinate origin parameter. When the + // printer should print in landscape mode it should have the Y axis flipped + // because the layers should be displayed upside down. PNG has its + // coordinate origin in the top-left corner so normally the Raster objects + // should be instantiated with the TOP_LEFT flag. However, in landscape mode + // we do want the pictures to be upside down so we will make BOTTOM_LEFT + // type rasters and the PNG format will do the flipping automatically. + + // In case of portrait images, we have to rotate the image by a 90 degrees + // and flip the y axis. To get the correct upside-down orientation of the + // slice images, we can flip the x and y coordinates of the input polygons + // and do the Y flipping of the image. This will generate the correct + // orientation in portrait mode. + inline FilePrinter(double width_mm, double height_mm, unsigned width_px, unsigned height_px, - double exp_time, double exp_time_first): - res_(width_px, height_px), - pxdim_(width_mm/width_px, height_mm/height_px), - exp_time_s_(exp_time), - exp_time_first_s_(exp_time_first) + double layer_height, + double exp_time, double exp_time_first, + RasterOrientation ro = RO_PORTRAIT): + m_res(width_px, height_px), + m_pxdim(width_mm/width_px, height_mm/height_px), + m_exp_time_s(exp_time), + m_exp_time_first_s(exp_time_first), + m_layer_height(layer_height), + + // Here is the trick with the orientation. + m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : + Raster::Origin::TOP_LEFT ) { } FilePrinter(const FilePrinter& ) = delete; FilePrinter(FilePrinter&& m): - layers_rst_(std::move(m.layers_rst_)), - res_(m.res_), - pxdim_(m.pxdim_) {} + m_layers_rst(std::move(m.m_layers_rst)), + m_res(m.m_res), + m_pxdim(m.m_pxdim) {} - inline void layers(unsigned cnt) { if(cnt > 0) layers_rst_.resize(cnt); } - inline unsigned layers() const { return layers_rst_.size(); } + inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } + inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - void printConfig(const Print& printconf) { print_ = &printconf; } - - inline void drawPolygon(const ExPolygon& p, unsigned lyr) { - assert(lyr < layers_rst_.size()); - layers_rst_[lyr].first.draw(p); + inline void draw_polygon(const ExPolygon& p, unsigned lyr) { + assert(lyr < m_layers_rst.size()); + m_layers_rst[lyr].first.draw(p); } - inline void beginLayer(unsigned lyr) { - if(layers_rst_.size() <= lyr) layers_rst_.resize(lyr+1); - layers_rst_[lyr].first.reset(res_, pxdim_, ORIGIN); + inline void begin_layer(unsigned lyr) { + if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); + m_layers_rst[lyr].first.reset(m_res, m_pxdim, m_o); } - inline void beginLayer() { - layers_rst_.emplace_back(); - layers_rst_.front().first.reset(res_, pxdim_, ORIGIN); + inline void begin_layer() { + m_layers_rst.emplace_back(); + m_layers_rst.front().first.reset(m_res, m_pxdim, m_o); } - inline void finishLayer(unsigned lyr_id) { - assert(lyr_id < layers_rst_.size()); - layers_rst_[lyr_id].first.save(layers_rst_[lyr_id].second, + inline void finish_layer(unsigned lyr_id) { + assert(lyr_id < m_layers_rst.size()); + m_layers_rst[lyr_id].first.save(m_layers_rst[lyr_id].second, Raster::Compression::PNG); - layers_rst_[lyr_id].first.reset(); + m_layers_rst[lyr_id].first.reset(); } - inline void finishLayer() { - if(!layers_rst_.empty()) { - layers_rst_.back().first.save(layers_rst_.back().second, + inline void finish_layer() { + if(!m_layers_rst.empty()) { + m_layers_rst.back().first.save(m_layers_rst.back().second, Raster::Compression::PNG); - layers_rst_.back().first.reset(); + m_layers_rst.back().first.reset(); } } + template inline void save(const std::string& path) { + try { + LayerWriter writer(path); + if(!writer.is_ok()) return; - wxFileName filepath(path); + std::string project = writer.get_name(); - wxFFileOutputStream zipfile(path); + writer.next_entry("config.ini"); + if(!writer.is_ok()) return; - std::string project = filepath.GetName().ToStdString(); + writer << createIniContent(project); - if(!zipfile.IsOk()) { - BOOST_LOG_TRIVIAL(error) << "Can't create zip file for layers! " - << path; - return; - } + for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++) + { + if(m_layers_rst[i].second.rdbuf()->in_avail() > 0) { + char lyrnum[6]; + std::sprintf(lyrnum, "%.5d", i); + auto zfilename = project + lyrnum + ".png"; + writer.next_entry(zfilename); - wxZipOutputStream zipstream(zipfile); - wxStdOutputStream pngstream(zipstream); + if(!writer.is_ok()) break; - zipstream.PutNextEntry("config.ini"); - pngstream << createIniContent(project); - - for(unsigned i = 0; i < layers_rst_.size(); i++) { - if(layers_rst_[i].second.rdbuf()->in_avail() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - zipstream.PutNextEntry(zfilename); - pngstream << layers_rst_[i].second.rdbuf(); - layers_rst_[i].second.str(""); + writer << m_layers_rst[i].second.str(); + // writer << m_layers_rst[i].second.rdbuf(); + // we can keep the date for later calls of this method + //m_layers_rst[i].second.str(""); + } } + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; } - - zipstream.Close(); - zipfile.Close(); } - void saveLayer(unsigned lyr, const std::string& path) { + void save_layer(unsigned lyr, const std::string& path) { unsigned i = lyr; - assert(i < layers_rst_.size()); + assert(i < m_layers_rst.size()); char lyrnum[6]; std::sprintf(lyrnum, "%.5d", lyr); @@ -227,157 +269,16 @@ public: std::fstream out(loc, std::fstream::out | std::fstream::binary); if(out.good()) { - layers_rst_[i].first.save(out, Raster::Compression::PNG); + m_layers_rst[i].first.save(out, Raster::Compression::PNG); } else { BOOST_LOG_TRIVIAL(error) << "Can't create file for layer"; } out.close(); - layers_rst_[i].first.reset(); + m_layers_rst[i].first.reset(); } }; -// Let's shadow this eigen interface -inline coord_t px(const Point& p) { return p(0); } -inline coord_t py(const Point& p) { return p(1); } -inline coordf_t px(const Vec2d& p) { return p(0); } -inline coordf_t py(const Vec2d& p) { return p(1); } - -template -void print_to(Print& print, - std::string dirpath, - double width_mm, - double height_mm, - Args&&...args) -{ - - std::string& dir = dirpath; - - // This map will hold the layers sorted by z coordinate. Layers on the - // same height (from different objects) will be mapped to the same key and - // rasterized to the same image. - std::map layers; - - auto& objects = print.objects(); - - // Merge the sliced layers with the support layers - std::for_each(objects.cbegin(), objects.cend(), [&layers](const PrintObject *o) { - for(const auto l : o->layers()) { - auto& lyrs = layers[static_cast(scale_(l->print_z))]; - lyrs.push_back(l); - } - - for(const auto l : o->support_layers()) { - auto& lyrs = layers[static_cast(scale_(l->print_z))]; - lyrs.push_back(l); - } - }); - - auto print_bb = print.bounding_box(); - Vec2d punsc = unscale(print_bb.size()); - - // If the print does not fit into the print area we should cry about it. - if(px(punsc) > width_mm || py(punsc) > height_mm) { - BOOST_LOG_TRIVIAL(warning) << "Warning: Print will not fit!" << "\n" - << "Width needed: " << px(punsc) << "\n" - << "Height needed: " << py(punsc) << "\n"; - } - - // Offset for centering the print onto the print area - auto cx = scale_(width_mm)/2 - (px(print_bb.center()) - px(print_bb.min)); - auto cy = scale_(height_mm)/2 - (py(print_bb.center()) - py(print_bb.min)); - - // Create the actual printer, forward any additional arguments to it. - FilePrinter printer(width_mm, height_mm, - std::forward(args)...); - - printer.printConfig(print); - - printer.layers(layers.size()); // Allocate space for all the layers - - int st_prev = 0; - const std::string jobdesc = "Rasterizing and compressing sliced layers"; - tbb::spin_mutex m; - - std::vector keys; - keys.reserve(layers.size()); - for(auto& e : layers) keys.push_back(e.first); - - //FIXME - int initstatus = //print.progressindicator? print.progressindicator->state() : - 0; - print.set_status(initstatus, jobdesc); - - // Method that prints one layer - auto process_layer = [&layers, &keys, &printer, &st_prev, &m, - &jobdesc, print_bb, dir, cx, cy, &print, initstatus] - (unsigned layer_id) - { - LayerPtrs lrange = layers[keys[layer_id]]; - - printer.beginLayer(layer_id); // Switch to the appropriate layer - - for(Layer *lp : lrange) { - Layer& l = *lp; - - ExPolygonCollection slices = l.slices; // Copy the layer slices - - // Sort the polygons in the layer - std::stable_sort(slices.expolygons.begin(), slices.expolygons.end(), - [](const ExPolygon& a, const ExPolygon& b) { - return a.contour.contains(b.contour.first_point()) ? false : - true; - }); - - // Draw all the polygons in the slice to the actual layer. - for (const Point &d : l.object()->copies()) - for (ExPolygon slice : slices.expolygons) { - slice.translate(px(d), py(d)); - slice.translate(-px(print_bb.min) + cx, - -py(print_bb.min) + cy); - - printer.drawPolygon(slice, layer_id); - } - - /*if(print.has_support_material() && layer_id > 0) { - BOOST_LOG_TRIVIAL(warning) << "support material for layer " - << layer_id - << " defined but export is " - "not yet implemented."; - - }*/ - - } - - printer.finishLayer(layer_id); // Finish the layer for later saving it. - - auto st = static_cast(layer_id*80.0/layers.size()); - m.lock(); - if( st - st_prev > 10) { - print.set_status(initstatus + st, jobdesc); - st_prev = st; - } - m.unlock(); - - // printer.saveLayer(layer_id, dir); We could save the layer immediately - }; - - // Print all the layers in parallel - tbb::parallel_for(0, - layers.size(), - process_layer); - - // Sequential version (for testing) - // for(unsigned l = 0; l < layers.size(); ++l) process_layer(l); - -// print.set_status(100, jobdesc); - - // Save the print into the file system. - print.set_status(initstatus + 90, "Writing layers to disk"); - printer.save(dir); - print.set_status(initstatus + 100, "Writing layers completed"); -} - } #endif // PRINTEXPORT_HPP diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a9c2d0f90..835faed34 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -34,10 +34,9 @@ namespace Slic3r { -PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) : +PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) : + PrintObjectBaseWithState(print, model_object), typed_slices(false), - m_print(print), - m_model_object(model_object), size(Vec3crd::Zero()), layer_height_profile_valid(false) { @@ -49,82 +48,46 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding // don't assume it's already aligned and we don't alter the original position in model. // We store the XY translation so that we can place copies correctly in the output G-code // (copies are expressed in G-code coordinates and this translation is not publicly exposed). + const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box(); m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); // Scale the object size and store it this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast(); } - this->reload_model_instances(); - this->layer_height_ranges = model_object->layer_height_ranges; + if (add_instances) { + Points copies; + copies.reserve(m_model_object->instances.size()); + for (const ModelInstance *mi : m_model_object->instances) { + assert(mi->is_printable()); + const Vec3d& offset = mi->get_offset(); + copies.emplace_back(Point::new_scale(offset(0), offset(1))); + } + this->set_copies(copies); + } + this->layer_height_profile = model_object->layer_height_profile; } -void PrintObject::set_started(PrintObjectStep step) -{ - m_state.set_started(step, m_print->m_mutex); -} - -void PrintObject::set_done(PrintObjectStep step) -{ - m_state.set_done(step, m_print->m_mutex); -} - -bool PrintObject::add_copy(const Vec2d &point) -{ - tbb::mutex::scoped_lock lock(m_print->m_mutex); - Points points = m_copies; - points.push_back(Point::new_scale(point(0), point(1))); - return this->set_copies(points); -} - -bool PrintObject::delete_last_copy() -{ - tbb::mutex::scoped_lock lock(m_print->m_mutex); - Points points = m_copies; - points.pop_back(); - return this->set_copies(points); -} - bool PrintObject::set_copies(const Points &points) { - bool copies_num_changed = m_copies.size() != points.size(); - - // order copies with a nearest neighbor search and translate them by _copies_shift - m_copies.clear(); - m_copies.reserve(points.size()); - - // order copies with a nearest-neighbor search - std::vector ordered_copies; - Slic3r::Geometry::chained_path(points, ordered_copies); - - for (size_t point_idx : ordered_copies) - m_copies.push_back(points[point_idx] + m_copies_shift); - - bool invalidated = m_print->invalidate_step(psSkirt); - invalidated |= m_print->invalidate_step(psBrim); - if (copies_num_changed) - invalidated |= m_print->invalidate_step(psWipeTower); - return invalidated; -} - -bool PrintObject::reload_model_instances() -{ - Points copies; - copies.reserve(m_model_object->instances.size()); - for (const ModelInstance *mi : m_model_object->instances) + // Order copies with a nearest-neighbor search. + std::vector copies; { -#if ENABLE_MODELINSTANCE_3D_OFFSET - if (mi->is_printable()) - { - const Vec3d& offset = mi->get_offset(); - copies.emplace_back(Point::new_scale(offset(0), offset(1))); - } -#else - if (mi->is_printable()) - copies.emplace_back(Point::new_scale(mi->offset(0), mi->offset(1))); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + std::vector ordered_copies; + Slic3r::Geometry::chained_path(points, ordered_copies); + copies.reserve(ordered_copies.size()); + for (size_t point_idx : ordered_copies) + copies.emplace_back(points[point_idx] + m_copies_shift); } - return this->set_copies(copies); + // Invalidate and set copies. + bool invalidated = false; + if (copies != m_copies) { + invalidated = m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }); + if (copies.size() != m_copies.size()) + invalidated |= m_print->invalidate_step(psWipeTower); + m_copies = copies; + } + return invalidated; } // 1) Decides Z positions of the layers, @@ -138,9 +101,8 @@ bool PrintObject::reload_model_instances() // this should be idempotent void PrintObject::slice() { - if (this->is_step_done(posSlice)) + if (! this->set_started(posSlice)) return; - this->set_started(posSlice); m_print->set_status(10, "Processing triangulated mesh"); this->_slice(); m_print->throw_if_canceled(); @@ -166,17 +128,16 @@ void PrintObject::make_perimeters() // prerequisites this->slice(); - if (this->is_step_done(posPerimeters)) + if (! this->set_started(posPerimeters)) return; - this->set_started(posPerimeters); m_print->set_status(20, "Generating perimeters"); BOOST_LOG_TRIVIAL(info) << "Generating perimeters..."; // merge slices if they were split into types if (this->typed_slices) { - FOREACH_LAYER(this, layer_it) { - (*layer_it)->merge_slices(); + for (Layer *layer : m_layers) { + layer->merge_slices(); m_print->throw_if_canceled(); } this->typed_slices = false; @@ -189,8 +150,8 @@ void PrintObject::make_perimeters() // but we don't generate any extra perimeter if fill density is zero, as they would be floating // inside the object - infill_only_where_needed should be the method of choice for printing // hollow objects - for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const PrintRegion ®ion = *m_print->regions()[region_id]; if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) continue; @@ -277,10 +238,9 @@ void PrintObject::make_perimeters() void PrintObject::prepare_infill() { - if (this->is_step_done(posPrepareInfill)) + if (! this->set_started(posPrepareInfill)) return; - this->set_started(posPrepareInfill); m_print->set_status(30, "Preparing infill"); // This will assign a type (top/bottom/internal) to $layerm->slices. @@ -318,7 +278,7 @@ void PrintObject::prepare_infill() // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final"); @@ -337,7 +297,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final"); @@ -356,7 +316,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final"); @@ -375,7 +335,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("9_prepare_infill-final"); @@ -393,14 +353,10 @@ void PrintObject::prepare_infill() void PrintObject::infill() { - if (! this->is_printable()) - return; - // prerequisites this->prepare_infill(); - if (! this->is_step_done(posInfill)) { - this->set_started(posInfill); + if (this->set_started(posInfill)) { BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -422,13 +378,20 @@ void PrintObject::infill() void PrintObject::generate_support_material() { - if (! this->is_step_done(posSupportMaterial)) { - this->set_started(posSupportMaterial); + if (this->set_started(posSupportMaterial)) { this->clear_support_layers(); if ((m_config.support_material || m_config.raft_layers > 0) && m_layers.size() > 1) { m_print->set_status(85, "Generating support material"); this->_generate_support_material(); m_print->throw_if_canceled(); + } else { +#if 0 + // Printing without supports. Empty layer means some objects or object parts are levitating, + // therefore they cannot be printed without supports. + for (const Layer *layer : m_layers) + if (layer->empty()) + throw std::runtime_error("Levitating objects cannot be printed without supports."); +#endif } this->set_done(posSupportMaterial); } @@ -584,11 +547,13 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorinvalidate_step(psGCodeExport); + } else if ( + opt_key == "wipe_into_infill" + || opt_key == "wipe_into_objects") { + invalidated |= m_print->invalidate_step(psWipeTower); + invalidated |= m_print->invalidate_step(psGCodeExport); } else { // for legacy, if we can't handle this option let's invalidate all steps this->invalidate_all_steps(); @@ -605,37 +570,34 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorm_mutex, m_print->m_cancel_callback); + bool invalidated = Inherited::invalidate_step(step); // propagate to dependent steps if (step == posPerimeters) { invalidated |= this->invalidate_step(posPrepareInfill); - invalidated |= m_print->invalidate_step(psSkirt); - invalidated |= m_print->invalidate_step(psBrim); + invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); } else if (step == posPrepareInfill) { invalidated |= this->invalidate_step(posInfill); } else if (step == posInfill) { - invalidated |= m_print->invalidate_step(psSkirt); - invalidated |= m_print->invalidate_step(psBrim); + invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); } else if (step == posSlice) { - invalidated |= this->invalidate_step(posPerimeters); - invalidated |= this->invalidate_step(posSupportMaterial); + invalidated |= this->invalidate_steps({ posPerimeters, posSupportMaterial }); invalidated |= m_print->invalidate_step(psWipeTower); - } else if (step == posSupportMaterial) { - invalidated |= m_print->invalidate_step(psSkirt); - invalidated |= m_print->invalidate_step(psBrim); - } + } else if (step == posSupportMaterial) + invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); // Wipe tower depends on the ordering of extruders, which in turn depends on everything. // It also decides about what the wipe_into_infill / wipe_into_object features will do, // and that too depends on many of the settings. invalidated |= m_print->invalidate_step(psWipeTower); + // Invalidate G-code export in any case. + invalidated |= m_print->invalidate_step(psGCodeExport); return invalidated; } -bool PrintObject::invalidate_all_steps() -{ - return m_state.invalidate_all(m_print->m_mutex, m_print->m_cancel_callback); +bool PrintObject::invalidate_all_steps() +{ + return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); } bool PrintObject::has_support_material() const @@ -735,18 +697,18 @@ void PrintObject::count_distance_solid() { // sparse area = layer's fill area - solid area const float COEFF_SPLIT = 2; const int NB_DENSE_LAYERS = 1; - for (int idx_region = 0; idx_region < this->_print->regions.size(); ++idx_region) { + for (size_t idx_region = 0; idx_region < this->m_print->regions().size(); ++idx_region) { //count how many surface there are on each one LayerRegion *previousOne = NULL; - if (this->layers.size() > 1) previousOne = this->layers[this->layers.size() - 1]->get_region(idx_region); - if (previousOne != NULL && previousOne->region()->config.infill_dense.getBool() && previousOne->region()->config.fill_density<40) { + if (this->layers().size() > 1) previousOne = this->layers()[this->layers().size() - 1]->get_region(idx_region); + if (previousOne != NULL && previousOne->region()->config().infill_dense.getBool() && previousOne->region()->config().fill_density<40) { for (Surface &surf : previousOne->fill_surfaces.surfaces) { if (surf.is_solid()) { surf.maxNbSolidLayersOnTop = 0; } } - for (int idx_layer = this->layers.size() - 2; idx_layer >= 0; --idx_layer){ - LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); + for (size_t idx_layer = this->layers().size() - 2; idx_layer >= 0; --idx_layer){ + LayerRegion *layerm = this->layers()[idx_layer]->get_region(idx_region); Surfaces surf_to_add; for (auto it_surf = layerm->fill_surfaces.surfaces.begin(); it_surf != layerm->fill_surfaces.surfaces.end(); ++it_surf) { Surface &surf = *it_surf; @@ -761,7 +723,7 @@ void PrintObject::count_distance_solid() { // upp.expolygon.overlaps(surf.expolygon) or surf.expolygon.overlaps(upp.expolygon) ExPolygons intersect = intersection_ex(sparse_polys, offset_ex(upp.expolygon, -layerm->flow(frInfill).scaled_width()), true); if (!intersect.empty()) { - if (layerm->region()->config.infill_dense_algo == dfaEnlarged) { + if (layerm->region()->config().infill_dense_algo == dfaEnlarged) { uint16_t dist = (uint16_t)(upp.maxNbSolidLayersOnTop + 1); const int nb_dense_layers = 1; if (dist <= nb_dense_layers) { @@ -769,11 +731,11 @@ void PrintObject::count_distance_solid() { uint64_t area_intersect = 0; for (ExPolygon poly_inter : intersect) area_intersect += poly_inter.area(); //if it's in a dense area and the current surface isn't a dense one yet and the not-dense is too small. - std::cout << idx_layer << " dfaEnlarged: " << layerm->region()->config.infill_dense_algo << "\n"; - std::cout << idx_layer << " dfaEnlarged: 1-" << (layerm->region()->config.infill_dense_algo == dfaEnlarged) << "\n"; + std::cout << idx_layer << " dfaEnlarged: " << layerm->region()->config().infill_dense_algo << "\n"; + std::cout << idx_layer << " dfaEnlarged: 1-" << (layerm->region()->config().infill_dense_algo == dfaEnlarged) << "\n"; std::cout << idx_layer << " dfaEnlarged: 2-" << (surf.area() > area_intersect * COEFF_SPLIT) << "\n"; std::cout << idx_layer << " dfaEnlarged: 3-" << (surf.maxNbSolidLayersOnTop > nb_dense_layers) << "\n"; - std::cout << idx_layer << " dfaEnlarged: surf.area()=" << unscale(unscale(surf.area())) << ", area_intersect=" << unscale(unscale(area_intersect)) << "\n"; + std::cout << idx_layer << " dfaEnlarged: surf.area()=" << unscale(unscale(surf.area())) << ", area_intersect=" << unscale(unscale(area_intersect)) << "\n"; std::cout << idx_layer << " dfaEnlarged: surf.maxNbSolidLayersOnTop=" << surf.maxNbSolidLayersOnTop << ", NB_DENSE_LAYERS=" << NB_DENSE_LAYERS << "\n"; if ((surf.area() > area_intersect * COEFF_SPLIT) && surf.maxNbSolidLayersOnTop > nb_dense_layers) { @@ -782,8 +744,7 @@ void PrintObject::count_distance_solid() { if (dist == 1) { //if just under the solid area, we can expand a bit //remove too small sections and grew a bit to anchor it into the part - intersect = offset_ex(intersect, - layerm->flow(frInfill).scaled_width() + scale_(layerm->region()->config.bridged_infill_margin)); + intersect = offset_ex(intersect, layerm->flow(frInfill).scaled_width() + scale_(layerm->region()->config().bridged_infill_margin)); } else { //just remove too small sections intersect = offset_ex(intersect, @@ -807,7 +768,7 @@ void PrintObject::count_distance_solid() { } else { surf.maxNbSolidLayersOnTop = std::min(surf.maxNbSolidLayersOnTop, dist); } - } else if (layerm->region()->config.infill_dense_algo == dfaAutoNotFull || layerm->region()->config.infill_dense_algo == dfaAutomatic) { + } else if (layerm->region()->config().infill_dense_algo == dfaAutoNotFull || layerm->region()->config().infill_dense_algo == dfaAutomatic) { double area_intersect = 0; for (ExPolygon poly_inter : intersect) area_intersect += poly_inter.area(); //like intersect.empty() but more resilient @@ -816,7 +777,7 @@ void PrintObject::count_distance_solid() { if (dist <= NB_DENSE_LAYERS) { // it will be a dense infill, split the surface if needed //if the not-dense is too big to do a full dense and the current surface isn't a dense one yet. - if ((layerm->region()->config.infill_dense_algo == dfaAutomatic || surf.area() > area_intersect * COEFF_SPLIT) && + if ((layerm->region()->config().infill_dense_algo == dfaAutomatic || surf.area() > area_intersect * COEFF_SPLIT) && surf.maxNbSolidLayersOnTop > NB_DENSE_LAYERS) { //split in two if (dist == 1) { @@ -832,7 +793,7 @@ void PrintObject::count_distance_solid() { } intersect = offset_ex(cover_intersect, layerm->flow(frInfill).scaled_width());// +scale_(expandby)); - //layerm->region()->config.external_infill_margin)); + //layerm->region()->config().external_infill_margin)); } else { //just remove too small sections intersect = offset_ex(intersect, @@ -858,10 +819,10 @@ void PrintObject::count_distance_solid() { surf.maxNbSolidLayersOnTop = std::min(surf.maxNbSolidLayersOnTop, dist); } } - } else if (layerm->region()->config.infill_dense_algo == dfaAutomatic) { + } else if (layerm->region()->config().infill_dense_algo == dfaAutomatic) { double area_intersect = 0; for (ExPolygon poly_inter : intersect) area_intersect += poly_inter.area(); - std::cout << idx_layer << " dfaAutomatic: area_intersect=" << unscale(unscale(area_intersect)) << "\n"; + std::cout << idx_layer << " dfaAutomatic: area_intersect=" << unscale(unscale(area_intersect)) << "\n"; //like intersect.empty() but more resilient if (area_intersect > layerm->flow(frInfill).scaled_width() * layerm->flow(frInfill).scaled_width() * 2) { std::cout << idx_layer << " dfaAutomatic: ok\n"; @@ -887,7 +848,7 @@ void PrintObject::count_distance_solid() { } intersect = offset_ex(cover_intersect, layerm->flow(frInfill).scaled_width());// +scale_(expandby)); - //layerm->region()->config.external_infill_margin)); + //layerm->region()->config().external_infill_margin)); } else { std::cout << "dfaAutomatic: remove too small sections\n"; //just remove too small sections @@ -984,7 +945,7 @@ void PrintObject::detect_surfaces_type() // should be visible. bool interface_shells = m_config.interface_shells.value; - for (int idx_region = 0; idx_region < m_print->m_regions.size(); ++ idx_region) { + for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) @@ -1018,7 +979,7 @@ void PrintObject::detect_surfaces_type() // unless internal shells are requested Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? m_layers[idx_layer + 1] : nullptr; Layer *lower_layer = (idx_layer > 0) ? m_layers[idx_layer - 1] : nullptr; - Layer *under_lower_layer = (idx_layer > 1) ? this->layers[idx_layer - 2] : nullptr; + Layer *under_lower_layer = (idx_layer > 1) ? this->layers()[idx_layer - 2] : nullptr; // collapse very narrow parts (using the safety offset in the diff is not enough) float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; @@ -1171,7 +1132,7 @@ void PrintObject::process_external_surfaces() { BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..."; - for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { const PrintRegion ®ion = *m_print->regions()[region_id]; BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; @@ -1204,13 +1165,13 @@ void PrintObject::discover_vertical_shells() Polygons holes; }; std::vector cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry()); - bool top_bottom_surfaces_all_regions = m_print->regions().size() > 1 && ! m_config.interface_shells.value; + bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value; if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. // Is the "ensure vertical wall thickness" applicable to any region? bool has_extra_layers = false; - for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { const PrintRegion ®ion = *m_print->get_region(idx_region); if (region.config().ensure_vertical_shell_thickness.value && (region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) { @@ -1227,7 +1188,7 @@ void PrintObject::discover_vertical_shells() tbb::blocked_range(0, m_layers.size(), grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; - const size_t num_regions = m_print->regions().size(); + const size_t num_regions = this->region_volumes.size(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); const Layer &layer = *m_layers[idx_layer]; @@ -1288,7 +1249,7 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } - for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { PROFILE_BLOCK(discover_vertical_shells_region); const PrintRegion ®ion = *m_print->get_region(idx_region); @@ -1580,7 +1541,7 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill..."; - for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion ®ion = *m_print->regions()[region_id]; // skip bridging in case there are no voids @@ -1596,9 +1557,10 @@ void PrintObject::bridge_over_infill() *this ); - FOREACH_LAYER(this, layer_it) { + for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) { // skip first layer - if (layer_it == m_layers.begin()) continue; + if (layer_it == m_layers.begin()) + continue; Layer* layer = *layer_it; LayerRegion* layerm = layer->m_regions[region_id]; @@ -1624,8 +1586,8 @@ void PrintObject::bridge_over_infill() // iterate through regions and collect internal surfaces Polygons lower_internal; - FOREACH_LAYERREGION(lower_layer, lower_layerm_it) - (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); + for (LayerRegion *lower_layerm : lower_layer->m_regions) + lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); // intersect such lower internal surfaces with the candidate solid surfaces to_bridge_pp = intersection(to_bridge_pp, lower_internal); @@ -1711,15 +1673,16 @@ void PrintObject::replaceSurfaceType(SurfaceType st_to_replace, SurfaceType st_r { BOOST_LOG_TRIVIAL(info) << "overextrude over Bridge..."; - FOREACH_REGION(this->_print, region) { - size_t region_id = region - this->_print->regions.begin(); - - FOREACH_LAYER(this, layer_it) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { + const PrintRegion ®ion = *m_print->regions()[region_id]; + + for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++layer_it) { + // skip first layer - if (layer_it == this->layers.begin()) continue; + if (layer_it == this->layers().begin()) continue; Layer* layer = *layer_it; - LayerRegion* layerm = layer->regions[region_id]; + LayerRegion* layerm = layer->regions()[region_id]; // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; @@ -1756,24 +1719,25 @@ void PrintObject::replaceSurfaceType(SurfaceType st_to_replace, SurfaceType st_r Polygons to_overextrude_pp = internal_solid; // get previous layer - if (int(layer_it - this->layers.begin()) - 1 >= 0) { - const Layer* lower_layer = this->layers[int(layer_it - this->layers.begin()) - 1]; + if (int(layer_it - this->layers().begin()) - 1 >= 0) { + const Layer* lower_layer = this->layers()[int(layer_it - this->layers().begin()) - 1]; // iterate through regions and collect internal surfaces Polygons lower_internal; - FOREACH_LAYERREGION(lower_layer, lower_layerm_it){ + for (LayerRegion *lower_layerm : lower_layer->m_regions) { + lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); Polygons lower_internal_OK; Polygons lower_internal_Bridge; Polygons lower_internal_Over; - (*lower_layerm_it)->fill_surfaces.filter_by_type(st_replacement, &lower_internal_OK); - (*lower_layerm_it)->fill_surfaces.filter_by_type(st_under_it, &lower_internal_Bridge); - (*lower_layerm_it)->fill_surfaces.filter_by_type(st_to_replace, &lower_internal_Over); + lower_layerm->fill_surfaces.filter_by_type(st_replacement, &lower_internal_OK); + lower_layerm->fill_surfaces.filter_by_type(st_under_it, &lower_internal_Bridge); + lower_layerm->fill_surfaces.filter_by_type(st_to_replace, &lower_internal_Over); double okarea =0, bridgearea=0, overarea=0; for (ExPolygon &ex : union_ex(lower_internal_OK)) okarea+=ex.area(); for (ExPolygon &ex : union_ex(lower_internal_Bridge)) bridgearea+=ex.area(); for (ExPolygon &ex : union_ex(lower_internal_Over)) overarea+=ex.area(); - (*lower_layerm_it)->fill_surfaces.filter_by_type(st_under_it, &lower_internal); + lower_layerm->fill_surfaces.filter_by_type(st_under_it, &lower_internal); } double sumarea=0; for (ExPolygon &ex : union_ex(lower_internal)) sumarea+=ex.area(); @@ -1875,7 +1839,7 @@ bool PrintObject::update_layer_height_profile(std::vector &layer_heigh bool updated = false; // If the layer height profile is not set, try to use the one stored at the ModelObject. - if (layer_height_profile.empty() && layer_height_profile.data() != this->model_object()->layer_height_profile.data()) { + if (layer_height_profile.empty()) { layer_height_profile = this->model_object()->layer_height_profile; updated = true; } @@ -1892,10 +1856,9 @@ bool PrintObject::update_layer_height_profile(std::vector &layer_heigh if (layer_height_profile.empty()) { if (0) // if (this->layer_height_profile.empty()) - layer_height_profile = layer_height_profile_adaptive(slicing_params, this->layer_height_ranges, - this->model_object()->volumes); + layer_height_profile = layer_height_profile_adaptive(slicing_params, this->model_object()->layer_height_ranges, this->model_object()->volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->layer_height_ranges); + layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->model_object()->layer_height_ranges); updated = true; } return updated; @@ -1957,14 +1920,14 @@ void PrintObject::_slice() layer->lower_layer = prev; } // Make sure all layers contain layer region objects for all regions. - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) layer->add_region(this->print()->regions()[region_id]); prev = layer; } } // Slice all non-modifier volumes. - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); m_print->throw_if_canceled(); @@ -1976,14 +1939,14 @@ void PrintObject::_slice() } // Slice all modifier volumes. - if (this->print()->regions().size() > 1) { - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + if (this->region_volumes.size() > 1) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); m_print->throw_if_canceled(); // loop through the other regions and 'steal' the slices belonging to this one BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " start"; - for (size_t other_region_id = 0; other_region_id < this->print()->regions().size(); ++ other_region_id) { + for (size_t other_region_id = 0; other_region_id < this->region_volumes.size(); ++ other_region_id) { if (region_id == other_region_id) continue; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) { @@ -2010,10 +1973,8 @@ void PrintObject::_slice() BOOST_LOG_TRIVIAL(debug) << "Slicing objects - removing top empty layers"; while (! m_layers.empty()) { const Layer *layer = m_layers.back(); - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) - if (layer->m_regions[region_id] != nullptr && ! layer->m_regions[region_id]->slices.empty()) - // Non empty layer. - goto end; + if (! layer->empty()) + goto end; delete layer; m_layers.pop_back(); if (! m_layers.empty()) @@ -2032,7 +1993,7 @@ end: Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(m_config.xy_size_compensation.value)); - float hole_delta = float(scale_(this->config.hole_size_compensation.value)); + float hole_delta = float(scale_(this->config().hole_size_compensation.value)); if (layer_id == 0) delta += float(scale_(m_config.elefant_foot_compensation.value)); bool scale = delta != 0.f; @@ -2043,7 +2004,7 @@ end: LayerRegion *layerm = layer->m_regions.front(); layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stInternal); } - _offsetHoles(hole_delta, layer->regions.front()); + _offsetHoles(hole_delta, layer->regions().front()); } else if (scale || clip || hole_delta != 0.f) { // Multiple regions, growing, shrinking or just clipping one region by the other. // When clipping the regions, priority is given to the first regions. @@ -2132,7 +2093,7 @@ std::vector PrintObject::slice_support_enforcers() const std::vector zs; zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) - zs.emplace_back(l->slice_z); + zs.emplace_back((float)l->slice_z); return this->_slice_volumes(zs, volumes); } @@ -2145,7 +2106,7 @@ std::vector PrintObject::slice_support_blockers() const std::vector zs; zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) - zs.emplace_back(l->slice_z); + zs.emplace_back((float)l->slice_z); return this->_slice_volumes(zs, volumes); } @@ -2157,14 +2118,19 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. TriangleMesh mesh; for (const ModelVolume *v : volumes) - mesh.merge(v->mesh); +#if ENABLE_MODELVOLUME_TRANSFORM + { + TriangleMesh vol_mesh(v->mesh); + vol_mesh.transform(v->get_matrix()); + mesh.merge(vol_mesh); + } +#else + mesh.merge(v->mesh); +#endif // ENABLE_MODELVOLUME_TRANSFORM if (mesh.stl.stats.number_of_facets > 0) { - // transform mesh - // we ignore the per-instance transformations currently and only - // consider the first one - this->model_object()->instances.front()->transform_mesh(&mesh, true); - // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); + mesh.transform(m_trafo); + // apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); // perform actual slicing TriangleMeshSlicer mslicer; const Print *print = this->print(); @@ -2282,19 +2248,15 @@ void PrintObject::_simplify_slices(double distance) void PrintObject::_make_perimeters() { - if (!this->is_printable()) + if (! this->set_started(posPerimeters)) return; - if (this->is_step_done(posPerimeters)) - return; - this->set_started(posPerimeters); - BOOST_LOG_TRIVIAL(info) << "Generating perimeters..."; // merge slices if they were split into types if (this->typed_slices) { - FOREACH_LAYER(this, layer_it) - (*layer_it)->merge_slices(); + for (Layer *layer : m_layers) + layer->merge_slices(); this->typed_slices = false; this->invalidate_step(posPrepareInfill); } @@ -2306,7 +2268,7 @@ void PrintObject::_make_perimeters() // but we don't generate any extra perimeter if fill density is zero, as they would be floating // inside the object - infill_only_where_needed should be the method of choice for printing // hollow objects - for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion ®ion = *m_print->regions()[region_id]; if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) continue; @@ -2478,7 +2440,7 @@ void PrintObject::discover_horizontal_shells() { BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (int i = 0; i < int(m_layers.size()); ++ i) { m_print->throw_if_canceled(); LayerRegion *layerm = m_layers[i]->regions()[region_id]; @@ -2652,7 +2614,7 @@ void PrintObject::discover_horizontal_shells() } // for each region #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (const Layer *layer : m_layers) { const LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells"); @@ -2668,7 +2630,7 @@ void PrintObject::discover_horizontal_shells() void PrintObject::combine_infill() { // Work on each region separately. - for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion *region = this->print()->regions()[region_id]; const int every = region->config().infill_every_layers.value; if (every < 2 || region->config().fill_density == 0.) @@ -2776,9 +2738,6 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { - if (!this->is_printable()) - return; - PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); support_material.generate(*this); } diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/Rasterizer/Rasterizer.cpp index b0bf04343..621b76b08 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.cpp +++ b/src/libslic3r/Rasterizer/Rasterizer.cpp @@ -37,38 +37,39 @@ public: using Origin = Raster::Origin; private: - Raster::Resolution resolution_; - Raster::PixelDim pxdim_; - TBuffer buf_; - TRawBuffer rbuf_; - TPixelRenderer pixfmt_; - TRawRenderer raw_renderer_; - TRendererAA renderer_; - Origin o_; - std::function flipy_ = [](agg::path_storage&) {}; + Raster::Resolution m_resolution; + Raster::PixelDim m_pxdim; + TBuffer m_buf; + TRawBuffer m_rbuf; + TPixelRenderer m_pixfmt; + TRawRenderer m_raw_renderer; + TRendererAA m_renderer; + Origin m_o; + + inline void flipy(agg::path_storage& path) const { + path.flip_y(0, m_resolution.height_px); + } + public: + inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, Origin o): - resolution_(res), pxdim_(pd), - buf_(res.pixels()), - rbuf_(reinterpret_cast(buf_.data()), + m_resolution(res), m_pxdim(pd), + m_buf(res.pixels()), + m_rbuf(reinterpret_cast(m_buf.data()), res.width_px, res.height_px, - res.width_px*TPixelRenderer::num_components), - pixfmt_(rbuf_), - raw_renderer_(pixfmt_), - renderer_(raw_renderer_), - o_(o) + int(res.width_px*TPixelRenderer::num_components)), + m_pixfmt(m_rbuf), + m_raw_renderer(m_pixfmt), + m_renderer(m_raw_renderer), + m_o(o) { - renderer_.color(ColorWhite); + m_renderer.color(ColorWhite); // If we would like to play around with gamma // ras.gamma(agg::gamma_power(1.0)); clear(); - - if(o_ == Origin::TOP_LEFT) flipy_ = [this](agg::path_storage& path) { - path.flip_y(0, resolution_.height_px); - }; } void draw(const ExPolygon &poly) { @@ -76,35 +77,37 @@ public: agg::scanline_p8 scanlines; auto&& path = to_path(poly.contour); - flipy_(path); + + if(m_o == Origin::TOP_LEFT) flipy(path); + ras.add_path(path); for(auto h : poly.holes) { auto&& holepath = to_path(h); - flipy_(holepath); + if(m_o == Origin::TOP_LEFT) flipy(holepath); ras.add_path(holepath); } - agg::render_scanlines(ras, scanlines, renderer_); + agg::render_scanlines(ras, scanlines, m_renderer); } inline void clear() { - raw_renderer_.clear(ColorBlack); + m_raw_renderer.clear(ColorBlack); } - inline TBuffer& buffer() { return buf_; } + inline TBuffer& buffer() { return m_buf; } - inline const Raster::Resolution resolution() { return resolution_; } + inline const Raster::Resolution resolution() { return m_resolution; } - inline Origin origin() const /*noexcept*/ { return o_; } + inline Origin origin() const /*noexcept*/ { return m_o; } private: double getPx(const Point& p) { - return p(0) * SCALING_FACTOR/pxdim_.w_mm; + return p(0) * SCALING_FACTOR/m_pxdim.w_mm; } double getPy(const Point& p) { - return p(1) * SCALING_FACTOR/pxdim_.h_mm; + return p(1) * SCALING_FACTOR/m_pxdim.h_mm; } agg::path_storage to_path(const Polygon& poly) { @@ -124,57 +127,57 @@ const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o): - impl_(new Impl(r, pd, o)) {} + m_impl(new Impl(r, pd, o)) {} Raster::Raster() {} Raster::~Raster() {} Raster::Raster(Raster &&m): - impl_(std::move(m.impl_)) {} + m_impl(std::move(m.m_impl)) {} void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd) { // Free up the unnecessary memory and make sure it stays clear after // an exception - auto o = impl_? impl_->origin() : Origin::TOP_LEFT; + auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT; reset(r, pd, o); } void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, Raster::Origin o) { - impl_.reset(); - impl_.reset(new Impl(r, pd, o)); + m_impl.reset(); + m_impl.reset(new Impl(r, pd, o)); } void Raster::reset() { - impl_.reset(); + m_impl.reset(); } Raster::Resolution Raster::resolution() const { - if(impl_) return impl_->resolution(); + if(m_impl) return m_impl->resolution(); return Resolution(0, 0); } void Raster::clear() { - assert(impl_); - impl_->clear(); + assert(m_impl); + m_impl->clear(); } void Raster::draw(const ExPolygon &poly) { - assert(impl_); - impl_->draw(poly); + assert(m_impl); + m_impl->draw(poly); } void Raster::save(std::ostream& stream, Compression comp) { - assert(impl_); + assert(m_impl); switch(comp) { case Compression::PNG: { @@ -188,7 +191,7 @@ void Raster::save(std::ostream& stream, Compression comp) wr.write_info(); - auto& b = impl_->buffer(); + auto& b = m_impl->buffer(); auto ptr = reinterpret_cast( b.data() ); unsigned stride = sizeof(Impl::TBuffer::value_type) * resolution().width_px; @@ -201,12 +204,13 @@ void Raster::save(std::ostream& stream, Compression comp) } case Compression::RAW: { stream << "P5 " - << impl_->resolution().width_px << " " - << impl_->resolution().height_px << " " + << m_impl->resolution().width_px << " " + << m_impl->resolution().height_px << " " << "255 "; - stream.write(reinterpret_cast(impl_->buffer().data()), - impl_->buffer().size()*sizeof(Impl::TBuffer::value_type)); + auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); + stream.write(reinterpret_cast(m_impl->buffer().data()), + std::streamsize(sz)); } } } diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp index cbb39bc6b..06d5b88c6 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/Rasterizer/Rasterizer.hpp @@ -18,7 +18,7 @@ class ExPolygon; */ class Raster { class Impl; - std::unique_ptr impl_; + std::unique_ptr m_impl; public: /// Supported compression types @@ -27,6 +27,13 @@ public: PNG //!> PNG compression }; + /// The Rasterizer expects the input polygons to have their coordinate + /// system origin in the bottom left corner. If the raster is then + /// configured with the TOP_LEFT origin parameter (in the constructor) than + /// it will flip the Y axis in output to maintain the correct orientation. + /// This is the default case with PNG images. They have the origin in the + /// top left corner. Without the flipping, the image would be upside down + /// with the scaled (clipper) coordinate system of the input polygons. enum class Origin { TOP_LEFT, BOTTOM_LEFT @@ -65,7 +72,7 @@ public: /** * Release the allocated resources. Drawing in this state ends in - * unspecified behaviour. + * unspecified behavior. */ void reset(); diff --git a/src/libslic3r/Rasterizer/bicubic.h b/src/libslic3r/Rasterizer/bicubic.h new file mode 100644 index 000000000..870d00dbd --- /dev/null +++ b/src/libslic3r/Rasterizer/bicubic.h @@ -0,0 +1,186 @@ +#ifndef BICUBIC_HPP +#define BICUBIC_HPP + +#include +#include +#include + +#include + +namespace Slic3r { + +namespace BicubicInternal { + // Linear kernel, to be able to test cubic methods with hat kernels. + template + struct LinearKernel + { + typedef T FloatType; + + static T a00() { return T(0.); } + static T a01() { return T(0.); } + static T a02() { return T(0.); } + static T a03() { return T(0.); } + static T a10() { return T(1.); } + static T a11() { return T(-1.); } + static T a12() { return T(0.); } + static T a13() { return T(0.); } + static T a20() { return T(0.); } + static T a21() { return T(1.); } + static T a22() { return T(0.); } + static T a23() { return T(0.); } + static T a30() { return T(0.); } + static T a31() { return T(0.); } + static T a32() { return T(0.); } + static T a33() { return T(0.); } + }; + + // Interpolation kernel aka Catmul-Rom aka Keyes kernel. + template + struct CubicCatmulRomKernel + { + typedef T FloatType; + + static T a00() { return 0; } + static T a01() { return (T)-0.5; } + static T a02() { return (T) 1.; } + static T a03() { return (T)-0.5; } + static T a10() { return (T) 1.; } + static T a11() { return 0; } + static T a12() { return (T)-5./2.; } + static T a13() { return (T) 3./2.; } + static T a20() { return 0; } + static T a21() { return (T) 0.5; } + static T a22() { return (T) 2.; } + static T a23() { return (T)-3./2.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return (T)-0.5; } + static T a33() { return (T) 0.5; } + }; + + // B-spline kernel + template + struct CubicBSplineKernel + { + typedef T FloatType; + + static T a00() { return (T) 1./6.; } + static T a01() { return (T) -3./6.; } + static T a02() { return (T) 3./6.; } + static T a03() { return (T) -1./6.; } + static T a10() { return (T) 4./6.; } + static T a11() { return 0; } + static T a12() { return (T) -6./6.; } + static T a13() { return (T) 3./6.; } + static T a20() { return (T) 1./6.; } + static T a21() { return (T) 3./6.; } + static T a22() { return (T) 3./6.; } + static T a23() { return (T)- 3./6.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return 0; } + static T a33() { return (T) 1./6.; } + }; + + template + inline T clamp(T a, T lower, T upper) + { + return (a < lower) ? lower : + (a > upper) ? upper : a; + } +} + +template +struct CubicKernel +{ + typedef typename KERNEL KernelInternal; + typedef typename KERNEL::FloatType FloatType; + + static FloatType kernel(FloatType x) + { + x = fabs(x); + if (x >= (FloatType)2.) + return 0.0f; + if (x <= (FloatType)1.) { + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3; + } + assert(x > (FloatType)1. && x < (FloatType)2.); + x -= (FloatType)1.; + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3; + } + + static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) + { + const FloatType x2 = x*x; + const FloatType x3 = x*x*x; + return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) + + f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) + + f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) + + f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3); + } +}; + +// Linear splines +typedef CubicKernel> LinearKernelf; +typedef CubicKernel> LinearKerneld; +// Catmul-Rom splines +typedef CubicKernel> CubicCatmulRomKernelf; +typedef CubicKernel> CubicCatmulRomKerneld; +typedef CubicKernel> CubicInterpolationKernelf; +typedef CubicKernel> CubicInterpolationKerneld; +// Cubic B-splines +typedef CubicKernel> CubicBSplineKernelf; +typedef CubicKernel> CubicBSplineKerneld; + +template +static float cubic_interpolate(const Eigen::ArrayBase &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = int(F.size()); + const int ix = (int)floor(pt); + const T s = pt - (T)ix; + + if (ix > 1 && ix + 2 < w) { + // Inside the fully interpolated region. + return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); + } + // Transition region. Extend with a constant function. + auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; } + return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); +} + +template +static float bicubic_interpolate(const Eigen::MatrixBase &F, const Eigen::Matrix &pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = F.cols(); + const int h = F.rows(); + const int ix = (int)floor(pt[0]); + const int iy = (int)floor(pt[1]); + const T s = pt[0] - (T)ix; + const T t = pt[1] - (T)iy; + + if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { + // Inside the fully interpolated region. + return KERNEL::interpolate( + KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s), + KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s), + KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s), + KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t); + } + // Transition region. Extend with a constant function. + auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); } + return KERNEL::interpolate( + KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s), + KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s), + KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s), + KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t); +} + +} // namespace Slic3r + +#endif /* BICUBIC_HPP */ diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp new file mode 100644 index 000000000..ca04b1bee --- /dev/null +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -0,0 +1,155 @@ +#include "igl/random_points_on_mesh.h" +#include "igl/AABB.h" + +#include "SLAAutoSupports.hpp" +#include "Model.hpp" + +#include + + +namespace Slic3r { + +SLAAutoSupports::SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c) +: m_model_object(mo), mesh(), m_config(c) +{} + + +float SLAAutoSupports::approximate_geodesic_distance(const Vec3f& p1, const Vec3f& p2, Vec3f& n1, Vec3f& n2) +{ + n1.normalize(); + n2.normalize(); + + Vec3f v = (p2-p1); + v.normalize(); + + float c1 = n1.dot(v); + float c2 = n2.dot(v); + float result = pow(p1(0)-p2(0), 2) + pow(p1(1)-p2(1), 2) + pow(p1(2)-p2(2), 2); + // Check for division by zero: + if(fabs(c1 - c2) > 0.0001) + result *= (asin(c1) - asin(c2)) / (c1 - c2); + return result; +} + + +void SLAAutoSupports::generate() +{ + // Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation). + // Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation. + // The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for). + // Results will be inverse-transformed to raw_mesh coordinates. + TriangleMesh mesh = m_model_object.raw_mesh(); + Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/); + mesh.transform(transformation_matrix); + + // Check that the object is thick enough to produce any support points + BoundingBoxf3 bb = mesh.bounding_box(); + if (bb.size()(2) < m_config.minimal_z) + return; + + // All points that we curretly have must be transformed too, so distance to them is correcly calculated. + for (Vec3f& point : m_model_object.sla_support_points) + point = transformation_matrix.cast() * point; + + const stl_file& stl = mesh.stl; + Eigen::MatrixXf V; + Eigen::MatrixXi F; + V.resize(3 * stl.stats.number_of_facets, 3); + F.resize(stl.stats.number_of_facets, 3); + for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); + V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); + V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + F(i, 0) = 3*i+0; + F(i, 1) = 3*i+1; + F(i, 2) = 3*i+2; + } + + // In order to calculate distance to already placed points, we must keep know which facet the point lies on. + std::vector facets_normals; + + // The AABB hierarchy will be used to find normals of already placed points. + // The points added automatically will just push_back the new normal on the fly. + igl::AABB aabb; + aabb.init(V, F); + for (unsigned int i=0; i dump; + Eigen::MatrixXf query_point = m_model_object.sla_support_points[i]; + aabb.squared_distance(V, F, query_point, facet_idx, dump); + Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0)); + Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0)); + Vec3f normal = a1.cross(a2); + normal.normalize(); + facets_normals.push_back(normal); + } + + // New potential support point is randomly generated on the mesh and distance to all already placed points is calculated. + // In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted. + // The process stops after certain number of points is refused in a row. + Vec3f point; + Vec3f normal; + int added_points = 0; + int refused_points = 0; + const int refused_limit = 30; + // Angle at which the density reaches zero: + const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal)); + + srand(time(NULL)); // rand() is used by igl::random_point_on_mesh + while (refused_points < refused_limit) { + // Place a random point on the mesh and calculate corresponding facet's normal: + Eigen::VectorXi FI; + Eigen::MatrixXf B; + igl::random_points_on_mesh(1, V, F, B, FI); + point = B(0,0)*V.row(F(FI(0),0)) + + B(0,1)*V.row(F(FI(0),1)) + + B(0,2)*V.row(F(FI(0),2)); + if (point(2) - bb.min(2) < m_config.minimal_z) + continue; + + Vec3f a1 = V.row(F(FI(0),1)) - V.row(F(FI(0),0)); + Vec3f a2 = V.row(F(FI(0),2)) - V.row(F(FI(0),0)); + normal = a1.cross(a2); + normal.normalize(); + + // calculate angle between the normal and vertical: + float angle = angle_from_normal(normal); + if (angle > threshold_angle) + continue; + + const float distance_limit = 1./(2.4*get_required_density(angle)); + bool add_it = true; + for (unsigned int i=0; i() * point; +} + + + +float SLAAutoSupports::get_required_density(float angle) const +{ + // calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle + // to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle). + float K = 4.f * float(acos(m_config.density_at_45/m_config.density_at_horizontal) / M_PI); + return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle))); +} + + + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp new file mode 100644 index 000000000..40726fd0e --- /dev/null +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -0,0 +1,42 @@ +#ifndef SLAAUTOSUPPORTS_HPP_ +#define SLAAUTOSUPPORTS_HPP_ + +#include +#include + + +namespace Slic3r { + +class ModelObject; + + + + +class SLAAutoSupports { +public: + struct Config { + float density_at_horizontal; + float density_at_45; + float minimal_z; + }; + + SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c); + void generate(); + +private: + TriangleMesh mesh; + static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); } + float get_required_density(float angle) const; + static float approximate_geodesic_distance(const Vec3f& p1, const Vec3f& p2, Vec3f& n1, Vec3f& n2); + + ModelObject& m_model_object; + SLAAutoSupports::Config m_config; +}; + + + + +} // namespace Slic3r + + +#endif // SLAAUTOSUPPORTS_HPP_ \ No newline at end of file diff --git a/src/libslic3r/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp similarity index 63% rename from src/libslic3r/SLABasePool.cpp rename to src/libslic3r/SLA/SLABasePool.cpp index f3683865c..21bd124f7 100644 --- a/src/libslic3r/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -1,73 +1,17 @@ -#include -#include - #include "SLABasePool.hpp" -#include "ExPolygon.hpp" -#include "TriangleMesh.hpp" -#include "ClipperUtils.hpp" +#include "SLABoilerPlate.hpp" + #include "boost/log/trivial.hpp" +#include "SLABoostAdapter.hpp" +#include "ClipperUtils.hpp" //#include "SVG.hpp" +//#include "benchmark.h" namespace Slic3r { namespace sla { -namespace { - -using coord_t = Point::coord_type; - -/// get the scaled clipper units for a millimeter value -inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); } - -/// Get x and y coordinates (because we are eigenizing...) -inline coord_t x(const Point& p) { return p(0); } -inline coord_t y(const Point& p) { return p(1); } -inline coord_t& x(Point& p) { return p(0); } -inline coord_t& y(Point& p) { return p(1); } - -inline coordf_t x(const Vec3d& p) { return p(0); } -inline coordf_t y(const Vec3d& p) { return p(1); } -inline coordf_t z(const Vec3d& p) { return p(2); } -inline coordf_t& x(Vec3d& p) { return p(0); } -inline coordf_t& y(Vec3d& p) { return p(1); } -inline coordf_t& z(Vec3d& p) { return p(2); } - -inline coord_t& x(Vec3crd& p) { return p(0); } -inline coord_t& y(Vec3crd& p) { return p(1); } -inline coord_t& z(Vec3crd& p) { return p(2); } -inline coord_t x(const Vec3crd& p) { return p(0); } -inline coord_t y(const Vec3crd& p) { return p(1); } -inline coord_t z(const Vec3crd& p) { return p(2); } - -inline void triangulate(const ExPolygon& expoly, Polygons& triangles) { - expoly.triangulate_p2t(&triangles); -} - -inline Polygons triangulate(const ExPolygon& expoly) { - Polygons tri; triangulate(expoly, tri); return tri; -} - -using Indices = std::vector; - -/// Intermediate struct for a 3D mesh -struct Contour3D { - Pointf3s points; - Indices indices; - - void merge(const Contour3D& ctr) { - auto s3 = coord_t(points.size()); - auto s = coord_t(indices.size()); - - points.insert(points.end(), ctr.points.begin(), ctr.points.end()); - indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); - - for(auto n = s; n < indices.size(); n++) { - auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; - } - } -}; - /// Convert the triangulation output to an intermediate mesh. -inline Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { +Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { Pointf3s points; points.reserve(3*triangles.size()); @@ -86,19 +30,10 @@ inline Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { return {points, indices}; } -/// Only a debug function to generate top and bottom plates from a 2D shape. -/// It is not used in the algorithm directly. -inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { - Polygons triangles = triangulate(poly); - - auto lower = convert(triangles, 0, false); - auto upper = convert(triangles, z_distance, true); - lower.merge(upper); - return lower; -} - -inline Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, - double floor_z_mm, double ceiling_z_mm) { +Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, + double floor_z_mm, double ceiling_z_mm, + ThrowOnCancel thr) +{ using std::transform; using std::back_inserter; ExPolygon poly; @@ -123,13 +58,15 @@ inline Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, auto is_upper = [&hlines](const Point& p) { return std::any_of(hlines.begin(), hlines.end(), [&p](const Line& l) { - return l.distance_to(p) < mm(0.01); + return l.distance_to(p) < mm(1e-6); }); }; std::for_each(tri.begin(), tri.end(), - [&rp, &rpi, &poly, &idx, is_upper, fz, cz](const Polygon& pp) + [&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp) { + thr(); // may throw if cancellation was requested + for(auto& p : pp.points) if(is_upper(p)) rp.emplace_back(unscale(x(p), y(p), mm(cz))); @@ -143,18 +80,8 @@ inline Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, return ret; } -/// Mesh from an existing contour. -inline TriangleMesh mesh(const Contour3D& ctour) { - return {ctour.points, ctour.indices}; -} - -/// Mesh from an evaporating 3D contour -inline TriangleMesh mesh(Contour3D&& ctour) { - return {std::move(ctour.points), std::move(ctour.indices)}; -} - /// Offsetting with clipper and smoothing the edges into a curvature. -inline void offset(ExPolygon& sh, coord_t distance) { +void offset(ExPolygon& sh, coord_t distance) { using ClipperLib::ClipperOffset; using ClipperLib::jtRound; using ClipperLib::etClosedPolygon; @@ -207,93 +134,8 @@ inline void offset(ExPolygon& sh, coord_t distance) { } } -template -inline Contour3D round_edges(const ExPolygon& base_plate, - double radius_mm, - double degrees, - double ceilheight_mm, - bool dir, - ExP&& last_offset = ExP(), D&& last_height = D()) -{ - auto ob = base_plate; - auto ob_prev = ob; - double wh = ceilheight_mm, wh_prev = wh; - Contour3D curvedwalls; - - const size_t steps = 6; // steps for 180 degrees - degrees = std::fmod(degrees, 180); - const int portion = int(steps*degrees / 90); - const double ystep_mm = radius_mm/steps; - coord_t s = dir? 1 : -1; - double xxprev = 0; - for(int i = 0; i < portion; i++) { - ob = base_plate; - - // The offset is given by the equation: x = sqrt(r^2 - y^2) - // which can be derived from the circle equation. y is the current - // height for which the offset is calculated and x is the offset itself - // r is the radius of the circle that is used to smooth the edges - - double r2 = radius_mm * radius_mm; - double y2 = steps*ystep_mm - i*ystep_mm; - y2 *= y2; - - double xx = sqrt(r2 - y2); - - offset(ob, s*mm(xx)); - wh = ceilheight_mm - i*ystep_mm; - - Contour3D pwalls; - if(xxprev < xx) pwalls = walls(ob, ob_prev, wh, wh_prev); - else pwalls = walls(ob_prev, ob, wh_prev, wh); - - curvedwalls.merge(pwalls); - ob_prev = ob; - wh_prev = wh; - xxprev = xx; - } - - last_offset = std::move(ob); - last_height = wh; - - return curvedwalls; -} - -/// Generating the concave part of the 3D pool with the bottom plate and the -/// side walls. -inline Contour3D inner_bed(const ExPolygon& poly, double depth_mm, - double begin_h_mm = 0) { - - Polygons triangles = triangulate(poly); - - coord_t depth = mm(depth_mm); - coord_t begin_h = mm(begin_h_mm); - - auto bottom = convert(triangles, -depth + begin_h, false); - auto lines = poly.lines(); - - // Generate outer walls - auto fp = [](const Point& p, Point::coord_type z) { - return unscale(x(p), y(p), z); - }; - - for(auto& l : lines) { - auto s = coord_t(bottom.points.size()); - - bottom.points.emplace_back(fp(l.a, -depth + begin_h)); - bottom.points.emplace_back(fp(l.b, -depth + begin_h)); - bottom.points.emplace_back(fp(l.a, begin_h)); - bottom.points.emplace_back(fp(l.b, begin_h)); - - bottom.indices.emplace_back(s + 3, s + 1, s); - bottom.indices.emplace_back(s + 2, s + 3, s); - } - - return bottom; -} - /// Unification of polygons (with clipper) preserving holes as well. -inline ExPolygons unify(const ExPolygons& shapes) { +ExPolygons unify(const ExPolygons& shapes) { using ClipperLib::ptSubject; ExPolygons retv; @@ -362,6 +204,120 @@ inline ExPolygons unify(const ExPolygons& shapes) { return retv; } +/// Only a debug function to generate top and bottom plates from a 2D shape. +/// It is not used in the algorithm directly. +inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { + Polygons triangles = triangulate(poly); + + auto lower = convert(triangles, 0, false); + auto upper = convert(triangles, z_distance, true); + lower.merge(upper); + return lower; +} + +template +Contour3D round_edges(const ExPolygon& base_plate, + double radius_mm, + double degrees, + double ceilheight_mm, + bool dir, + ThrowOnCancel throw_on_cancel, + ExP&& last_offset = ExP(), D&& last_height = D()) +{ + auto ob = base_plate; + auto ob_prev = ob; + double wh = ceilheight_mm, wh_prev = wh; + Contour3D curvedwalls; + + int steps = 15; // int(std::ceil(10*std::pow(radius_mm, 1.0/3))); + double stepx = radius_mm / steps; + coord_t s = dir? 1 : -1; + degrees = std::fmod(degrees, 180); + + if(degrees >= 90) { + for(int i = 1; i <= steps; ++i) { + throw_on_cancel(); + + ob = base_plate; + + double r2 = radius_mm * radius_mm; + double xx = i*stepx; + double x2 = xx*xx; + double stepy = std::sqrt(r2 - x2); + + offset(ob, s*mm(xx)); + wh = ceilheight_mm - radius_mm + stepy; + + Contour3D pwalls; + pwalls = walls(ob, ob_prev, wh, wh_prev, throw_on_cancel); + + curvedwalls.merge(pwalls); + ob_prev = ob; + wh_prev = wh; + } + } + + double tox = radius_mm - radius_mm*std::sin(degrees * PI / 180); + int tos = int(tox / stepx); + + for(int i = 1; i <= tos; ++i) { + throw_on_cancel(); + ob = base_plate; + + double r2 = radius_mm * radius_mm; + double xx = radius_mm - i*stepx; + double x2 = xx*xx; + double stepy = std::sqrt(r2 - x2); + offset(ob, s*mm(xx)); + wh = ceilheight_mm - radius_mm - stepy; + + Contour3D pwalls; + pwalls = walls(ob_prev, ob, wh_prev, wh, throw_on_cancel); + + curvedwalls.merge(pwalls); + ob_prev = ob; + wh_prev = wh; + } + + last_offset = std::move(ob); + last_height = wh; + + return curvedwalls; +} + +/// Generating the concave part of the 3D pool with the bottom plate and the +/// side walls. +Contour3D inner_bed(const ExPolygon& poly, double depth_mm, + double begin_h_mm = 0) { + + Polygons triangles = triangulate(poly); + + coord_t depth = mm(depth_mm); + coord_t begin_h = mm(begin_h_mm); + + auto bottom = convert(triangles, -depth + begin_h, false); + auto lines = poly.lines(); + + // Generate outer walls + auto fp = [](const Point& p, Point::coord_type z) { + return unscale(x(p), y(p), z); + }; + + for(auto& l : lines) { + auto s = coord_t(bottom.points.size()); + + bottom.points.emplace_back(fp(l.a, -depth + begin_h)); + bottom.points.emplace_back(fp(l.b, -depth + begin_h)); + bottom.points.emplace_back(fp(l.a, begin_h)); + bottom.points.emplace_back(fp(l.b, begin_h)); + + bottom.indices.emplace_back(s + 3, s + 1, s); + bottom.indices.emplace_back(s + 2, s + 3, s); + } + + return bottom; +} + inline Point centroid(Points& pp) { Point c; switch(pp.size()) { @@ -369,10 +325,22 @@ inline Point centroid(Points& pp) { case 1: c = pp.front(); break; case 2: c = (pp[0] + pp[1]) / 2; break; default: { - Polygon p; - p.points.swap(pp); - c = p.centroid(); - pp.swap(p.points); + auto MAX = std::numeric_limits::max(); + auto MIN = std::numeric_limits::min(); + Point min = {MAX, MAX}, max = {MIN, MIN}; + + for(auto& p : pp) { + if(p(0) < min(0)) min(0) = p(0); + if(p(1) < min(1)) min(1) = p(1); + if(p(0) > max(0)) max(0) = p(0); + if(p(1) > max(1)) max(1) = p(1); + } + c(0) = min(0) + (max(0) - min(0)) / 2; + c(1) = min(1) + (max(1) - min(1)) / 2; + + // TODO: fails for non convex cluster +// c = std::accumulate(pp.begin(), pp.end(), Point{0, 0}); +// x(c) /= coord_t(pp.size()); y(c) /= coord_t(pp.size()); break; } } @@ -388,8 +356,13 @@ inline Point centroid(const ExPolygon& poly) { /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -inline ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) +ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}) { + namespace bgi = boost::geometry::index; + using SpatElement = std::pair; + using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; + if(polys.empty()) return ExPolygons(); ExPolygons punion = unify(polys); // could be redundant @@ -401,22 +374,40 @@ inline ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), [](const ExPolygon& poly) { return centroid(poly); }); + + SpatIndex boxindex; unsigned idx = 0; + std::for_each(punion.begin(), punion.end(), + [&boxindex, &idx](const ExPolygon& expo) { + BoundingBox bb(expo); + boxindex.insert(std::make_pair(bb, idx++)); + }); + + // Centroid of the centroids of islands. This is where the additional // connector sticks are routed. Point cc = centroid(centroids); punion.reserve(punion.size() + centroids.size()); + idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [cc, max_dist_mm](const Point& c) { - + [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + (const Point& c) + { + throw_on_cancel(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; double max_dist = mm(max_dist_mm); - if(l > max_dist) return ExPolygon(); + ExPolygon& expo = punion[idx++]; + BoundingBox querybb(expo); + + querybb.offset(max_dist); + std::vector result; + boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); + if(result.size() <= 1) return ExPolygon(); ExPolygon r; auto& ctour = r.contour.points; @@ -437,41 +428,49 @@ inline ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) return punion; } -} - -void ground_layer(const TriangleMesh &mesh, ExPolygons &output, float h) +void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, + float layerh, ThrowOnCancel thrfn) { TriangleMesh m = mesh; TriangleMeshSlicer slicer(&m); - std::vector tmp; + auto bb = mesh.bounding_box(); + float gnd = float(bb.min(Z)); + std::vector heights = {float(bb.min(Z))}; + for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) + heights.emplace_back(hi); - slicer.slice({h}, &tmp, [](){}); + std::vector out; out.reserve(size_t(std::ceil(h/layerh))); + slicer.slice(heights, &out, thrfn); - output = tmp.front(); + size_t count = 0; for(auto& o : out) count += o.size(); + ExPolygons tmp; tmp.reserve(count); + for(auto& o : out) for(auto& e : o) tmp.emplace_back(std::move(e)); + + ExPolygons utmp = unify(tmp); + for(auto& o : utmp) { + auto&& smp = o.simplify(0.1/SCALING_FACTOR); + output.insert(output.end(), smp.begin(), smp.end()); + } } void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, - double min_wall_thickness_mm, - double min_wall_height_mm, - double max_merge_distance_mm) + const PoolConfig& cfg) { - auto concavehs = concave_hull(ground_layer, max_merge_distance_mm); + double mdist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm) + + cfg.max_merge_distance_mm; + + auto concavehs = concave_hull(ground_layer, mdist, cfg.throw_on_cancel); for(ExPolygon& concaveh : concavehs) { if(concaveh.contour.points.empty()) return; concaveh.holes.clear(); - BoundingBox bb(concaveh); - coord_t w = x(bb.max) - x(bb.min); - coord_t h = y(bb.max) - y(bb.min); + const coord_t WALL_THICKNESS = mm(cfg.min_wall_thickness_mm); - auto wall_thickness = coord_t((w+h)*0.01); + const coord_t WALL_DISTANCE = mm(2*cfg.edge_radius_mm) + + coord_t(0.8*WALL_THICKNESS); - const coord_t WALL_THICKNESS = mm(min_wall_thickness_mm) + - wall_thickness; - - const coord_t WALL_DISTANCE = coord_t(0.3*WALL_THICKNESS); - const coord_t HEIGHT = mm(min_wall_height_mm); + const coord_t HEIGHT = mm(cfg.min_wall_height_mm); auto outer_base = concaveh; offset(outer_base, WALL_THICKNESS+WALL_DISTANCE); @@ -488,18 +487,47 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, Contour3D pool; ExPolygon ob = outer_base; double wh = 0; + + // now we will calculate the angle or portion of the circle from + // pi/2 that will connect perfectly with the bottom plate. + // this is a tangent point calculation problem and the equation can + // be found for example here: + // http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm + // the y coordinate would be: + // y = cy + (r^2*py - r*px*sqrt(px^2 + py^2 - r^2) / (px^2 + py^2) + // where px and py are the coordinates of the point outside the circle + // cx and cy are the circle center, r is the radius + // to get the angle we use arcsin function and subtract 90 degrees then + // flip the sign to get the right input to the round_edge function. + double r = cfg.edge_radius_mm; + double cy = 0; + double cx = 0; + double px = cfg.min_wall_thickness_mm; + double py = r - cfg.min_wall_height_mm; + + double pxcx = px - cx; + double pycy = py - cy; + double b_2 = pxcx*pxcx + pycy*pycy; + double r_2 = r*r; + double D = std::sqrt(b_2 - r_2); + double vy = (r_2*pycy - r*pxcx*D) / b_2; + double phi = -(std::asin(vy/r) * 180 / PI - 90); + auto curvedwalls = round_edges(ob, - 1, // radius 1 mm - 170, // 170 degrees + r, + phi, // 170 degrees 0, // z position of the input plane true, + cfg.throw_on_cancel, ob, wh); + pool.merge(curvedwalls); ExPolygon ob_contr = ob; ob_contr.holes.clear(); - auto pwalls = walls(ob_contr, inner_base, wh, -min_wall_height_mm); + auto pwalls = walls(ob_contr, inner_base, wh, -cfg.min_wall_height_mm, + cfg.throw_on_cancel); pool.merge(pwalls); Polygons top_triangles, bottom_triangles; @@ -509,15 +537,17 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, auto bottom_plate = convert(bottom_triangles, -HEIGHT, true); ob = inner_base; wh = 0; + // rounded edge generation for the inner bed curvedwalls = round_edges(ob, - 1, // radius 1 mm - 90, // 170 degrees + cfg.edge_radius_mm, + 90, // 90 degrees 0, // z position of the input plane false, + cfg.throw_on_cancel, ob, wh); pool.merge(curvedwalls); - auto innerbed = inner_bed(ob, min_wall_height_mm/2 + wh, wh); + auto innerbed = inner_bed(ob, cfg.min_wall_height_mm/2 + wh, wh); pool.merge(top_plate); pool.merge(bottom_plate); diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp new file mode 100644 index 000000000..62c4971eb --- /dev/null +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -0,0 +1,59 @@ +#ifndef SLABASEPOOL_HPP +#define SLABASEPOOL_HPP + +#include +#include + +namespace Slic3r { + +class ExPolygon; +using ExPolygons = std::vector; + +class TriangleMesh; + +namespace sla { + +using ThrowOnCancel = std::function; + +/// Calculate the polygon representing the silhouette from the specified height +void base_plate(const TriangleMesh& mesh, // input mesh + ExPolygons& output, // Output will be merged with + float zlevel = 0.1f, // Plate creation level + float layerheight = 0.05f, // The sampling height + ThrowOnCancel thrfn = [](){}); // Will be called frequently + +struct PoolConfig { + double min_wall_thickness_mm = 2; + double min_wall_height_mm = 5; + double max_merge_distance_mm = 50; + double edge_radius_mm = 1; + + ThrowOnCancel throw_on_cancel = [](){}; + + inline PoolConfig() {} + inline PoolConfig(double wt, double wh, double md, double er): + min_wall_thickness_mm(wt), + min_wall_height_mm(wh), + max_merge_distance_mm(md), + edge_radius_mm(er) {} +}; + +/// Calculate the pool for the mesh for SLA printing +void create_base_pool(const ExPolygons& base_plate, + TriangleMesh& output_mesh, + const PoolConfig& = PoolConfig()); + +/// TODO: Currently the base plate of the pool will have half the height of the +/// whole pool. So the carved out space has also half the height. This is not +/// a particularly elegant solution, the thickness should be exactly +/// min_wall_thickness and it should be corrected in the future. This method +/// will return the correct value for further processing. +inline double get_pad_elevation(const PoolConfig& cfg) { + return cfg.min_wall_height_mm / 2.0; +} + +} + +} + +#endif // SLABASEPOOL_HPP diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp new file mode 100644 index 000000000..1436be17f --- /dev/null +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -0,0 +1,87 @@ +#ifndef SLABOILERPLATE_HPP +#define SLABOILERPLATE_HPP + +#include +#include +#include + +#include "ExPolygon.hpp" +#include "TriangleMesh.hpp" + +namespace Slic3r { +namespace sla { + +using coord_t = Point::coord_type; + +/// get the scaled clipper units for a millimeter value +inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); } + +/// Get x and y coordinates (because we are eigenizing...) +inline coord_t x(const Point& p) { return p(0); } +inline coord_t y(const Point& p) { return p(1); } +inline coord_t& x(Point& p) { return p(0); } +inline coord_t& y(Point& p) { return p(1); } + +inline coordf_t x(const Vec3d& p) { return p(0); } +inline coordf_t y(const Vec3d& p) { return p(1); } +inline coordf_t z(const Vec3d& p) { return p(2); } +inline coordf_t& x(Vec3d& p) { return p(0); } +inline coordf_t& y(Vec3d& p) { return p(1); } +inline coordf_t& z(Vec3d& p) { return p(2); } + +inline coord_t& x(Vec3crd& p) { return p(0); } +inline coord_t& y(Vec3crd& p) { return p(1); } +inline coord_t& z(Vec3crd& p) { return p(2); } +inline coord_t x(const Vec3crd& p) { return p(0); } +inline coord_t y(const Vec3crd& p) { return p(1); } +inline coord_t z(const Vec3crd& p) { return p(2); } + +inline void triangulate(const ExPolygon& expoly, Polygons& triangles) { + expoly.triangulate_p2t(&triangles); +} + +inline Polygons triangulate(const ExPolygon& expoly) { + Polygons tri; triangulate(expoly, tri); return tri; +} + +using Indices = std::vector; + +/// Intermediate struct for a 3D mesh +struct Contour3D { + Pointf3s points; + Indices indices; + + void merge(const Contour3D& ctr) { + auto s3 = coord_t(points.size()); + auto s = coord_t(indices.size()); + + points.insert(points.end(), ctr.points.begin(), ctr.points.end()); + indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); + + for(size_t n = s; n < indices.size(); n++) { + auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; + } + } +}; + +//using PointSet = Eigen::Matrix; //Eigen::MatrixXd; +using ClusterEl = std::vector; +using ClusteredPoints = std::vector; + +/// Convert the triangulation output to an intermediate mesh. +Contour3D convert(const Polygons& triangles, coord_t z, bool dir); + +/// Mesh from an existing contour. +inline TriangleMesh mesh(const Contour3D& ctour) { + return {ctour.points, ctour.indices}; +} + +/// Mesh from an evaporating 3D contour +inline TriangleMesh mesh(Contour3D&& ctour) { + return {std::move(ctour.points), std::move(ctour.indices)}; +} + +} +} + +#endif // SLABOILERPLATE_HPP diff --git a/src/libslic3r/SLA/SLABoostAdapter.hpp b/src/libslic3r/SLA/SLABoostAdapter.hpp new file mode 100644 index 000000000..1e9daf461 --- /dev/null +++ b/src/libslic3r/SLA/SLABoostAdapter.hpp @@ -0,0 +1,132 @@ +#ifndef SLABOOSTADAPTER_HPP +#define SLABOOSTADAPTER_HPP + +#include "SLA/SLABoilerPlate.hpp" +#include + +namespace boost { +namespace geometry { +namespace traits { + +/* ************************************************************************** */ +/* Point concept adaptation ************************************************* */ +/* ************************************************************************** */ + +template<> struct tag { + using type = point_tag; +}; + +template<> struct coordinate_type { + using type = coord_t; +}; + +template<> struct coordinate_system { + using type = cs::cartesian; +}; + +template<> struct dimension: boost::mpl::int_<2> {}; + +template struct access { + static inline coord_t get(Slic3r::Point const& a) { + return a(d); + } + + static inline void set(Slic3r::Point& a, coord_t const& value) { + a(d) = value; + } +}; + +// For Vec2d /////////////////////////////////////////////////////////////////// + +template<> struct tag { + using type = point_tag; +}; + +template<> struct coordinate_type { + using type = double; +}; + +template<> struct coordinate_system { + using type = cs::cartesian; +}; + +template<> struct dimension: boost::mpl::int_<2> {}; + +template struct access { + static inline double get(Slic3r::Vec2d const& a) { + return a(d); + } + + static inline void set(Slic3r::Vec2d& a, double const& value) { + a(d) = value; + } +}; + +// For Vec3d /////////////////////////////////////////////////////////////////// + +template<> struct tag { + using type = point_tag; +}; + +template<> struct coordinate_type { + using type = double; +}; + +template<> struct coordinate_system { + using type = cs::cartesian; +}; + +template<> struct dimension: boost::mpl::int_<3> {}; + +template struct access { + static inline double get(Slic3r::Vec3d const& a) { + return a(d); + } + + static inline void set(Slic3r::Vec3d& a, double const& value) { + a(d) = value; + } +}; + +/* ************************************************************************** */ +/* Box concept adaptation *************************************************** */ +/* ************************************************************************** */ + +template<> struct tag { + using type = box_tag; +}; + +template<> struct point_type { + using type = Slic3r::Point; +}; + +template +struct indexed_access { + static inline coord_t get(Slic3r::BoundingBox const& box) { + return box.min(d); + } + static inline void set(Slic3r::BoundingBox &box, coord_t const& coord) { + box.min(d) = coord; + } +}; + +template +struct indexed_access { + static inline coord_t get(Slic3r::BoundingBox const& box) { + return box.max(d); + } + static inline void set(Slic3r::BoundingBox &box, coord_t const& coord) { + box.max(d) = coord; + } +}; + +} +} + +template<> struct range_value> { + using type = Slic3r::Vec2d; +}; + +} + +#endif // SLABOOSTADAPTER_HPP diff --git a/src/libslic3r/SLA/SLARotfinder.cpp b/src/libslic3r/SLA/SLARotfinder.cpp new file mode 100644 index 000000000..e66e26706 --- /dev/null +++ b/src/libslic3r/SLA/SLARotfinder.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include +#include "SLABoilerPlate.hpp" +#include "SLARotfinder.hpp" +#include "SLASupportTree.hpp" +#include "Model.hpp" + +namespace Slic3r { +namespace sla { + +std::array find_best_rotation(const ModelObject& modelobj, + float accuracy, + std::function statuscb, + std::function stopcond) +{ + using libnest2d::opt::Method; + using libnest2d::opt::bound; + using libnest2d::opt::Optimizer; + using libnest2d::opt::TOptimizer; + using libnest2d::opt::StopCriteria; + + static const unsigned MAX_TRIES = 100000; + + // return value + std::array rot; + + // We will use only one instance of this converted mesh to examine different + // rotations + EigenMesh3D emesh = to_eigenmesh(modelobj); + + // For current iteration number + unsigned status = 0; + + // The maximum number of iterations + auto max_tries = unsigned(accuracy * MAX_TRIES); + + // call status callback with zero, because we are at the start + statuscb(status); + + // So this is the object function which is called by the solver many times + // It has to yield a single value representing the current score. We will + // call the status callback in each iteration but the actual value may be + // the same for subsequent iterations (status goes from 0 to 100 but + // iterations can be many more) + auto objfunc = [&emesh, &status, &statuscb, max_tries] + (double rx, double ry, double rz) + { + EigenMesh3D& m = emesh; + + // prepare the rotation transformation + Transform3d rt = Transform3d::Identity(); + + rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ())); + rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY())); + rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX())); + + double score = 0; + + // For all triangles we calculate the normal and sum up the dot product + // (a scalar indicating how much are two vectors aligned) with each axis + // this will result in a value that is greater if a normal is aligned + // with all axes. If the normal is aligned than the triangle itself is + // orthogonal to the axes and that is good for print quality. + + // TODO: some applications optimize for minimum z-axis cross section + // area. The current function is only an example of how to optimize. + + // Later we can add more criteria like the number of overhangs, etc... + for(int i = 0; i < m.F.rows(); i++) { + auto idx = m.F.row(i); + + Vec3d p1 = m.V.row(idx(0)); + Vec3d p2 = m.V.row(idx(1)); + Vec3d p3 = m.V.row(idx(2)); + + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + + // So this is the normal + auto n = U.cross(V).normalized(); + + // rotate the normal with the current rotation given by the solver + n = rt * n; + + // We should score against the alignment with the reference planes + score += std::abs(n.dot(Vec3d::UnitX())); + score += std::abs(n.dot(Vec3d::UnitY())); + score += std::abs(n.dot(Vec3d::UnitZ())); + } + + // report status + statuscb( unsigned(++status * 100.0/max_tries) ); + + return score; + }; + + // Firing up the genetic optimizer. For now it uses the nlopt library. + StopCriteria stc; + stc.max_iterations = max_tries; + stc.relative_score_difference = 1e-3; + stc.stop_condition = stopcond; // stop when stopcond returns true + TOptimizer solver(stc); + + // We are searching rotations around the three axes x, y, z. Thus the + // problem becomes a 3 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto b = bound(-PI/2, PI/2); + + // Now we start the optimization process with initial angles (0, 0, 0) + auto result = solver.optimize_max(objfunc, + libnest2d::opt::initvals(0.0, 0.0, 0.0), + b, b, b); + + // Save the result and fck off + rot[0] = std::get<0>(result.optimum); + rot[1] = std::get<1>(result.optimum); + rot[2] = std::get<2>(result.optimum); + + return rot; +} + +} +} diff --git a/src/libslic3r/SLA/SLARotfinder.hpp b/src/libslic3r/SLA/SLARotfinder.hpp new file mode 100644 index 000000000..b8cec859e --- /dev/null +++ b/src/libslic3r/SLA/SLARotfinder.hpp @@ -0,0 +1,38 @@ +#ifndef SLAROTFINDER_HPP +#define SLAROTFINDER_HPP + +#include +#include + +namespace Slic3r { + +class ModelObject; + +namespace sla { + +/** + * The function should find the best rotation for SLA upside down printing. + * + * @param modelobj The model object representing the 3d mesh. + * @param accuracy The optimization accuracy from 0.0f to 1.0f. Currently, + * the nlopt genetic optimizer is used and the number of iterations is + * accuracy * 100000. This can change in the future. + * @param statuscb A status indicator callback called with the unsigned + * argument spanning from 0 to 100. May not reach 100 if the optimization finds + * an optimum before max iterations are reached. + * @param stopcond A function that if returns true, the search process will be + * terminated and the best solution found will be returned. + * + * @return Returns the rotations around each axis (x, y, z) + */ +std::array find_best_rotation( + const ModelObject& modelobj, + float accuracy = 1.0f, + std::function statuscb = [] (unsigned) {}, + std::function stopcond = [] () { return false; } + ); + +} +} + +#endif // SLAROTFINDER_HPP diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp new file mode 100644 index 000000000..792e86c85 --- /dev/null +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -0,0 +1,51 @@ +#ifndef SPATINDEX_HPP +#define SPATINDEX_HPP + +#include +#include +#include + +#include + +namespace Slic3r { +namespace sla { + +typedef Eigen::Matrix Vec3d; +using SpatElement = std::pair; + +class SpatIndex { + class Impl; + + // We use Pimpl because it takes a long time to compile boost headers which + // is the engine of this class. We include it only in the cpp file. + std::unique_ptr m_impl; +public: + + SpatIndex(); + ~SpatIndex(); + + SpatIndex(const SpatIndex&); + SpatIndex(SpatIndex&&); + SpatIndex& operator=(const SpatIndex&); + SpatIndex& operator=(SpatIndex&&); + + void insert(const SpatElement&); + bool remove(const SpatElement&); + + inline void insert(const Vec3d& v, unsigned idx) + { + insert(std::make_pair(v, unsigned(idx))); + } + + std::vector query(std::function); + std::vector nearest(const Vec3d&, unsigned k); + + // For testing + size_t size() const; + bool empty() const { return size() == 0; } +}; + +} +} + +#endif // SPATINDEX_HPP diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp new file mode 100644 index 000000000..8615ab91c --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -0,0 +1,1755 @@ +/** + * In this file we will implement the automatic SLA support tree generation. + * + */ + +#include +#include "SLASupportTree.hpp" +#include "SLABoilerPlate.hpp" +#include "SLASpatIndex.hpp" +#include "SLABasePool.hpp" +#include +#include "ClipperUtils.hpp" + +#include "Model.hpp" + +/** + * Terminology: + * + * Support point: + * The point on the model surface that needs support. + * + * Pillar: + * A thick column that spans from a support point to the ground and has + * a thick cone shaped base where it touches the ground. + * + * Ground facing support point: + * A support point that can be directly connected with the ground with a pillar + * that does not collide or cut through the model. + * + * Non ground facing support point: + * A support point that cannot be directly connected with the ground (only with + * the model surface). + * + * Head: + * The pinhead that connects to the model surface with the sharp end end + * to a pillar or bridge stick with the dull end. + * + * Headless support point: + * A support point on the model surface for which there is not enough place for + * the head. It is either in a hole or there is some barrier that would collide + * with the head geometry. The headless support point can be ground facing and + * non ground facing as well. + * + * Bridge: + * A stick that connects two pillars or a head with a pillar. + * + * Junction: + * A small ball in the intersection of two or more sticks (pillar, bridge, ...) + * + * CompactBridge: + * A bridge that connects a headless support point with the model surface or a + * nearby pillar. + */ + +namespace Slic3r { +namespace sla { + +using Coordf = double; +using Portion = std::tuple; + +inline Portion make_portion(double a, double b) { + return std::make_tuple(a, b); +} + +template double distance(const Vec& p) { + return std::sqrt(p.transpose() * p); +} + +template double distance(const Vec& pp1, const Vec& pp2) { + auto p = pp2 - pp1; + return distance(p); +} + +Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), + double fa=(2*PI/360)) { + + Contour3D ret; + + // prohibit close to zero radius + if(rho <= 1e-6 && rho >= -1e-6) return ret; + + auto& vertices = ret.points; + auto& facets = ret.indices; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative + // coordinates. Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + + for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); + + const auto sbegin = size_t(2*std::get<0>(portion)/angle); + const auto send = size_t(2*std::get<1>(portion)/angle); + + const size_t steps = ring.size(); + const double increment = 1.0 / double(steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + if(sbegin == 0) + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); + + auto id = coord_t(vertices.size()); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); + // radius of the circle for this step. + const double r = std::sqrt(std::abs(rho*rho - z*z)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + + if(sbegin == 0) + facets.emplace_back((i == 0) ? Vec3crd(coord_t(ring.size()), 0, 1) : + Vec3crd(id - 1, 0, id)); + ++ id; + } + + // General case: insert and form facets for each step, + // joining it to the ring below it. + for (size_t s = sbegin + 2; s < send - 1; s++) { + const double z = -rho + increment*double(s*2.0*rho); + const double r = std::sqrt(std::abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // wrap around + facets.emplace_back(Vec3crd(id - 1, id, + id + coord_t(ring.size() - 1))); + facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); + } else { + facets.emplace_back(Vec3crd(id_ringsize - 1, id_ringsize, id)); + facets.emplace_back(Vec3crd(id - 1, id_ringsize - 1, id)); + } + id++; + } + } + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + if(send >= size_t(2*PI / angle)) { + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); + for (size_t i = 0; i < ring.size(); i++) { + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // third vertex is on the other side of the ring. + facets.emplace_back(Vec3crd(id - 1, id_ringsize, id)); + } else { + auto ci = coord_t(id_ringsize + coord_t(i)); + facets.emplace_back(Vec3crd(ci - 1, ci, id)); + } + } + } + id++; + + return ret; +} + +Contour3D cylinder(double r, double h, size_t ssteps) { + Contour3D ret; + + auto steps = int(ssteps); + auto& points = ret.points; + auto& indices = ret.indices; + points.reserve(2*ssteps); + double a = 2*PI/steps; + + Vec3d jp = {0, 0, 0}; + Vec3d endp = {0, 0, h}; + + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double ex = endp(X) + r*std::cos(phi); + double ey = endp(Y) + r*std::sin(phi); + points.emplace_back(ex, ey, endp(Z)); + } + + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double x = jp(X) + r*std::cos(phi); + double y = jp(Y) + r*std::sin(phi); + points.emplace_back(x, y, jp(Z)); + } + + indices.reserve(2*ssteps); + auto offs = steps; + for(int i = 0; i < steps - 1; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + } + + auto last = steps - 1; + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + + return ret; +} + +struct Head { + Contour3D mesh; + + size_t steps = 45; + Vec3d dir = {0, 0, -1}; + Vec3d tr = {0, 0, 0}; + + double r_back_mm = 1; + double r_pin_mm = 0.5; + double width_mm = 2; + double penetration_mm = 0.5; + + // For identification purposes. This will be used as the index into the + // container holding the head structures. See SLASupportTree::Impl + long id = -1; + + // If there is a pillar connecting to this head, then the id will be set. + long pillar_id = -1; + + Head(double r_big_mm, + double r_small_mm, + double length_mm, + double penetration, + Vec3d direction = {0, 0, -1}, // direction (normal to the dull end ) + Vec3d offset = {0, 0, 0}, // displacement + const size_t circlesteps = 45): + steps(circlesteps), dir(direction), tr(offset), + r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm), + penetration_mm(penetration) + { + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_big_mm + r_small_mm + width_mm; + double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) z(p) += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.indices.emplace_back(i1s1, i2s1, i2s2); + mesh.indices.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.indices.emplace_back(i2s2, i2s1, i1s1); + mesh.indices.emplace_back(i1s2, i2s2, i1s1); + + // To simplify further processing, we translate the mesh so that the + // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) + for(auto& p : mesh.points) z(p) -= (h + r_small_mm - penetration_mm); + } + + void transform() + { + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); + + for(auto& p : mesh.points) p = quatern * p + tr; + } + + double fullwidth() const { + return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; + } + + Vec3d junction_point() const { + return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; + } + + double request_pillar_radius(double radius) const { + const double rmax = r_back_mm; + return radius > 0 && radius < rmax ? radius : rmax; + } +}; + +struct Junction { + Contour3D mesh; + double r = 1; + size_t steps = 45; + Vec3d pos; + + long id = -1; + + Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): + r(r_mm), steps(stepnum), pos(tr) + { + mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); + for(auto& p : mesh.points) p += tr; + } +}; + +struct Pillar { + Contour3D mesh; + Contour3D base; + double r = 1; + size_t steps = 0; + Vec3d endpoint; + + long id = -1; + + // If the pillar connects to a head, this is the id of that head + bool starts_from_head = true; // Could start from a junction as well + long start_junction_id = -1; + + Pillar(const Vec3d& jp, const Vec3d& endp, + double radius = 1, size_t st = 45): + r(radius), steps(st), endpoint(endp), starts_from_head(false) + { + assert(steps > 0); + int steps_1 = int(steps - 1); + + auto& points = mesh.points; + auto& indices = mesh.indices; + points.reserve(2*steps); + double a = 2*PI/steps; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = jp(X) + r*std::cos(phi); + double y = jp(Y) + r*std::sin(phi); + points.emplace_back(x, y, jp(Z)); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double ex = endp(X) + r*std::cos(phi); + double ey = endp(Y) + r*std::sin(phi); + points.emplace_back(ex, ey, endp(Z)); + } + + indices.reserve(2*steps); + int offs = int(steps); + for(int i = 0; i < steps_1 ; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + } + + indices.emplace_back(0, steps_1, offs); + indices.emplace_back(steps_1, offs + steps_1, offs); + } + + Pillar(const Junction& junc, const Vec3d& endp): + Pillar(junc.pos, endp, junc.r, junc.steps){} + + Pillar(const Head& head, const Vec3d& endp, double radius = 1): + Pillar(head.junction_point(), endp, head.request_pillar_radius(radius), + head.steps) + { + } + + void add_base(double height = 3, double radius = 2) { + if(height <= 0) return; + + assert(steps >= 0); + auto last = int(steps - 1); + + if(radius < r ) radius = r; + + double a = 2*PI/steps; + double z = endpoint(2) + height; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpoint(0) + r*std::cos(phi); + double y = endpoint(1) + r*std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpoint(0) + radius*std::cos(phi); + double y = endpoint(1) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - height); + } + + auto ep = endpoint; ep(2) += height; + base.points.emplace_back(endpoint); + base.points.emplace_back(ep); + + auto& indices = base.indices; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + + } + + bool has_base() const { return !base.points.empty(); } +}; + +// A Bridge between two pillars (with junction endpoints) +struct Bridge { + Contour3D mesh; + double r = 0.8; + + long id = -1; + long start_jid = -1; + long end_jid = -1; + + // We should reduce the radius a tiny bit to help the convex hull algorithm + Bridge(const Vec3d& j1, const Vec3d& j2, + double r_mm = 0.8, size_t steps = 45): + r(r_mm) + { + using Quaternion = Eigen::Quaternion; + Vec3d dir = (j2 - j1).normalized(); + double d = distance(j2, j1); + + mesh = cylinder(r, d, steps); + + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + for(auto& p : mesh.points) p = quater * p + j1; + } + + Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8): + Bridge(j1.pos, j2.pos, r_mm, j1.steps) {} + +}; + +// A bridge that spans from model surface to model surface with small connecting +// edges on the endpoints. Used for headless support points. +struct CompactBridge { + Contour3D mesh; + long id = -1; + + CompactBridge(const Vec3d& sp, + const Vec3d& ep, + const Vec3d& n, + double r, + size_t steps = 45) + { + Vec3d startp = sp + r * n; + Vec3d dir = (ep - startp).normalized(); + Vec3d endp = ep - r * dir; + + Bridge br(startp, endp, r, steps); + mesh.merge(br.mesh); + + // now add the pins + double fa = 2*PI/steps; + auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); + for(auto& p : upperball.points) p += startp; + + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + + mesh.merge(upperball); + mesh.merge(lowerball); + } +}; + +// A wrapper struct around the base pool (pad) +struct Pad { +// Contour3D mesh; + TriangleMesh tmesh; + PoolConfig cfg; + double zlevel = 0; + + Pad() {} + + Pad(const TriangleMesh& object_support_mesh, + const ExPolygons& baseplate, + double ground_level, + const PoolConfig& pcfg) : + cfg(pcfg), + zlevel(ground_level + sla::get_pad_elevation(pcfg)) + { + ExPolygons basep; + cfg.throw_on_cancel(); + + // The 0.1f is the layer height with which the mesh is sampled and then + // the layers are unified into one vector of polygons. + base_plate(object_support_mesh, basep, + float(cfg.min_wall_height_mm), 0.1f, pcfg.throw_on_cancel); + + for(auto& bp : baseplate) basep.emplace_back(bp); + + create_base_pool(basep, tmesh, cfg); + tmesh.translate(0, 0, float(zlevel)); + } + + bool empty() const { return tmesh.facets_count() == 0; } +}; + +EigenMesh3D to_eigenmesh(const Contour3D& cntr) { + EigenMesh3D emesh; + + auto& V = emesh.V; + auto& F = emesh.F; + + V.resize(Eigen::Index(cntr.points.size()), 3); + F.resize(Eigen::Index(cntr.indices.size()), 3); + + for (int i = 0; i < V.rows(); ++i) { + V.row(i) = cntr.points[size_t(i)]; + F.row(i) = cntr.indices[size_t(i)]; + } + + return emesh; +} + +// The minimum distance for two support points to remain valid. +static const double /*constexpr*/ D_SP = 0.1; + +enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers + X, Y, Z +}; + +EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) { + + const stl_file& stl = tmesh.stl; + + EigenMesh3D outmesh; + + auto&& bb = tmesh.bounding_box(); + outmesh.ground_level += bb.min(Z); + + auto& V = outmesh.V; + auto& F = outmesh.F; + + V.resize(3*stl.stats.number_of_facets, 3); + F.resize(stl.stats.number_of_facets, 3); + for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { + const stl_facet* facet = stl.facet_start+i; + V(3*i+0, 0) = double(facet->vertex[0](0)); + V(3*i+0, 1) = double(facet->vertex[0](1)); + V(3*i+0, 2) = double(facet->vertex[0](2)); + + V(3*i+1, 0) = double(facet->vertex[1](0)); + V(3*i+1, 1) = double(facet->vertex[1](1)); + V(3*i+1, 2) = double(facet->vertex[1](2)); + + V(3*i+2, 0) = double(facet->vertex[2](0)); + V(3*i+2, 1) = double(facet->vertex[2](1)); + V(3*i+2, 2) = double(facet->vertex[2](2)); + + F(i, 0) = int(3*i+0); + F(i, 1) = int(3*i+1); + F(i, 2) = int(3*i+2); + } + + return outmesh; +} + +EigenMesh3D to_eigenmesh(const ModelObject& modelobj) { + return to_eigenmesh(modelobj.raw_mesh()); +} + +PointSet to_point_set(const std::vector &v) +{ + PointSet ret(v.size(), 3); + { long i = 0; for(const Vec3d& p : v) ret.row(i++) = p; } + return ret; +} + +Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) { + return object.transform_vector(mesh_coord.cast()); +} + +double ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir, + const EigenMesh3D& m); + +PointSet normals(const PointSet& points, const EigenMesh3D& mesh); + +inline Vec2d to_vec2(const Vec3d& v3) { + return {v3(X), v3(Y)}; +} + +bool operator==(const SpatElement& e1, const SpatElement& e2) { + return e1.second == e2.second; +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const PointSet& points, + std::function pred, + unsigned max_points = 0); + +class SLASupportTree::Impl { + std::vector m_heads; + std::vector m_pillars; + std::vector m_junctions; + std::vector m_bridges; + std::vector m_compact_bridges; + Controller m_ctl; + + Pad m_pad; + mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; + mutable double model_height = 0; // the full height of the model +public: + double ground_level = 0; + + Impl() = default; + inline Impl(const Controller& ctl): m_ctl(ctl) {} + + const Controller& ctl() const { return m_ctl; } + + template Head& add_head(Args&&... args) { + m_heads.emplace_back(std::forward(args)...); + m_heads.back().id = long(m_heads.size() - 1); + meshcache_valid = false; + return m_heads.back(); + } + + template Pillar& add_pillar(long headid, Args&&... args) { + assert(headid >= 0 && headid < m_heads.size()); + Head& head = m_heads[size_t(headid)]; + m_pillars.emplace_back(head, std::forward(args)...); + Pillar& pillar = m_pillars.back(); + pillar.id = long(m_pillars.size() - 1); + head.pillar_id = pillar.id; + pillar.start_junction_id = head.id; + pillar.starts_from_head = true; + meshcache_valid = false; + return m_pillars.back(); + } + + const Head& pillar_head(long pillar_id) const { + assert(pillar_id >= 0 && pillar_id < m_pillars.size()); + const Pillar& p = m_pillars[size_t(pillar_id)]; + assert(p.starts_from_head && p.start_junction_id >= 0 && + p.start_junction_id < m_heads.size() ); + return m_heads[size_t(p.start_junction_id)]; + } + + const Pillar& head_pillar(long headid) const { + assert(headid >= 0 && headid < m_heads.size()); + const Head& h = m_heads[size_t(headid)]; + assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); + return m_pillars[size_t(h.pillar_id)]; + } + + template const Junction& add_junction(Args&&... args) { + m_junctions.emplace_back(std::forward(args)...); + m_junctions.back().id = long(m_junctions.size() - 1); + meshcache_valid = false; + return m_junctions.back(); + } + + template const Bridge& add_bridge(Args&&... args) { + m_bridges.emplace_back(std::forward(args)...); + m_bridges.back().id = long(m_bridges.size() - 1); + meshcache_valid = false; + return m_bridges.back(); + } + + template + const CompactBridge& add_compact_bridge(Args&&...args) { + m_compact_bridges.emplace_back(std::forward(args)...); + m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); + meshcache_valid = false; + return m_compact_bridges.back(); + } + + const std::vector& heads() const { return m_heads; } + Head& head(size_t idx) { meshcache_valid = false; return m_heads[idx]; } + const std::vector& pillars() const { return m_pillars; } + const std::vector& bridges() const { return m_bridges; } + const std::vector& junctions() const { return m_junctions; } + const std::vector& compact_bridges() const { + return m_compact_bridges; + } + + const Pad& create_pad(const TriangleMesh& object_supports, + const ExPolygons& baseplate, + const PoolConfig& cfg) { + m_pad = Pad(object_supports, baseplate, ground_level, cfg); + return m_pad; + } + + const Pad& pad() const { return m_pad; } + + // WITHOUT THE PAD!!! + const TriangleMesh& merged_mesh() const { + if(meshcache_valid) return meshcache; + + meshcache = TriangleMesh(); + + for(auto& head : heads()) { + if(m_ctl.stopcondition()) break; + auto&& m = mesh(head.mesh); + meshcache.merge(m); + } + + for(auto& stick : pillars()) { + if(m_ctl.stopcondition()) break; + meshcache.merge(mesh(stick.mesh)); + meshcache.merge(mesh(stick.base)); + } + + for(auto& j : junctions()) { + if(m_ctl.stopcondition()) break; + meshcache.merge(mesh(j.mesh)); + } + + for(auto& cb : compact_bridges()) { + if(m_ctl.stopcondition()) break; + meshcache.merge(mesh(cb.mesh)); + } + + for(auto& bs : bridges()) { + if(m_ctl.stopcondition()) break; + meshcache.merge(mesh(bs.mesh)); + } + + if(m_ctl.stopcondition()) { + // In case of failure we have to return an empty mesh + meshcache = TriangleMesh(); + return meshcache; + } + + // TODO: Is this necessary? + meshcache.repair(); + + BoundingBoxf3&& bb = meshcache.bounding_box(); + model_height = bb.max(Z) - bb.min(Z); + + meshcache_valid = true; + return meshcache; + } + + // WITH THE PAD + double full_height() const { + if(merged_mesh().empty() && !pad().empty()) + return pad().cfg.min_wall_height_mm; + + double h = mesh_height(); + if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg); + return h; + } + + // WITHOUT THE PAD!!! + double mesh_height() const { + if(!meshcache_valid) merged_mesh(); + return model_height; + } + +}; + +template +long cluster_centroid(const ClusterEl& clust, + std::function pointfn, + DistFn df) +{ + switch(clust.size()) { + case 0: /* empty cluster */ return -1; + case 1: /* only one element */ return 0; + case 2: /* if two elements, there is no center */ return 0; + default: ; + } + + // The function works by calculating for each point the average distance + // from all the other points in the cluster. We create a selector bitmask of + // the same size as the cluster. The bitmask will have two true bits and + // false bits for the rest of items and we will loop through all the + // permutations of the bitmask (combinations of two points). Get the + // distance for the two points and add the distance to the averages. + // The point with the smallest average than wins. + + std::vector sel(clust.size(), false); // create full zero bitmask + std::fill(sel.end() - 2, sel.end(), true); // insert the two ones + std::vector avgs(clust.size(), 0.0); // store the average distances + + do { + std::array idx; + for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; + + double d = df(pointfn(clust[idx[0]]), + pointfn(clust[idx[1]])); + + // add the distance to the sums for both associated points + for(auto i : idx) avgs[i] += d; + + // now continue with the next permutation of the bitmask with two 1s + } while(std::next_permutation(sel.begin(), sel.end())); + + // Divide by point size in the cluster to get the average (may be redundant) + for(auto& a : avgs) a /= clust.size(); + + // get the lowest average distance and return the index + auto minit = std::min_element(avgs.begin(), avgs.end()); + return long(minit - avgs.begin()); +} + +/** + * This function will calculate the convex hull of the input point set and + * return the indices of those points belonging to the chull in the right + * (counter clockwise) order. The input is also the set of indices and a + * functor to get the actual point form the index. + * + * I've adapted this algorithm from here: + * https://www.geeksforgeeks.org/convex-hull-set-1-jarviss-algorithm-or-wrapping/ + * and modified it so that it starts with the leftmost lower vertex. Also added + * support for floating point coordinates. + * + * This function is a modded version of the standard convex hull. If the points + * are all collinear with each other, it will return their indices in spatially + * subsequent order (the order they appear on the screen). + */ +ClusterEl pts_convex_hull(const ClusterEl& inpts, + std::function pfn) +{ + using Point = Vec2d; + using std::vector; + + static const double ERR = 1e-3; + + auto orientation = [](const Point& p, const Point& q, const Point& r) + { + double val = (q(Y) - p(Y)) * (r(X) - q(X)) - + (q(X) - p(X)) * (r(Y) - q(Y)); + + if (std::abs(val) < ERR) return 0; // collinear + return (val > ERR)? 1: 2; // clock or counterclockwise + }; + + size_t n = inpts.size(); + + if (n < 3) return inpts; + + // Initialize Result + ClusterEl hull; + vector points; points.reserve(n); + for(auto i : inpts) { + points.emplace_back(pfn(i)); + } + + // Check if the triplet of points is collinear. The standard convex hull + // algorithms are not capable of handling such input properly. + bool collinear = true; + for(auto one = points.begin(), two = std::next(one), three = std::next(two); + three != points.end() && collinear; + ++one, ++two, ++three) + { + // check if the points are collinear + if(orientation(*one, *two, *three) != 0) collinear = false; + } + + // Find the leftmost (bottom) point + size_t l = 0; + for (size_t i = 1; i < n; i++) { + if(std::abs(points[i](X) - points[l](X)) < ERR) { + if(points[i](Y) < points[l](Y)) l = i; + } + else if (points[i](X) < points[l](X)) l = i; + } + + if(collinear) { + // fill the output with the spatially ordered set of points. + + // find the direction + hull = inpts; + auto& lp = points[l]; + std::sort(hull.begin(), hull.end(), + [&lp, points](unsigned i1, unsigned i2) { + // compare the distance from the leftmost point + return distance(lp, points[i1]) < distance(lp, points[i2]); + }); + + return hull; + } + + // TODO: this algorithm is O(m*n) and O(n^2) in the worst case so it needs + // to be replaced with a graham scan or something O(nlogn) + + // Start from leftmost point, keep moving counterclockwise + // until reach the start point again. This loop runs O(h) + // times where h is number of points in result or output. + size_t p = l; + do + { + // Add current point to result + hull.push_back(inpts[p]); + + // Search for a point 'q' such that orientation(p, x, + // q) is counterclockwise for all points 'x'. The idea + // is to keep track of last visited most counterclock- + // wise point in q. If any point 'i' is more counterclock- + // wise than q, then update q. + size_t q = (p + 1) % n; + for (size_t i = 0; i < n; i++) + { + // If i is more counterclockwise than current q, then + // update q + if (orientation(points[p], points[i], points[q]) == 2) q = i; + } + + // Now q is the most counterclockwise with respect to p + // Set p as q for next iteration, so that q is added to + // result 'hull' + p = q; + + } while (p != l); // While we don't come to first point + + auto first = hull.front(); + hull.emplace_back(first); + + return hull; +} + +Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { + return (endp - startp).normalized(); +} + +/// Generation of the supports, entry point function. This is called from the +/// SLASupportTree constructor and throws an SLASupportsStoppedException if it +/// gets canceled by the ctl object's stopcondition functor. +bool SLASupportTree::generate(const PointSet &points, + const EigenMesh3D& mesh, + const SupportConfig &cfg, + const Controller &ctl) +{ + // If there are no input points there is no point in doing anything + if(points.rows() == 0) return false; + + PointSet filtered_points; // all valid support points + PointSet head_positions; // support points with pinhead + PointSet head_normals; // head normals + PointSet headless_positions; // headless support points + PointSet headless_normals; // headless support point normals + + using IndexSet = std::vector; + + // Distances from head positions to ground or mesh touch points + std::vector head_heights; + + // Indices of those who touch the ground + IndexSet ground_heads; + + // Indices of those who don't touch the ground + IndexSet noground_heads; + + ClusteredPoints ground_connectors; + + auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) { + return Vec3d(head_positions.row(ground_heads[idx])); + }; + + using Result = SLASupportTree::Impl; + + Result& result = *m_impl; + + enum Steps { + BEGIN, + FILTER, + PINHEADS, + CLASSIFY, + ROUTING_GROUND, + ROUTING_NONGROUND, + HEADLESS, + DONE, + HALT, + ABORT, + NUM_STEPS + //... + }; + + // Debug: + // for(int pn = 0; pn < points.rows(); ++pn) { + // std::cout << "p " << pn << " " << points.row(pn) << std::endl; + // } + + + auto& tifcl = ctl.cancelfn; + + auto filterfn = [tifcl] ( + const SupportConfig& cfg, + const PointSet& points, + const EigenMesh3D& mesh, + PointSet& filt_pts, + PointSet& head_norm, + PointSet& head_pos, + PointSet& headless_pos, + PointSet& headless_norm) + { + /* ******************************************************** */ + /* Filtering step */ + /* ******************************************************** */ + + // Get the points that are too close to each other and keep only the + // first one + auto aliases = + cluster(points, + [tifcl](const SpatElement& p, const SpatElement& se) + { + tifcl(); + return distance(p.first, se.first) < D_SP; + }, 2); + + filt_pts.resize(Eigen::Index(aliases.size()), 3); + int count = 0; + for(auto& a : aliases) { + // Here we keep only the front point of the cluster. + filt_pts.row(count++) = points.row(a.front()); + } + + tifcl(); + + // calculate the normals to the triangles belonging to filtered points + auto nmls = sla::normals(filt_pts, mesh); + + head_norm.resize(count, 3); + head_pos.resize(count, 3); + headless_pos.resize(count, 3); + headless_norm.resize(count, 3); + + // Not all of the support points have to be a valid position for + // support creation. The angle may be inappropriate or there may + // not be enough space for the pinhead. Filtering is applied for + // these reasons. + + int pcount = 0, hlcount = 0; + for(int i = 0; i < count; i++) { + tifcl(); + auto n = nmls.row(i); + + // for all normals we generate the spherical coordinates and + // saturate the polar angle to 45 degrees from the bottom then + // convert back to standard coordinates to get the new normal. + // Then we just create a quaternion from the two normals + // (Quaternion::FromTwoVectors) and apply the rotation to the + // arrow head. + + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); + double azimuth = std::atan2(n(1), n(0)); + + if(polar >= PI / 2) { // skip if the tilt is not sane + + // We saturate the polar angle to 3pi/4 + polar = std::max(polar, 3*PI / 4); + + // Reassemble the now corrected normal + Vec3d nn(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)); + + // save the head (pinpoint) position + Vec3d hp = filt_pts.row(i); + + // the full width of the head + double w = cfg.head_width_mm + + cfg.head_back_radius_mm + + 2*cfg.head_front_radius_mm; + + // We should shoot a ray in the direction of the pinhead and + // see if there is enough space for it + double t = ray_mesh_intersect(hp + 0.1*nn, nn, mesh); + + if(t > 2*w || std::isinf(t)) { + // 2*w because of lower and upper pinhead + + head_pos.row(pcount) = hp; + + // save the verified and corrected normal + head_norm.row(pcount) = nn; + + ++pcount; + } else { + headless_norm.row(hlcount) = nn; + headless_pos.row(hlcount++) = hp; + } + } + } + + head_pos.conservativeResize(pcount, Eigen::NoChange); + head_norm.conservativeResize(pcount, Eigen::NoChange); + headless_pos.conservativeResize(hlcount, Eigen::NoChange); + headless_norm.conservativeResize(hlcount, Eigen::NoChange); + }; + + // Function to write the pinheads into the result + auto pinheadfn = [tifcl] ( + const SupportConfig& cfg, + PointSet& head_pos, + PointSet& nmls, + Result& result + ) + { + + /* ******************************************************** */ + /* Generating Pinheads */ + /* ******************************************************** */ + + for (int i = 0; i < head_pos.rows(); ++i) { + tifcl(); + result.add_head( + cfg.head_back_radius_mm, + cfg.head_front_radius_mm, + cfg.head_width_mm, + cfg.head_penetration_mm, + nmls.row(i), // dir + head_pos.row(i) // displacement + ); + } + }; + + // &filtered_points, &head_positions, &result, &mesh, + // &gndidx, &gndheight, &nogndidx, cfg + auto classifyfn = [tifcl] ( + const SupportConfig& cfg, + const EigenMesh3D& mesh, + PointSet& head_pos, + IndexSet& gndidx, + IndexSet& nogndidx, + std::vector& gndheight, + ClusteredPoints& ground_clusters, + Result& result + ) { + + /* ******************************************************** */ + /* Classification */ + /* ******************************************************** */ + + // We should first get the heads that reach the ground directly + gndheight.reserve(size_t(head_pos.rows())); + gndidx.reserve(size_t(head_pos.rows())); + nogndidx.reserve(size_t(head_pos.rows())); + + for(unsigned i = 0; i < head_pos.rows(); i++) { + tifcl(); + auto& head = result.heads()[i]; + + Vec3d dir(0, 0, -1); + Vec3d startpoint = head.junction_point(); + + double t = ray_mesh_intersect(startpoint, dir, mesh); + + gndheight.emplace_back(t); + + if(std::isinf(t)) gndidx.emplace_back(i); + else nogndidx.emplace_back(i); + } + + PointSet gnd(gndidx.size(), 3); + + for(size_t i = 0; i < gndidx.size(); i++) + gnd.row(long(i)) = head_pos.row(gndidx[i]); + + // We want to search for clusters of points that are far enough from + // each other in the XY plane to not cross their pillar bases + // These clusters of support points will join in one pillar, possibly in + // their centroid support point. + auto d_base = 2*cfg.base_radius_mm; + ground_clusters = + cluster( + gnd, + [d_base, tifcl](const SpatElement& p, const SpatElement& s) + { + tifcl(); + return distance(Vec2d(p.first(X), p.first(Y)), + Vec2d(s.first(X), s.first(Y))) < d_base; + }, 3); // max 3 heads to connect to one centroid + }; + + // Helper function for interconnecting two pillars with zig-zag bridges + auto interconnect = [&cfg]( + const Pillar& pillar, + const Pillar& nextpillar, + const EigenMesh3D& emesh, + Result& result) + { + const Head& phead = result.pillar_head(pillar.id); + const Head& nextphead = result.pillar_head(nextpillar.id); + + Vec3d sj = phead.junction_point(); + sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); + Vec3d ej = nextpillar.endpoint; + double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, + Vec2d{ej(X), ej(Y)}); + double zstep = pillar_dist * std::tan(-cfg.tilt); + ej(Z) = sj(Z) + zstep; + + double chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); + double bridge_distance = pillar_dist / std::cos(-cfg.tilt); + + // If the pillars are so close that they touch each other, + // there is no need to bridge them together. + if(pillar_dist > 2*cfg.head_back_radius_mm && + bridge_distance < cfg.max_bridge_length_mm) + while(sj(Z) > pillar.endpoint(Z) && + ej(Z) > nextpillar.endpoint(Z)) + { + if(chkd >= bridge_distance) { + result.add_bridge(sj, ej, pillar.r); + + // double bridging: (crosses) + if(bridge_distance > 2*cfg.base_radius_mm) { + // If the columns are close together, no need to + // double bridge them + Vec3d bsj(ej(X), ej(Y), sj(Z)); + Vec3d bej(sj(X), sj(Y), ej(Z)); + + // need to check collision for the cross stick + double backchkd = ray_mesh_intersect(bsj, + dirv(bsj, bej), + emesh); + + if(backchkd >= bridge_distance) { + result.add_bridge(bsj, bej, pillar.r); + } + } + } + sj.swap(ej); + ej(Z) = sj(Z) + zstep; + chkd = ray_mesh_intersect(sj, dirv(sj, ej), emesh); + } + }; + + auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl]( + const SupportConfig& cfg, + const ClusteredPoints& gnd_clusters, + const IndexSet& gndidx, + const EigenMesh3D& emesh, + Result& result) + { + const double hbr = cfg.head_back_radius_mm; + const double pradius = cfg.head_back_radius_mm; + const double maxbridgelen = cfg.max_bridge_length_mm; + const double gndlvl = result.ground_level; + + ClusterEl cl_centroids; + cl_centroids.reserve(gnd_clusters.size()); + + SpatIndex pheadindex; // spatial index for the junctions + for(auto& cl : gnd_clusters) { tifcl(); + // place all the centroid head positions into the index. We will + // query for alternative pillar positions. If a sidehead cannot + // connect to the cluster centroid, we have to search for another + // head with a full pillar. Also when there are two elements in the + // cluster, the centroid is arbitrary and the sidehead is allowed to + // connect to a nearby pillar to increase structural stability. + if(cl.empty()) continue; + + // get the current cluster centroid + long lcid = cluster_centroid(cl, gnd_head_pt, + [tifcl](const Vec3d& p1, const Vec3d& p2) + { + tifcl(); + return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y))); + }); + + assert(lcid >= 0); + auto cid = unsigned(lcid); + + cl_centroids.push_back(unsigned(cid)); + + unsigned hid = gndidx[cl[cid]]; // Head index + Head& h = result.head(hid); + h.transform(); + Vec3d p = h.junction_point(); p(Z) = gndlvl; + + pheadindex.insert(p, hid); + } + + // now we will go through the clusters ones again and connect the + // sidepoints with the cluster centroid (which is a ground pillar) + // or a nearby pillar if the centroid is unreachable. + size_t ci = 0; + for(auto cl : gnd_clusters) { tifcl(); + + auto cidx = cl_centroids[ci]; + cl_centroids[ci++] = cl[cidx]; + + size_t index_to_heads = gndidx[cl[cidx]]; + auto& head = result.head(index_to_heads); + + Vec3d startpoint = head.junction_point(); + auto endpoint = startpoint; endpoint(Z) = gndlvl; + + // Create the central pillar of the cluster with its base on the + // ground + result.add_pillar(long(index_to_heads), endpoint, pradius) + .add_base(cfg.base_height_mm, cfg.base_radius_mm); + + // Process side point in current cluster + cl.erase(cl.begin() + cidx); // delete the centroid before looping + + // TODO: dont consider the cluster centroid but calculate a central + // position where the pillar can be placed. this way the weight + // is distributed more effectively on the pillar. + + auto search_nearest = + [&cfg, &result, &emesh, maxbridgelen, gndlvl] + (SpatIndex& spindex, const Vec3d& jsh) + { + long nearest_id = -1; + const double max_len = maxbridgelen / 2; + while(nearest_id < 0 && !spindex.empty()) { + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(jsh(X), jsh(Y), gndlvl); + auto ne = spindex.nearest(qp, 1).front(); + const Head& nearhead = result.heads()[ne.second]; + + Vec3d jh = nearhead.junction_point(); + Vec3d jp = jsh; + double dist2d = distance(qp, ne.first); + + // Bridge endpoint on the main pillar + Vec3d jn(jh(X), jh(Y), jp(Z) + dist2d*std::tan(-cfg.tilt)); + + if(jn(Z) > jh(Z)) { + // If the sidepoint cannot connect to the pillar from + // its head junction, then just skip this pillar. + spindex.remove(ne); + continue; + } + + double d = distance(jp, jn); + if(jn(Z) <= gndlvl || d > max_len) break; + + double chkd = ray_mesh_intersect(jp, dirv(jp, jn), emesh); + if(chkd >= d) nearest_id = ne.second; + + spindex.remove(ne); + } + return nearest_id; + }; + + for(auto c : cl) { tifcl(); + auto& sidehead = result.head(gndidx[c]); + sidehead.transform(); + + Vec3d jsh = sidehead.junction_point(); + SpatIndex spindex = pheadindex; + long nearest_id = search_nearest(spindex, jsh); + + // at this point we either have our pillar index or we have + // to connect the sidehead to the ground + if(nearest_id < 0) { + // Could not find a pillar, create one + Vec3d jp = jsh; jp(Z) = gndlvl; + result.add_pillar(sidehead.id, jp, pradius). + add_base(cfg.base_height_mm, cfg.base_radius_mm); + + // connects to ground, eligible for bridging + cl_centroids.emplace_back(c); + } else { + // Creating the bridge to the nearest pillar + + const Head& nearhead = result.heads()[size_t(nearest_id)]; + Vec3d jp = jsh; + Vec3d jh = nearhead.junction_point(); + + double d = distance(Vec2d{jp(X), jp(Y)}, + Vec2d{jh(X), jh(Y)}); + Vec3d jn(jh(X), jh(Y), jp(Z) + d*std::tan(-cfg.tilt)); + + if(jn(Z) > jh(Z)) { + double hdiff = jn(Z) - jh(Z); + jp(Z) -= hdiff; + jn(Z) -= hdiff; + + // pillar without base, this does not connect to ground. + result.add_pillar(sidehead.id, jp, pradius); + } + + if(jp(Z) < jsh(Z)) result.add_junction(jp, hbr); + if(jn(Z) >= jh(Z)) result.add_junction(jn, hbr); + double r_pillar = sidehead.request_pillar_radius(pradius); + result.add_bridge(jp, jn, r_pillar); + } + } + } + + // We will break down the pillar positions in 2D into concentric rings. + // Connecting the pillars belonging to the same ring will prevent + // bridges from crossing each other. After bridging the rings we can + // create bridges between the rings without the possibility of crossing + // bridges. Two pillars will be bridged with X shaped stick pairs. + // If they are really close to each other, than only one stick will be + // used in zig-zag mode. + + // Breaking down the points into rings will be done with a modified + // convex hull algorithm (see pts_convex_hull()), that works for + // collinear points as well. If the points are on the same surface, + // they can be part of an imaginary line segment for which the convex + // hull is not defined. I this case it is enough to sort the points + // spatially and create the bridge stick from the one endpoint to + // another. + + ClusterEl rem = cl_centroids; + ClusterEl ring; + + while(!rem.empty()) { // loop until all the points belong to some ring + tifcl(); + std::sort(rem.begin(), rem.end()); + + auto newring = pts_convex_hull(rem, + [gnd_head_pt](unsigned i) { + auto&& p = gnd_head_pt(i); + return Vec2d(p(X), p(Y)); // project to 2D in along Z axis + }); + + if(!ring.empty()) { + // inner ring is now in 'newring' and outer ring is in 'ring' + SpatIndex innerring; + for(unsigned i : newring) { + const Pillar& pill = result.head_pillar(gndidx[i]); + assert(pill.id >= 0); + innerring.insert(pill.endpoint, unsigned(pill.id)); + } + + // For all pillars in the outer ring find the closest in the + // inner ring and connect them. This will create the spider web + // fashioned connections between pillars + for(unsigned i : ring) { + const Pillar& outerpill = result.head_pillar(gndidx[i]); + auto res = innerring.nearest(outerpill.endpoint, 1); + if(res.empty()) continue; + + auto ne = res.front(); + const Pillar& innerpill = result.pillars()[ne.second]; + interconnect(outerpill, innerpill, emesh, result); + } + } + + // no need for newring anymore in the current iteration + ring.swap(newring); + + /*std::cout << "ring: \n"; + for(auto ri : ring) { + std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X) + << " Y = " << gnd_head_pt(ri)(Y) << std::endl; + } + std::cout << std::endl;*/ + + // now the ring has to be connected with bridge sticks + for(auto it = ring.begin(), next = std::next(it); + next != ring.end(); + ++it, ++next) + { + const Pillar& pillar = result.head_pillar(gndidx[*it]); + const Pillar& nextpillar = result.head_pillar(gndidx[*next]); + interconnect(pillar, nextpillar, emesh, result); + } + + auto sring = ring; ClusterEl tmp; + std::sort(sring.begin(), sring.end()); + std::set_difference(rem.begin(), rem.end(), + sring.begin(), sring.end(), + std::back_inserter(tmp)); + rem.swap(tmp); + } + }; + + auto routing_nongnd_fn = [tifcl]( + const SupportConfig& cfg, + const std::vector& gndheight, + const IndexSet& nogndidx, + Result& result) + { + // TODO: connect these to the ground pillars if possible + for(auto idx : nogndidx) { tifcl(); + auto& head = result.head(idx); + head.transform(); + + double gh = gndheight[idx]; + Vec3d headend = head.junction_point(); + + Head base_head(cfg.head_back_radius_mm, + cfg.head_front_radius_mm, + cfg.head_width_mm, + cfg.head_penetration_mm, + {0.0, 0.0, 1.0}, + {headend(X), headend(Y), headend(Z) - gh}); + + base_head.transform(); + + double hl = head.fullwidth() - head.r_back_mm; + + result.add_pillar(idx, + Vec3d{headend(X), headend(Y), headend(Z) - gh + hl}, + cfg.head_back_radius_mm + ).base = base_head.mesh; + } + }; + + auto process_headless = [tifcl]( + const SupportConfig& cfg, + const PointSet& headless_pts, + const PointSet& headless_norm, + const EigenMesh3D& emesh, + Result& result) + { + // For now we will just generate smaller headless sticks with a sharp + // ending point that connects to the mesh surface. + + const double R = cfg.headless_pillar_radius_mm; + const double HWIDTH_MM = R/3; + + // We will sink the pins into the model surface for a distance of 1/3 of + // HWIDTH_MM + for(int i = 0; i < headless_pts.rows(); i++) { tifcl(); + Vec3d sp = headless_pts.row(i); + + Vec3d n = headless_norm.row(i); + sp = sp - n * HWIDTH_MM; + + Vec3d dir = {0, 0, -1}; + Vec3d sj = sp + R * n; + double dist = ray_mesh_intersect(sj, dir, emesh); + + if(std::isinf(dist) || std::isnan(dist)) continue; + + Vec3d ej = sj + (dist + HWIDTH_MM)* dir; + result.add_compact_bridge(sp, ej, n, R); + } + }; + + using std::ref; + using std::cref; + using std::bind; + + // Here we can easily track what goes in and what comes out of each step: + // (see the cref-s as inputs and ref-s as outputs) + std::array, NUM_STEPS> program = { + [] () { + // Begin + // clear up the shared data + }, + + // Filtering unnecessary support points + bind(filterfn, cref(cfg), cref(points), cref(mesh), + ref(filtered_points), ref(head_normals), + ref(head_positions), ref(headless_positions), ref(headless_normals)), + + // Pinhead generation + bind(pinheadfn, cref(cfg), + ref(head_positions), ref(head_normals), ref(result)), + + // Classification of support points + bind(classifyfn, cref(cfg), cref(mesh), + ref(head_positions), ref(ground_heads), ref(noground_heads), + ref(head_heights), ref(ground_connectors), ref(result)), + + // Routing ground connecting clusters + bind(routing_ground_fn, + cref(cfg), cref(ground_connectors), cref(ground_heads), cref(mesh), + ref(result)), + + // routing non ground connecting support points + bind(routing_nongnd_fn, cref(cfg), cref(head_heights), cref(noground_heads), + ref(result)), + + bind(process_headless, + cref(cfg), cref(headless_positions), + cref(headless_normals), cref(mesh), + ref(result)), + [] () { + // Done + }, + [] () { + // Halt + }, + [] () { + // Abort + } + }; + + Steps pc = BEGIN, pc_prev = BEGIN; + + auto progress = [&ctl, &pc, &pc_prev] () { + static const std::array stepstr { + "Starting", + "Filtering", + "Generate pinheads", + "Classification", + "Routing to ground", + "Routing supports to model surface", + "Processing small holes", + "Done", + "Halt", + "Abort" + }; + + static const std::array stepstate { + 0, + 10, + 30, + 50, + 60, + 70, + 80, + 100, + 0, + 0 + }; + + if(ctl.stopcondition()) pc = ABORT; + + switch(pc) { + case BEGIN: pc = FILTER; break; + case FILTER: pc = PINHEADS; break; + case PINHEADS: pc = CLASSIFY; break; + case CLASSIFY: pc = ROUTING_GROUND; break; + case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; + case ROUTING_NONGROUND: pc = HEADLESS; break; + case HEADLESS: pc = DONE; break; + case HALT: pc = pc_prev; break; + case DONE: + case ABORT: break; + default: ; + } + ctl.statuscb(stepstate[pc], stepstr[pc]); + }; + + // Just here we run the computation... + while(pc < DONE || pc == HALT) { + progress(); + program[pc](); + } + + if(pc == ABORT) throw SLASupportsStoppedException(); + + return pc == ABORT; +} + +SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} + +const TriangleMesh &SLASupportTree::merged_mesh() const +{ + return get().merged_mesh(); +} + +void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { + outmesh.merge(merged_mesh()); + outmesh.merge(get_pad()); +} + +SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const +{ + if(init_layerh < 0) init_layerh = layerh; + auto& stree = get(); + + const auto modelh = float(stree.full_height()); + auto gndlvl = float(this->m_impl->ground_level); + const Pad& pad = m_impl->pad(); + if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg)); + + std::vector heights = {gndlvl}; + heights.reserve(size_t(modelh/layerh) + 1); + + for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) { + heights.emplace_back(h); + } + + TriangleMesh fullmesh = m_impl->merged_mesh(); + fullmesh.merge(get_pad()); + TriangleMeshSlicer slicer(&fullmesh); + SlicedSupports ret; + slicer.slice(heights, &ret, get().ctl().cancelfn); + + return ret; +} + +const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, + const PoolConfig& pcfg) const +{ +// PoolConfig pcfg; +// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; +// pcfg.min_wall_height_mm = min_wall_height_mm; +// pcfg.max_merge_distance_mm = max_merge_distance_mm; +// pcfg.edge_radius_mm = edge_radius_mm; + return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; +} + +const TriangleMesh &SLASupportTree::get_pad() const +{ + return m_impl->pad().tmesh; +} + +SLASupportTree::SLASupportTree(const PointSet &points, + const EigenMesh3D& emesh, + const SupportConfig &cfg, + const Controller &ctl): + m_impl(new Impl(ctl)) +{ + m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm; + generate(points, emesh, cfg, ctl); +} + +SLASupportTree::SLASupportTree(const SLASupportTree &c): + m_impl(new Impl(*c.m_impl)) {} + +SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) +{ + m_impl = make_unique(*c.m_impl); + return *this; +} + +SLASupportTree::~SLASupportTree() {} + +SLASupportsStoppedException::SLASupportsStoppedException(): + std::runtime_error("") {} + +} +} diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp new file mode 100644 index 000000000..62e790611 --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -0,0 +1,173 @@ +#ifndef SLASUPPORTTREE_HPP +#define SLASUPPORTTREE_HPP + +#include +#include +#include +#include +#include + +namespace Slic3r { + +// Needed types from Point.hpp +typedef int32_t coord_t; +typedef Eigen::Matrix Vec3d; +typedef Eigen::Matrix Vec3f; +typedef Eigen::Matrix Vec3crd; +typedef std::vector Pointf3s; +typedef std::vector Points3; + +class TriangleMesh; +class Model; +class ModelInstance; +class ModelObject; +class ExPolygon; + +using SliceLayer = std::vector; +using SlicedSupports = std::vector; + +namespace sla { + +struct SupportConfig { + // Radius in mm of the pointing side of the head. + double head_front_radius_mm = 0.2; + + // How much the pinhead has to penetrate the model surface + double head_penetration_mm = 0.5; + + // Radius of the back side of the 3d arrow. + double head_back_radius_mm = 0.5; + + // Width in mm from the back sphere center to the front sphere center. + double head_width_mm = 1.0; + + // Radius in mm of the support pillars. The actual radius of the pillars + // beginning with a head will not be higher than head_back_radius but the + // headless pillars will have half of this value. + double headless_pillar_radius_mm = 0.4; + + // TODO: unimplemented at the moment. This coefficient will have an impact + // when bridges and pillars are merged. The resulting pillar should be a bit + // thicker than the ones merging into it. How much thicker? I don't know + // but it will be derived from this value. + double pillar_widening_factor = 0.5; + + // Radius in mm of the pillar base. + double base_radius_mm = 2.0; + + // The height of the pillar base cone in mm. + double base_height_mm = 1.0; + + // The default angle for connecting support sticks and junctions. + double tilt = M_PI/4; + + // The max length of a bridge in mm + double max_bridge_length_mm = 15.0; + + // The elevation in Z direction upwards. This is the space between the pad + // and the model object's bounding box bottom. + double object_elevation_mm = 10; +}; + +struct PoolConfig; + +/// A Control structure for the support calculation. Consists of the status +/// indicator callback and the stop condition predicate. +struct Controller { + + // This will signal the status of the calculation to the front-end + std::function statuscb = + [](unsigned, const std::string&){}; + + // Returns true if the calculation should be aborted. + std::function stopcondition = [](){ return false; }; + + // Similar to cancel callback. This should check the stop condition and + // if true, throw an appropriate exception. (TriangleMeshSlicer needs this) + // consider it a hard abort. stopcondition is permits the algorithm to + // terminate itself + std::function cancelfn = [](){}; +}; + +/// An index-triangle structure for libIGL functions. Also serves as an +/// alternative (raw) input format for the SLASupportTree +struct EigenMesh3D { + Eigen::MatrixXd V; + Eigen::MatrixXi F; + double ground_level = 0; +}; + +using PointSet = Eigen::MatrixXd; + +EigenMesh3D to_eigenmesh(const TriangleMesh& m); + +// needed for find best rotation +EigenMesh3D to_eigenmesh(const ModelObject& model); + +// Simple conversion of 'vector of points' to an Eigen matrix +PointSet to_point_set(const std::vector&); + + +/* ************************************************************************** */ + +/// Just a wrapper to the runtime error to be recognizable in try blocks +class SLASupportsStoppedException: public std::runtime_error { +public: + using std::runtime_error::runtime_error; + SLASupportsStoppedException(); +}; + +/// The class containing mesh data for the generated supports. +class SLASupportTree { + class Impl; + std::unique_ptr m_impl; + + Impl& get() { return *m_impl; } + const Impl& get() const { return *m_impl; } + + friend void add_sla_supports(Model&, + const SupportConfig&, + const Controller&); + + /// Generate the 3D supports for a model intended for SLA print. + bool generate(const PointSet& pts, + const EigenMesh3D& mesh, + const SupportConfig& cfg = {}, + const Controller& ctl = {}); +public: + + SLASupportTree(); + + SLASupportTree(const PointSet& pts, + const EigenMesh3D& em, + const SupportConfig& cfg = {}, + const Controller& ctl = {}); + + SLASupportTree(const SLASupportTree&); + SLASupportTree& operator=(const SLASupportTree&); + + ~SLASupportTree(); + + /// Get the whole mesh united into the output TriangleMesh + /// WITHOUT THE PAD + const TriangleMesh& merged_mesh() const; + + void merged_mesh_with_pad(TriangleMesh&) const; + + /// Get the sliced 2d layers of the support geometry. + SlicedSupports slice(float layerh, float init_layerh = -1.0) const; + + /// Adding the "pad" (base pool) under the supports + const TriangleMesh& add_pad(const SliceLayer& baseplate, + const PoolConfig& pcfg) const; + + /// Get the pad geometry + const TriangleMesh& get_pad() const; + +}; + +} + +} + +#endif // SLASUPPORTTREE_HPP diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp new file mode 100644 index 000000000..50d7775a2 --- /dev/null +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -0,0 +1,252 @@ +#include "SLA/SLASupportTree.hpp" +#include "SLA/SLABoilerPlate.hpp" +#include "SLA/SLASpatIndex.hpp" + +// HEAVY headers... takes eternity to compile + +// for concave hull merging decisions +#include "SLABoostAdapter.hpp" +#include "boost/geometry/index/rtree.hpp" + +#include + +//#if !defined(_MSC_VER) || defined(_WIN64) +#if 1 +#define IGL_COMPATIBLE +#endif + +#ifdef IGL_COMPATIBLE +#include +#endif + +#include "SLASpatIndex.hpp" +#include "ClipperUtils.hpp" + +namespace Slic3r { +namespace sla { + +class SpatIndex::Impl { +public: + using BoostIndex = boost::geometry::index::rtree< SpatElement, + boost::geometry::index::rstar<16, 4> /* ? */ >; + + BoostIndex m_store; +}; + +SpatIndex::SpatIndex(): m_impl(new Impl()) {} +SpatIndex::~SpatIndex() {} + +SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void SpatIndex::insert(const SpatElement &el) +{ + m_impl->m_store.insert(el); +} + +bool SpatIndex::remove(const SpatElement& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector +SpatIndex::query(std::function fn) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; + m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); + return ret; +} + +std::vector SpatIndex::nearest(const Vec3d &el, unsigned k = 1) +{ + namespace bgi = boost::geometry::index; + std::vector ret; ret.reserve(k); + m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); + return ret; +} + +size_t SpatIndex::size() const +{ + return m_impl->m_store.size(); +} + +PointSet normals(const PointSet& points, const EigenMesh3D& mesh) { + if(points.rows() == 0 || mesh.V.rows() == 0 || mesh.F.rows() == 0) return {}; +#ifdef IGL_COMPATIBLE + Eigen::VectorXd dists; + Eigen::VectorXi I; + PointSet C; + + igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); + + PointSet ret(I.rows(), 3); + for(int i = 0; i < I.rows(); i++) { + auto idx = I(i); + auto trindex = mesh.F.row(idx); + + auto& p1 = mesh.V.row(trindex(0)); + auto& p2 = mesh.V.row(trindex(1)); + auto& p3 = mesh.V.row(trindex(2)); + + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + ret.row(i) = U.cross(V).normalized(); + } + + return ret; +#else // TODO: do something on 32 bit windows + return {}; +#endif +} + +double ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir, + const EigenMesh3D& m) +{ + igl::Hit hit; + hit.t = std::numeric_limits::infinity(); + igl::ray_mesh_intersect(s, dir, m.V, m.F, hit); + return double(hit.t); +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const sla::PointSet& points, + std::function pred, + unsigned max_points = 0) +{ + + namespace bgi = boost::geometry::index; + using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; + + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(unsigned idx = 0; idx < points.rows(); idx++) + sindex.insert( std::make_pair(points.row(idx), idx)); + + using Elems = std::vector; + + // Recursive function for visiting all the points in a given distance to + // each other + std::function group = + [&sindex, &group, pred, max_points](Elems& pts, Elems& cluster) + { + for(auto& p : pts) { + std::vector tmp; + + sindex.query( + bgi::satisfies([p, pred](const SpatElement& se) { + return pred(p, se); + }), + std::back_inserter(tmp) + ); + + auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + return e1.second < e2.second; + }; + + std::sort(tmp.begin(), tmp.end(), cmp); + + Elems newpts; + std::set_difference(tmp.begin(), tmp.end(), + cluster.begin(), cluster.end(), + std::back_inserter(newpts), cmp); + + int c = max_points && newpts.size() + cluster.size() > max_points? + int(max_points - cluster.size()) : int(newpts.size()); + + cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); + std::sort(cluster.begin(), cluster.end(), cmp); + + if(!newpts.empty() && (!max_points || cluster.size() < max_points)) + group(newpts, cluster); + } + }; + + std::vector clusters; + for(auto it = sindex.begin(); it != sindex.end();) { + Elems cluster = {}; + Elems pts = {*it}; + group(pts, cluster); + + for(auto& c : cluster) sindex.remove(c); + it = sindex.begin(); + + clusters.emplace_back(cluster); + } + + ClusteredPoints result; + for(auto& cluster : clusters) { + result.emplace_back(); + for(auto c : cluster) result.back().emplace_back(c.second); + } + + return result; +} + +using Segments = std::vector>; + +Segments model_boundary(const EigenMesh3D& emesh, double offs) +{ + Segments ret; + Polygons pp; + pp.reserve(size_t(emesh.F.rows())); + + for (int i = 0; i < emesh.F.rows(); i++) { + auto trindex = emesh.F.row(i); + auto& p1 = emesh.V.row(trindex(0)); + auto& p2 = emesh.V.row(trindex(1)); + auto& p3 = emesh.V.row(trindex(2)); + + Polygon p; + p.points.resize(3); + p.points[0] = Point::new_scale(p1(X), p1(Y)); + p.points[1] = Point::new_scale(p2(X), p2(Y)); + p.points[2] = Point::new_scale(p3(X), p3(Y)); + p.make_counter_clockwise(); + pp.emplace_back(p); + } + + ExPolygons merged = union_ex(offset(pp, float(scale_(offs))), true); + + for(auto& expoly : merged) { + auto lines = expoly.lines(); + for(Line& l : lines) { + Vec2d a(l.a(X) * SCALING_FACTOR, l.a(Y) * SCALING_FACTOR); + Vec2d b(l.b(X) * SCALING_FACTOR, l.b(Y) * SCALING_FACTOR); + ret.emplace_back(std::make_pair(a, b)); + } + } + + return ret; +} + +//struct SegmentIndex { + +//}; + +//using SegmentIndexEl = std::pair; + +//SegmentIndexEl + + + + +} +} diff --git a/src/libslic3r/SLABasePool.hpp b/src/libslic3r/SLABasePool.hpp deleted file mode 100644 index 55c94df07..000000000 --- a/src/libslic3r/SLABasePool.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SLASUPPORTPOOL_HPP -#define SLASUPPORTPOOL_HPP - -#include - -namespace Slic3r { - -class ExPolygon; -class TriangleMesh; - -namespace sla { - -using ExPolygons = std::vector; - -/// Calculate the polygon representing the slice of the lowest layer of mesh -void ground_layer(const TriangleMesh& mesh, - ExPolygons& output, - float height = 0.1f); - -/// Calculate the pool for the mesh for SLA printing -void create_base_pool(const ExPolygons& ground_layer, - TriangleMesh& output_mesh, - double min_wall_thickness_mm = 2, - double min_wall_height_mm = 5, - double max_merge_distance_mm = 50 - ); - -} - -} - -#endif // SLASUPPORTPOOL_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp new file mode 100644 index 000000000..90abe290f --- /dev/null +++ b/src/libslic3r/SLAPrint.cpp @@ -0,0 +1,1186 @@ +#include "SLAPrint.hpp" +#include "SLA/SLASupportTree.hpp" +#include "SLA/SLABasePool.hpp" +#include "MTUtils.hpp" + +#include +#include + +#include +#include + +//#include //#include "tbb/mutex.h" + +#include "I18N.hpp" + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + +namespace Slic3r { + +using SupportTreePtr = std::unique_ptr; + +class SLAPrintObject::SupportData { +public: + sla::EigenMesh3D emesh; // index-triangle representation + sla::PointSet support_points; // all the support points (manual/auto) + SupportTreePtr support_tree_ptr; // the supports + SlicedSupports support_slices; // sliced supports + std::vector level_ids; +}; + +namespace { + +// should add up to 100 (%) +const std::array OBJ_STEP_LEVELS = +{ + 10, // slaposObjectSlice, + 10, // slaposSupportIslands, + 20, // slaposSupportPoints, + 25, // slaposSupportTree, + 25, // slaposBasePool, + 5, // slaposSliceSupports, + 5 // slaposIndexSlices +}; + +const std::array OBJ_STEP_LABELS = +{ + L("Slicing model"), // slaposObjectSlice, + L("Generating islands"), // slaposSupportIslands, + L("Scanning model structure"), // slaposSupportPoints, + L("Generating support tree"), // slaposSupportTree, + L("Generating base pool"), // slaposBasePool, + L("Slicing supports"), // slaposSliceSupports, + L("Slicing supports") // slaposIndexSlices, +}; + +// Should also add up to 100 (%) +const std::array PRINT_STEP_LEVELS = +{ + 80, // slapsRasterize + 20, // slapsValidate +}; + +const std::array PRINT_STEP_LABELS = +{ + L("Rasterizing layers"), // slapsRasterize + L("Validating"), // slapsValidate +}; + +} + +void SLAPrint::clear() +{ + tbb::mutex::scoped_lock lock(this->state_mutex()); + // The following call should stop background processing if it is running. + this->invalidate_all_steps(); + for (SLAPrintObject *object : m_objects) + delete object; + m_objects.clear(); + m_model.clear_objects(); +} + +// Transformation without rotation around Z and without a shift by X and Y. +static Transform3d sla_trafo(const ModelObject &model_object) +{ + ModelInstance &model_instance = *model_object.instances.front(); + Vec3d offset = model_instance.get_offset(); + Vec3d rotation = model_instance.get_rotation(); + offset(0) = 0.; + offset(1) = 0.; + rotation(2) = 0.; + return Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror()); +} + +// List of instances, where the ModelInstance transformation is a composite of sla_trafo and the transformation defined by SLAPrintObject::Instance. +static std::vector sla_instances(const ModelObject &model_object) +{ + std::vector instances; + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + instances.emplace_back(SLAPrintObject::Instance( + model_instance->id(), + Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)), + float(model_instance->get_rotation(Z)))); + } + return instances; +} + +SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConfig &config_in) +{ +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Make a copy of the config, normalize it. + DynamicPrintConfig config(config_in); + config.normalize(); + // Collect changes to print config. + t_config_option_keys print_diff = m_print_config.diff(config); + t_config_option_keys printer_diff = m_printer_config.diff(config); + t_config_option_keys material_diff = m_material_config.diff(config); + t_config_option_keys object_diff = m_default_object_config.diff(config); + t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (print_diff.empty() && printer_diff.empty() && material_diff.empty() && object_diff.empty())) + update_apply_status(false); + + // Grab the lock for the Print / PrintObject milestones. + tbb::mutex::scoped_lock lock(this->state_mutex()); + + // The following call may stop the background processing. + if (! print_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(print_diff)); + if (! printer_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(printer_diff)); + if (! material_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(material_diff)); + + // Apply variables to placeholder parser. The placeholder parser is currently used + // only to generate the output file name. + if (! placeholder_parser_diff.empty()) { + // update_apply_status(this->invalidate_step(slapsRasterize)); + PlaceholderParser &pp = this->placeholder_parser(); + pp.apply_config(config); + // Set the profile aliases for the PrintBase::output_filename() + pp.set("print_preset", config_in.option("sla_print_settings_id")->clone()); + pp.set("material_preset", config_in.option("sla_material_settings_id")->clone()); + pp.set("printer_preset", config_in.option("printer_settings_id")->clone()); + } + + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_print_config.apply_only(config, print_diff, true); + m_printer_config.apply_only(config, printer_diff, true); + // Handle changes to material config. + m_material_config.apply_only(config, material_diff, true); + // Handle changes to object config defaults + m_default_object_config.apply_only(config, object_diff, true); + + struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} + ModelID id; + Status status; + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } + }; + std::set model_object_status; + + // 1) Synchronize model objects. + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // Stop background processing. + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + for (SLAPrintObject *object : m_objects) { + model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + delete object; + } + m_objects.clear(); + m_model.assign_copy(model); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::New); + } else { + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(slapsRasterize)); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); + m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancell_callback(); + update_apply_status(this->invalidate_step(slapsRasterize)); + // Second create a new list of objects. + std::vector model_objects_old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&m_model); + model_object_status.emplace(mobj->id(), ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status.emplace(mobj->id(), ModelObjectStatus::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) { + if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { + model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); + deleted_any = true; + } else + // Do not delete this ModelObject instance. + model_object = nullptr; + } + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + std::vector print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (SLAPrintObject *print_object : print_objects_old) { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + if (it_status->status == ModelObjectStatus::Deleted) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; + } + } + } + + // 2) Map print objects including their transformation matrices. + struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + PrintObjectStatus(SLAPrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + // ID of the ModelObject & PrintObject + ModelID id; + // Pointer to the old PrintObject + SLAPrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } + }; + std::multiset print_object_status; + for (SLAPrintObject *print_object : m_objects) + print_object_status.emplace(PrintObjectStatus(print_object)); + + // 3) Synchronize ModelObjects & PrintObjects. + std::vector print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + if (it_status->status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + auto it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); + if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) + it_print_object_status = print_object_status.end(); + // Check whether a model part volume was added or removed, their transformations or order changed. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART); + bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || + (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); + if (model_parts_differ || sla_trafo_differs) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + if (it_print_object_status != print_object_status.end()) { + update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + // Synchronize Object's config. + bool object_config_changed = model_object.config != model_object_new.config; + if (object_config_changed) + model_object.config = model_object_new.config; + if (! object_diff.empty() || object_config_changed) { + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + if (it_print_object_status != print_object_status.end()) { + t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); + it_print_object_status->print_object->config_apply_only(new_config, diff, true); + } + } + } + if (model_object.sla_support_points != model_object_new.sla_support_points) { + model_object.sla_support_points = model_object_new.sla_support_points; + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + } + // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } + + std::vector new_instances = sla_instances(model_object); + if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { + // The SLAPrintObject is already there. + if (new_instances != it_print_object_status->print_object->instances()) { + // Instances changed. + it_print_object_status->print_object->set_instances(new_instances); + update_apply_status(this->invalidate_step(slapsRasterize)); + } + print_objects_new.emplace_back(it_print_object_status->print_object); + const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; + } else { + auto print_object = new SLAPrintObject(this, &model_object); + print_object->set_trafo(sla_trafo(model_object)); + print_object->set_instances(new_instances); + print_object->config_apply(config, true); + print_objects_new.emplace_back(print_object); + new_objects = true; + } + } + + if (m_objects != print_objects_new) { + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (auto &pos : print_object_status) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + // update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + update_apply_status(new_objects); + } + + this->update_object_placeholders(); + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); +} + +namespace { +// Compile the argument for support creation from the static print config. +sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { + sla::SupportConfig scfg; + + scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); + scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); + scfg.head_penetration_mm = c.support_head_penetration.getFloat(); + scfg.head_width_mm = c.support_head_width.getFloat(); + scfg.object_elevation_mm = c.support_object_elevation.getFloat(); + scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); + scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat(); + scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); + scfg.base_height_mm = c.support_base_height.getFloat(); + + return scfg; +} + +void swapXY(ExPolygon& expoly) { + for(auto& p : expoly.contour.points) std::swap(p(X), p(Y)); + for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); +} + +} + +template +void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) { + BOOST_LOG_TRIVIAL(info) << st << "% " << msg; + p.set_status(st, msg, std::forward(args)...); +} + +void SLAPrint::process() +{ + using namespace sla; + using ExPolygon = Slic3r::ExPolygon; + + // Assumption: at this point the print objects should be populated only with + // the model objects we have to process and the instances are also filtered + + // shortcut to initial layer height + double ilhd = m_material_config.initial_layer_height.getFloat(); + auto ilh = float(ilhd); + const size_t objcount = m_objects.size(); + + const unsigned min_objstatus = 0; // where the per object operations start + const unsigned max_objstatus = 80; // where the per object operations end + + // the coefficient that multiplies the per object status values which + // are set up for <0, 100>. They need to be scaled into the whole process + const double ostepd = (max_objstatus - min_objstatus) / (objcount * 100.0); + + // The slicing will be performed on an imaginary 1D grid which starts from + // the bottom of the bounding box created around the supported model. So + // the first layer which is usually thicker will be part of the supports + // not the model geometry. Exception is when the model is not in the air + // (elevation is zero) and no pad creation was requested. In this case the + // model geometry starts on the ground level and the initial layer is part + // of it. In any case, the model and the supports have to be sliced in the + // same imaginary grid (the height vector argument to TriangleMeshSlicer). + + // Slicing the model object. This method is oversimplified and needs to + // be compared with the fff slicing algorithm for verification + auto slice_model = [this, ilh](SLAPrintObject& po) { + double lh = po.m_config.layer_height.getFloat(); + + TriangleMesh mesh = po.transformed_mesh(); + TriangleMeshSlicer slicer(&mesh); + auto bb3d = mesh.bounding_box(); + + double elevation = po.get_elevation(); + + float minZ = float(bb3d.min(Z)) - float(elevation); + float maxZ = float(bb3d.max(Z)) ; + auto flh = float(lh); + auto gnd = float(bb3d.min(Z)); + + // The 1D grid heights + std::vector heights; + + // The first layer (the one before the initial height) is added only + // if there is no pad and no elevation value + if(minZ >= gnd) heights.emplace_back(minZ); + + for(float h = minZ + ilh; h < maxZ; h += flh) + if(h >= gnd) heights.emplace_back(h); + + auto& layers = po.m_model_slices; layers.clear(); + slicer.slice(heights, &layers, [this](){ throw_if_canceled(); }); + }; + + // this procedure simply converts the points and copies them into + // the support data cache + auto support_points = [](SLAPrintObject& po) { + ModelObject& mo = *po.m_model_object; + po.m_supportdata.reset(new SLAPrintObject::SupportData()); + + if(!mo.sla_support_points.empty()) { + po.m_supportdata->emesh = sla::to_eigenmesh(po.transformed_mesh()); + po.m_supportdata->support_points = + sla::to_point_set(po.transformed_support_points()); + } else if(po.m_config.supports_enable.getBool()) { + // Supports are enabled but there are no support points to process. + // We throw here a runtime exception with some explanation and + // the background processing framework will handle it. + throw std::runtime_error( + L("Supports are enabled but no support points selected." + " Hint: create some support points or disable support " + "creation.")); + } + }; + + // In this step we create the supports + auto support_tree = [this, objcount, ostepd](SLAPrintObject& po) { + if(!po.m_supportdata) return; + + if(!po.m_config.supports_enable.getBool()) { + // Generate empty support tree. It can still host a pad + po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); + return; + } + + try { + sla::SupportConfig scfg = make_support_cfg(po.m_config); + sla::Controller ctl; + + // some magic to scale the status values coming from the support + // tree creation into the whole print process + auto stfirst = OBJ_STEP_LEVELS.begin(); + auto stthis = stfirst + slaposSupportTree; + // we need to add up the status portions until this operation + int init = std::accumulate(stfirst, stthis, 0); + init = int(init * ostepd); // scale the init portion + + // scaling for the sub operations + double d = *stthis / (objcount * 100.0); + + ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) + { + report_status(*this, int(init + st*d), msg); + }; + + ctl.stopcondition = [this](){ return canceled(); }; + ctl.cancelfn = [this]() { throw_if_canceled(); }; + + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->support_points, + po.m_supportdata->emesh, scfg, ctl)); + + // Create the unified mesh + auto rc = SlicingStatus::RELOAD_SCENE; + report_status(*this, -1, L("Visualizing supports")); + po.m_supportdata->support_tree_ptr->merged_mesh(); + report_status(*this, -1, L("Visualizing supports"), rc); + } catch(sla::SLASupportsStoppedException&) { + // no need to rethrow + // throw_if_canceled(); + } + }; + + // This step generates the sla base pad + auto base_pool = [this](SLAPrintObject& po) { + // this step can only go after the support tree has been created + // and before the supports had been sliced. (or the slicing has to be + // repeated) + + if(po.m_config.pad_enable.getBool() && + po.m_supportdata && + po.m_supportdata->support_tree_ptr) + { + double wt = po.m_config.pad_wall_thickness.getFloat(); + double h = po.m_config.pad_wall_height.getFloat(); + double md = po.m_config.pad_max_merge_distance.getFloat(); + double er = po.m_config.pad_edge_radius.getFloat(); + double lh = po.m_config.layer_height.getFloat(); + double elevation = po.m_config.support_object_elevation.getFloat(); + if(!po.m_config.supports_enable.getBool()) elevation = 0; + sla::PoolConfig pcfg(wt, h, md, er); + + ExPolygons bp; + double pad_h = sla::get_pad_elevation(pcfg); + auto&& trmesh = po.transformed_mesh(); + + // This call can get pretty time consuming + auto thrfn = [this](){ throw_if_canceled(); }; + + if(elevation < pad_h) + sla::base_plate(trmesh, bp, float(pad_h), float(lh), + thrfn); + + pcfg.throw_on_cancel = thrfn; + po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); + } + + po.throw_if_canceled(); + auto rc = SlicingStatus::RELOAD_SCENE; + report_status(*this, -1, L("Visualizing supports"), rc); + }; + + // Slicing the support geometries similarly to the model slicing procedure. + // If the pad had been added previously (see step "base_pool" than it will + // be part of the slices) + auto slice_supports = [ilh](SLAPrintObject& po) { + auto& sd = po.m_supportdata; + if(sd && sd->support_tree_ptr) { + auto lh = float(po.m_config.layer_height.getFloat()); + sd->support_slices = sd->support_tree_ptr->slice(lh, ilh); + } + }; + + // We have the layer polygon collection but we need to unite them into + // an index where the key is the height level in discrete levels (clipper) + auto index_slices = [ilhd](SLAPrintObject& po) { + po.m_slice_index.clear(); + auto sih = LevelID(scale_(ilhd)); + + // Establish the slice grid boundaries + auto bb = po.transformed_mesh().bounding_box(); + double modelgnd = bb.min(Z); + double elevation = po.get_elevation(); + double lh = po.m_config.layer_height.getFloat(); + double minZ = modelgnd - elevation; + + // scaled values: + auto sminZ = LevelID(scale_(minZ)); + auto smaxZ = LevelID(scale_(bb.max(Z))); + auto smodelgnd = LevelID(scale_(modelgnd)); + auto slh = LevelID(scale_(lh)); + + // It is important that the next levels match the levels in + // model_slice method. Only difference is that here it works with + // scaled coordinates + po.m_level_ids.clear(); + if(sminZ >= smodelgnd) po.m_level_ids.emplace_back(sminZ); + for(LevelID h = sminZ + sih; h < smaxZ; h += slh) + if(h >= smodelgnd) po.m_level_ids.emplace_back(h); + + std::vector& oslices = po.m_model_slices; + + // If everything went well this code should not run at all, but + // let's be robust... + // assert(levelids.size() == oslices.size()); + if(po.m_level_ids.size() < oslices.size()) { // extend the levels until... + + BOOST_LOG_TRIVIAL(warning) + << "Height level mismatch at rasterization!\n"; + + LevelID lastlvl = po.m_level_ids.back(); + while(po.m_level_ids.size() < oslices.size()) { + lastlvl += slh; + po.m_level_ids.emplace_back(lastlvl); + } + } + + for(size_t i = 0; i < oslices.size(); ++i) { + LevelID h = po.m_level_ids[i]; + + float fh = float(double(h) * SCALING_FACTOR); + + // now for the public slice index: + SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; + // There should be only one slice layer for each print object + assert(sr.model_slices_idx == SLAPrintObject::SliceRecord::NONE); + sr.model_slices_idx = i; + } + + if(po.m_supportdata) { // deal with the support slices if present + std::vector& sslices = po.m_supportdata->support_slices; + po.m_supportdata->level_ids.clear(); + po.m_supportdata->level_ids.reserve(sslices.size()); + + for(int i = 0; i < int(sslices.size()); ++i) { + int a = i == 0 ? 0 : 1; + int b = i == 0 ? 0 : i - 1; + LevelID h = sminZ + a * sih + b * slh; + po.m_supportdata->level_ids.emplace_back(h); + + float fh = float(double(h) * SCALING_FACTOR); + + SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; + assert(sr.support_slices_idx == SLAPrintObject::SliceRecord::NONE); + sr.support_slices_idx = SLAPrintObject::SliceRecord::Idx(i); + } + } + }; + + // Rasterizing the model objects, and their supports + auto rasterize = [this, max_objstatus]() { + if(canceled()) return; + + // clear the rasterizer input + m_printer_input.clear(); + + for(SLAPrintObject * o : m_objects) { + auto& po = *o; + std::vector& oslices = po.m_model_slices; + + // We need to adjust the min Z level of the slices to be zero + LevelID smfirst = + po.m_supportdata && !po.m_supportdata->level_ids.empty() ? + po.m_supportdata->level_ids.front() : 0; + LevelID mfirst = po.m_level_ids.empty()? 0 : po.m_level_ids.front(); + LevelID gndlvl = -(std::min(smfirst, mfirst)); + + // now merge this object's support and object slices with the rest + // of the print object slices + + for(size_t i = 0; i < oslices.size(); ++i) { + auto& lyrs = m_printer_input[gndlvl + po.m_level_ids[i]]; + lyrs.emplace_back(oslices[i], po.m_instances); + } + + if(!po.m_supportdata) continue; + std::vector& sslices = po.m_supportdata->support_slices; + for(size_t i = 0; i < sslices.size(); ++i) { + LayerRefs& lyrs = + m_printer_input[gndlvl + po.m_supportdata->level_ids[i]]; + lyrs.emplace_back(sslices[i], po.m_instances); + } + } + + // collect all the keys + std::vector keys; keys.reserve(m_printer_input.size()); + for(auto& e : m_printer_input) keys.emplace_back(e.first); + + // If the raster has vertical orientation, we will flip the coordinates + bool flpXY = m_printer_config.display_orientation.getInt() == + SLADisplayOrientation::sladoPortrait; + + { // create a raster printer for the current print parameters + // I don't know any better + auto& ocfg = m_objects.front()->m_config; + auto& matcfg = m_material_config; + auto& printcfg = m_printer_config; + + double w = printcfg.display_width.getFloat(); + double h = printcfg.display_height.getFloat(); + auto pw = unsigned(printcfg.display_pixels_x.getInt()); + auto ph = unsigned(printcfg.display_pixels_y.getInt()); + double lh = ocfg.layer_height.getFloat(); + double exp_t = matcfg.exposure_time.getFloat(); + double iexp_t = matcfg.initial_exposure_time.getFloat(); + + if(flpXY) { std::swap(w, h); std::swap(pw, ph); } + + m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, + flpXY? SLAPrinter::RO_PORTRAIT : + SLAPrinter::RO_LANDSCAPE)); + } + + // Allocate space for all the layers + SLAPrinter& printer = *m_printer; + auto lvlcnt = unsigned(m_printer_input.size()); + printer.layers(lvlcnt); + + // slot is the portion of 100% that is realted to rasterization + unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; + // ist: initial state; pst: previous state + unsigned ist = max_objstatus, pst = ist; + // coefficient to map the rasterization state (0-99) to the allocated + // portion (slot) of the process state + double sd = (100 - ist) / 100.0; + SpinMutex slck; + + // procedure to process one height level. This will run in parallel + auto lvlfn = + [this, &slck, &keys, &printer, slot, sd, ist, &pst, flpXY] + (unsigned level_id) + { + if(canceled()) return; + + LayerRefs& lrange = m_printer_input[keys[level_id]]; + + // Switch to the appropriate layer in the printer + printer.begin_layer(level_id); + + for(auto& lyrref : lrange) { // for all layers in the current level + if(canceled()) break; + const Layer& sl = lyrref.lref; // get the layer reference + const LayerCopies& copies = lyrref.copies; + + // Draw all the polygons in the slice to the actual layer. + for(auto& cp : copies) { + for(ExPolygon slice : sl) { + // The order is important here: + // apply rotation before translation... + slice.rotate(double(cp.rotation)); + slice.translate(cp.shift(X), cp.shift(Y)); + if(flpXY) swapXY(slice); + printer.draw_polygon(slice, level_id); + } + } + } + + // Finish the layer for later saving it. + printer.finish_layer(level_id); + + // Status indication guarded with the spinlock + auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size()); + { std::lock_guard lck(slck); + if( st > pst) { + report_status(*this, int(st), PRINT_STEP_LABELS[slapsRasterize]); + pst = st; + } + } + }; + + // last minute escape + if(canceled()) return; + + // Sequential version (for testing) + // for(unsigned l = 0; l < lvlcnt; ++l) process_level(l); + + // Print all the layers in parallel + tbb::parallel_for(0, lvlcnt, lvlfn); + }; + + using slaposFn = std::function; + using slapsFn = std::function; + + // This is the actual order of steps done on each PrintObject + std::array objectsteps = { + slaposObjectSlice, // Support Islands will need this step + slaposSupportIslands, + slaposSupportPoints, + slaposSupportTree, + slaposBasePool, + slaposSliceSupports, + slaposIndexSlices + }; + + std::array pobj_program = + { + slice_model, + [](SLAPrintObject&){}, // slaposSupportIslands now empty + support_points, + support_tree, + base_pool, + slice_supports, + index_slices + }; + + std::array print_program = + { + rasterize, + [](){} // validate + }; + + unsigned st = min_objstatus; + unsigned incr = 0; + + BOOST_LOG_TRIVIAL(info) << "Start slicing process."; + + // TODO: this loop could run in parallel but should not exhaust all the CPU + // power available + for(SLAPrintObject * po : m_objects) { + + BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + + for(size_t s = 0; s < objectsteps.size(); ++s) { + auto currentstep = objectsteps[s]; + + // Cancellation checking. Each step will check for cancellation + // on its own and return earlier gracefully. Just after it returns + // execution gets to this point and throws the canceled signal. + throw_if_canceled(); + + st += unsigned(incr * ostepd); + + if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { + report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); + pobj_program[currentstep](*po); + po->set_done(currentstep); + } + + incr = OBJ_STEP_LEVELS[currentstep]; + } + } + + std::array printsteps = { + slapsRasterize, slapsValidate + }; + + // this would disable the rasterization step +// m_stepmask[slapsRasterize] = false; + + double pstd = (100 - max_objstatus) / 100.0; + st = max_objstatus; + for(size_t s = 0; s < print_program.size(); ++s) { + auto currentstep = printsteps[s]; + + throw_if_canceled(); + + if(m_stepmask[currentstep] && set_started(currentstep)) + { + report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]); + print_program[currentstep](); + set_done(currentstep); + } + + st += unsigned(PRINT_STEP_LEVELS[currentstep] * pstd); + } + + // If everything vent well + report_status(*this, 100, L("Slicing done")); +} + +bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys) +{ + if (opt_keys.empty()) + return false; + + // Cache the plenty of parameters, which influence the final rasterization only, + // or they are only notes not influencing the rasterization step. + static std::unordered_set steps_rasterize = { + "exposure_time", + "initial_exposure_time", + "material_correction_printing", + "material_correction_curing", + "display_width", + "display_height", + "display_pixels_x", + "display_pixels_y", + "display_orientation", + "printer_correction" + }; + + static std::unordered_set steps_ignore = { + "bed_shape", + "max_print_height", + "printer_technology", + "output_filename_format" + }; + + std::vector steps; + std::vector osteps; + bool invalidated = false; + + for (const t_config_option_key &opt_key : opt_keys) { + if (steps_rasterize.find(opt_key) != steps_rasterize.end()) { + // These options only affect the final rasterization, or they are just notes without influence on the output, + // so there is nothing to invalidate. + steps.emplace_back(slapsRasterize); + } else if (steps_ignore.find(opt_key) != steps_ignore.end()) { + // These steps have no influence on the output. Just ignore them. + } else if (opt_key == "initial_layer_height") { + steps.emplace_back(slapsRasterize); + osteps.emplace_back(slaposObjectSlice); + } else { + // All values should be covered. + assert(false); + } + } + + sort_remove_duplicates(steps); + for (SLAPrintStep step : steps) + invalidated |= this->invalidate_step(step); + sort_remove_duplicates(osteps); + for (SLAPrintObjectStep ostep : osteps) + for (SLAPrintObject *object : m_objects) + invalidated |= object->invalidate_step(ostep); + return invalidated; +} + +// Returns true if an object step is done on all objects and there's at least one object. +bool SLAPrint::is_step_done(SLAPrintObjectStep step) const +{ + if (m_objects.empty()) + return false; + tbb::mutex::scoped_lock lock(this->state_mutex()); + for (const SLAPrintObject *object : m_objects) + if (! object->m_state.is_done_unguarded(step)) + return false; + return true; +} + +SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): + Inherited(print, model_object), + m_stepmask(slaposCount, true), + m_transformed_rmesh( [this](TriangleMesh& obj){ + obj = m_model_object->raw_mesh(); obj.transform(m_trafo); + }) +{ +} + +SLAPrintObject::~SLAPrintObject() {} + +// Called by SLAPrint::apply_config(). +// This method only accepts SLAPrintObjectConfig option keys. +bool SLAPrintObject::invalidate_state_by_config_options(const std::vector &opt_keys) +{ + if (opt_keys.empty()) + return false; + + std::vector steps; + bool invalidated = false; + for (const t_config_option_key &opt_key : opt_keys) { + if (opt_key == "layer_height") { + steps.emplace_back(slaposObjectSlice); + } else if (opt_key == "supports_enable") { + steps.emplace_back(slaposSupportPoints); + } else if ( + opt_key == "support_head_front_diameter" + || opt_key == "support_head_penetration" + || opt_key == "support_head_width" + || opt_key == "support_pillar_diameter" + || opt_key == "support_base_diameter" + || opt_key == "support_base_height" + || opt_key == "support_critical_angle" + || opt_key == "support_max_bridge_length" + || opt_key == "support_object_elevation") { + steps.emplace_back(slaposSupportTree); + } else if ( + opt_key == "pad_enable" + || opt_key == "pad_wall_thickness" + || opt_key == "pad_wall_height" + || opt_key == "pad_max_merge_distance" + || opt_key == "pad_edge_radius") { + steps.emplace_back(slaposBasePool); + } else { + // All keys should be covered. + assert(false); + } + } + + sort_remove_duplicates(steps); + for (SLAPrintObjectStep step : steps) + invalidated |= this->invalidate_step(step); + return invalidated; +} + +bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) +{ + bool invalidated = Inherited::invalidate_step(step); + // propagate to dependent steps + if (step == slaposObjectSlice) { + invalidated |= this->invalidate_all_steps(); + } else if (step == slaposSupportIslands) { + invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposBasePool, slaposSliceSupports, slaposIndexSlices }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSupportPoints) { + invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports, slaposIndexSlices }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSupportTree) { + invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports, slaposIndexSlices }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposBasePool) { + invalidated |= this->invalidate_steps({slaposSliceSupports, slaposIndexSlices}); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSliceSupports) { + invalidated |= this->invalidate_step(slaposIndexSlices); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if(step == slaposIndexSlices) { + invalidated |= m_print->invalidate_step(slapsRasterize); + } + return invalidated; +} + +bool SLAPrintObject::invalidate_all_steps() +{ + return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); +} + +double SLAPrintObject::get_elevation() const { + bool se = m_config.supports_enable.getBool(); + double ret = se? m_config.support_object_elevation.getFloat() : 0; + + // if the pad is enabled, then half of the pad height is its base plate + if(m_config.pad_enable.getBool()) { + // Normally the elevation for the pad itself would be the thickness of + // its walls but currently it is half of its thickness. Whatever it + // will be in the future, we provide the config to the get_pad_elevation + // method and we will have the correct value + sla::PoolConfig pcfg; + pcfg.min_wall_height_mm = m_config.pad_wall_height.getFloat(); + pcfg.min_wall_thickness_mm = m_config.pad_wall_thickness.getFloat(); + pcfg.edge_radius_mm = m_config.pad_edge_radius.getFloat(); + pcfg.max_merge_distance_mm = m_config.pad_max_merge_distance.getFloat(); + ret += sla::get_pad_elevation(pcfg); + } + + return ret; +} + +double SLAPrintObject::get_current_elevation() const +{ + bool se = m_config.supports_enable.getBool(); + bool has_supports = is_step_done(slaposSupportTree); + bool has_pad = is_step_done(slaposBasePool); + if(!has_supports && !has_pad) return 0; + else if(has_supports && !has_pad) + return se ? m_config.support_object_elevation.getFloat() : 0; + else return get_elevation(); + + return 0; +} + +namespace { // dummy empty static containers for return values in some methods +const std::vector EMPTY_SLICES; +const TriangleMesh EMPTY_MESH; +} + +const std::vector &SLAPrintObject::get_support_slices() const +{ + // assert(is_step_done(slaposSliceSupports)); + if (!m_supportdata) return EMPTY_SLICES; + return m_supportdata->support_slices; +} + +const SLAPrintObject::SliceIndex &SLAPrintObject::get_slice_index() const +{ + // assert(is_step_done(slaposIndexSlices)); + return m_slice_index; +} + +const std::vector &SLAPrintObject::get_model_slices() const +{ + // assert(is_step_done(slaposObjectSlice)); + return m_model_slices; +} + +bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const +{ + switch (step) { + case slaposSupportTree: + return ! this->support_mesh().empty(); + case slaposBasePool: + return ! this->pad_mesh().empty(); + default: + return false; + } +} + +TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const +{ + switch (step) { + case slaposSupportTree: + return this->support_mesh(); + case slaposBasePool: + return this->pad_mesh(); + default: + return TriangleMesh(); + } +} + +const TriangleMesh& SLAPrintObject::support_mesh() const +{ + if(m_config.supports_enable.getBool() && m_supportdata && + m_supportdata->support_tree_ptr) { + return m_supportdata->support_tree_ptr->merged_mesh(); + } + + return EMPTY_MESH; +} + +const TriangleMesh& SLAPrintObject::pad_mesh() const +{ + if(m_config.pad_enable.getBool() && m_supportdata && m_supportdata->support_tree_ptr) + return m_supportdata->support_tree_ptr->get_pad(); + + return EMPTY_MESH; +} + +const TriangleMesh &SLAPrintObject::transformed_mesh() const { + // we need to transform the raw mesh... + // currently all the instances share the same x and y rotation and scaling + // so we have to extract those from e.g. the first instance and apply to the + // raw mesh. This is also true for the support points. + // BUT: when the support structure is spawned for each instance than it has + // to omit the X, Y rotation and scaling as those have been already applied + // or apply an inverse transformation on the support structure after it + // has been created. + + return m_transformed_rmesh.get(); +} + +std::vector SLAPrintObject::transformed_support_points() const +{ + assert(m_model_object != nullptr); + auto& spts = m_model_object->sla_support_points; + + // this could be cached as well + std::vector ret; ret.reserve(spts.size()); + + for(auto& sp : spts) ret.emplace_back( trafo() * Vec3d(sp.cast())); + + return ret; +} + +} // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp new file mode 100644 index 000000000..4a0876767 --- /dev/null +++ b/src/libslic3r/SLAPrint.hpp @@ -0,0 +1,242 @@ +#ifndef slic3r_SLAPrint_hpp_ +#define slic3r_SLAPrint_hpp_ + +#include + +#include "PrintBase.hpp" +#include "PrintExport.hpp" +#include "Point.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +enum SLAPrintStep : unsigned int { + slapsRasterize, + slapsValidate, + slapsCount +}; + +enum SLAPrintObjectStep : unsigned int { + slaposObjectSlice, + slaposSupportIslands, + slaposSupportPoints, + slaposSupportTree, + slaposBasePool, + slaposSliceSupports, + slaposIndexSlices, + slaposCount +}; + +class SLAPrint; +class GLCanvas; + +using _SLAPrintObjectBase = + PrintObjectBaseWithState; + +// Layers according to quantized height levels. This will be consumed by +// the printer (rasterizer) in the SLAPrint class. +using LevelID = long long; + +class SLAPrintObject : public _SLAPrintObjectBase +{ +private: // Prevents erroneous use by other classes. + using Inherited = _SLAPrintObjectBase; + +public: + const SLAPrintObjectConfig& config() const { return m_config; } + const Transform3d& trafo() const { return m_trafo; } + + struct Instance { + Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } + // ID of the corresponding ModelInstance. + ModelID instance_id; + // Slic3r::Point objects in scaled G-code coordinates + Point shift; + // Rotation along the Z axis, in radians. + float rotation; + }; + const std::vector& instances() const { return m_instances; } + + bool has_mesh(SLAPrintObjectStep step) const; + TriangleMesh get_mesh(SLAPrintObjectStep step) const; + + // Get a support mesh centered around origin in XY, and with zero rotation around Z applied. + // Support mesh is only valid if this->is_step_done(slaposSupportTree) is true. + const TriangleMesh& support_mesh() const; + // Get a pad mesh centered around origin in XY, and with zero rotation around Z applied. + // Support mesh is only valid if this->is_step_done(slaposBasePool) is true. + const TriangleMesh& pad_mesh() const; + + // This will return the transformed mesh which is cached + const TriangleMesh& transformed_mesh() const; + + std::vector transformed_support_points() const; + + // Get the needed Z elevation for the model geometry if supports should be + // displayed. This Z offset should also be applied to the support + // geometries. Note that this is not the same as the value stored in config + // as the pad height also needs to be considered. + double get_elevation() const; + + // This method returns the needed elevation according to the processing + // status. If the supports are not ready, it is zero, if they are and the + // pad is not, then without the pad, otherwise the full value is returned. + double get_current_elevation() const; + + // These two methods should be callable on the client side (e.g. UI thread) + // when the appropriate steps slaposObjectSlice and slaposSliceSupports + // are ready. All the print objects are processed before slapsRasterize so + // it is safe to call them during and/or after slapsRasterize. + const std::vector& get_model_slices() const; + const std::vector& get_support_slices() const; + + // An index record referencing the slices + // (get_model_slices(), get_support_slices()) where the keys are the height + // levels of the model in scaled-clipper coordinates. The levels correspond + // to the z coordinate of the object coordinate system. + struct SliceRecord { + using Key = float; + + using Idx = size_t; + static const Idx NONE = Idx(-1); // this will be the max limit of size_t + + Idx model_slices_idx = NONE; + Idx support_slices_idx = NONE; + }; + + using SliceIndex = std::map; + + // Retrieve the slice index which is readable only after slaposIndexSlices + // is done. + const SliceIndex& get_slice_index() const; + + // I refuse to grantee copying (Tamas) + SLAPrintObject(const SLAPrintObject&) = delete; + SLAPrintObject& operator=(const SLAPrintObject&) = delete; + +protected: + // to be called from SLAPrint only. + friend class SLAPrint; + + SLAPrintObject(SLAPrint* print, ModelObject* model_object); + ~SLAPrintObject(); + + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) + { this->m_config.apply_only(other, keys, ignore_nonexistent); } + + void set_trafo(const Transform3d& trafo) { + m_transformed_rmesh.invalidate([this, &trafo](){ m_trafo = trafo; }); + } + + void set_instances(const std::vector &instances) { m_instances = instances; } + // Invalidates the step, and its depending steps in SLAPrintObject and SLAPrint. + bool invalidate_step(SLAPrintObjectStep step); + bool invalidate_all_steps(); + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); + +private: + // Object specific configuration, pulled from the configuration layer. + SLAPrintObjectConfig m_config; + // Translation in Z + Rotation by Y and Z + Scaling / Mirroring. + Transform3d m_trafo = Transform3d::Identity(); + std::vector m_instances; + + // Which steps have to be performed. Implicitly: all + std::vector m_stepmask; + std::vector m_model_slices; + SliceIndex m_slice_index; + std::vector m_level_ids; + + // Caching the transformed (m_trafo) raw mesh of the object + mutable CachedObject m_transformed_rmesh; + + class SupportData; + std::unique_ptr m_supportdata; +}; + +using PrintObjects = std::vector; + +class TriangleMesh; + +/** + * @brief This class is the high level FSM for the SLA printing process. + * + * It should support the background processing framework and contain the + * metadata for the support geometries and their slicing. It should also + * dispatch the SLA printing configuration values to the appropriate calculation + * steps. + */ +class SLAPrint : public PrintBaseWithState +{ +private: // Prevents erroneous use by other classes. + typedef PrintBaseWithState Inherited; + +public: + SLAPrint(): m_stepmask(slapsCount, true) {} + + virtual ~SLAPrint() override { this->clear(); } + + PrinterTechnology technology() const noexcept override { return ptSLA; } + + void clear() override; + bool empty() const override { return m_objects.empty(); } + ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + void process() override; + // Returns true if an object step is done on all objects and there's at least one object. + bool is_step_done(SLAPrintObjectStep step) const; + // Returns true if the last step was finished with success. + bool finished() const override { return this->is_step_done(slaposIndexSlices); } + + template void export_raster(const std::string& fname) { + if(m_printer) m_printer->save(fname); + } + const PrintObjects& objects() const { return m_objects; } + + std::string output_filename() const override + { return this->PrintBase::output_filename(m_print_config.output_filename_format.value, "zip"); } + +private: + using SLAPrinter = FilePrinter; + using SLAPrinterPtr = std::unique_ptr; + + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); + + SLAPrintConfig m_print_config; + SLAPrinterConfig m_printer_config; + SLAMaterialConfig m_material_config; + SLAPrintObjectConfig m_default_object_config; + + PrintObjects m_objects; + std::vector m_stepmask; + + // Definition of the print input map. It consists of the slices indexed + // with scaled (clipper) Z coordinates. Also contains the instance + // transformations in scaled and filtered version. This is enough for the + // rasterizer to be able to draw every layer in the right position + using Layer = ExPolygons; + using LayerCopies = std::vector; + struct LayerRef { + std::reference_wrapper lref; + std::reference_wrapper copies; + LayerRef(const Layer& lyr, const LayerCopies& cp) : + lref(std::cref(lyr)), copies(std::cref(cp)) {} + }; + + // One level may contain multiple slices from multiple objects and their + // supports + using LayerRefs = std::vector; + std::map m_printer_input; + + // The printer itself + SLAPrinterPtr m_printer; + + friend SLAPrintObject; +}; + +} // namespace Slic3r + +#endif /* slic3r_SLAPrint_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 21650c089..60fb0f71a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -495,7 +495,7 @@ public: m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); // assert(! m_grid.has_intersecting_edges()); - printf("SupportGridPattern: fixing polygons with intersection %s\n", + printf("SupportGridPattern: fixing polygons with intersection %s\n", m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); } #endif @@ -513,9 +513,12 @@ public: { // Generate islands, so each island may be tested for overlap with m_island_samples. assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); - ExPolygons islands = diff_ex( - m_grid.contours_simplified(offset_in_grid, fill_holes), - *m_trimming_polygons, false); +#ifdef SLIC3R_DEBUG + Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); + ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); +#else + ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); +#endif // Extract polygons, which contain some of the m_island_samples. Polygons out; @@ -586,7 +589,7 @@ public: } #ifdef SLIC3R_DEBUG - void serialize(const std::string &path) + void serialize(const std::string &path) { FILE *file = ::fopen(path.c_str(), "wb"); ::fwrite(&m_support_spacing, 8, 1, file); @@ -618,7 +621,7 @@ public: ::fclose(file); } - static SupportGridPattern deserialize(const std::string &path, int which = -1) + static SupportGridPattern deserialize(const std::string &path, int which = -1) { SupportGridPattern out; out.deserialize_(path, which); @@ -920,17 +923,40 @@ namespace SupportMaterialInternal { if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - contact_polygons = diff(contact_polygons, bridges, true); - // Add the bridge anchors into the region. + // Remove the unsupported ends of the bridges from the bridged areas. //FIXME add supports at regular intervals to support long bridges! - polygons_append(contact_polygons, - intersection( + bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, true); } } +#if 0 +static int Test() +{ +// for (int i = 0; i < 30; ++ i) + { + int i = -1; +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); + auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); + std::vector> intersections = grid.grid().intersecting_edges(); + if (! intersections.empty()) + printf("Intersections between contours!\n"); + Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); + Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); + Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); + Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); + Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); + printf("hu!"); + } + return 0; +} +static int run_support_test = Test(); +#endif /* SLIC3R_DEBUG */ + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -1253,9 +1279,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); -// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (! dense_interface_polygons.empty()) { - //FIXME do it for the bridges only? + dense_interface_polygons = + // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise + // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin_cached); SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. dense_interface_polygons, @@ -1265,8 +1296,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); - } - } + #ifdef SLIC3R_DEBUG + { + support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); BoundingBox bbox = get_extents(contact_polygons); bbox.merge(get_extents(new_layer.polygons)); @@ -2606,7 +2638,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // Transform loops into ExtrusionPath objects. extrusion_entities_append_paths( top_contact_layer.extrusions.entities, - STDMOVE(loop_lines), + std::move(loop_lines), erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); } @@ -2776,7 +2808,10 @@ void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {} + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { + return *this; + } + const std::vector &m_path_fragments; }; const coord_t search_radius = 7; @@ -2983,7 +3018,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing())); extrusion_entities_append_paths( support_layer.support_fills.entities, - to_polylines(STDMOVE(to_infill_polygons)), + to_polylines(std::move(to_infill_polygons)), erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); } if (! to_infill.empty()) { @@ -2997,7 +3032,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Destination support_layer.support_fills.entities, // Regions to fill - STDMOVE(to_infill), + std::move(to_infill), // Filler and its parameters filler, float(support_density), // Extrusion parameters @@ -3216,14 +3251,14 @@ void PrintObjectSupportMaterial::generate_toolpaths( to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions.entities, - to_polylines(STDMOVE(to_infill_polygons)), + to_polylines(std::move(to_infill_polygons)), erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); } fill_expolygons_generate_paths( // Destination base_layer.extrusions.entities, // Regions to fill - STDMOVE(to_infill), + std::move(to_infill), // Filler and its parameters filler, density, // Extrusion parameters diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 5c4f8617d..d604896ad 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -1,11 +1,45 @@ #ifndef _technologies_h_ #define _technologies_h_ +//============ +// debug techs +//============ + +// Shows camera target in the 3D scene +#define ENABLE_SHOW_CAMERA_TARGET 0 +// Log debug messages to console when changing selection +#define ENABLE_SELECTION_DEBUG_OUTPUT 0 + +//============= // 1.42.0 techs +//============= #define ENABLE_1_42_0 1 -// Add z coordinate to model instances' offset -#define ENABLE_MODELINSTANCE_3D_OFFSET (1 && ENABLE_1_42_0) +// Uses a unique opengl context +#define ENABLE_USE_UNIQUE_GLCONTEXT (1 && ENABLE_1_42_0) +// Disable synchronization of unselected instances +#define DISABLE_INSTANCES_SYNCH (0 && ENABLE_1_42_0) +// Modified camera target behavior +#define ENABLE_MODIFIED_CAMERA_TARGET (1 && ENABLE_1_42_0) +// Add Geometry::Transformation class and use it into ModelInstance, ModelVolume and GLVolume +#define ENABLE_MODELVOLUME_TRANSFORM (1 && ENABLE_1_42_0) +// Keeps objects on bed while scaling them using the scale gizmo +#define ENABLE_ENSURE_ON_BED_WHILE_SCALING (1 && ENABLE_MODELVOLUME_TRANSFORM) +// All rotations made using the rotate gizmo are done with respect to the world reference system +#define ENABLE_WORLD_ROTATIONS (1 && ENABLE_1_42_0) +// Scene's GUI made using imgui library +#define ENABLE_IMGUI (1 && ENABLE_1_42_0) +#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI) +// Modified Sla support gizmo +#define ENABLE_SLA_SUPPORT_GIZMO_MOD (1 && ENABLE_1_42_0) +// Removes the wxNotebook from plater +#define ENABLE_REMOVE_TABS_FROM_PLATER (1 && ENABLE_1_42_0) +// Constrains the camera target into the scene bounding box +#define ENABLE_CONSTRAINED_CAMERA_TARGET (1 && ENABLE_1_42_0) +// Use wxDataViewRender instead of wxDataViewCustomRenderer +#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0) +// Adds background texture to toolbars +#define ENABLE_TOOLBAR_BACKGROUND_TEXTURE (1 && ENABLE_1_42_0) #endif // _technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 1f7c8167c..507d6f813 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,7 +1,6 @@ #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "MultiPoint.hpp" #include "qhull/src/libqhullcpp/Qhull.h" #include "qhull/src/libqhullcpp/QhullFacetList.h" #include "qhull/src/libqhullcpp/QhullVertexSet.h" @@ -109,21 +108,23 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact - stl_check_facets_exact(&stl); + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; + stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby //int last_edges_fixed = 0; - float tolerance = stl.stats.shortest_edge; + float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); - stl_check_facets_nearby(&stl, tolerance); + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; + stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; @@ -135,29 +136,31 @@ void TriangleMesh::repair() // remove_unconnected if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; stl_remove_unconnected_facets(&stl); } // fill_holes -#if 0 - // Don't fill holes, the current algorithm does more harm than good on complex holes. - // Rather let the slicing algorithm close gaps in 2D slices. if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + BOOST_LOG_TRIVIAL(trace) << "\tstl_fill_holes"; stl_fill_holes(&stl); stl_clear_error(&stl); } -#endif // normal_directions + BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; stl_fix_normal_directions(&stl); // normal_values + BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; stl_fix_normal_values(&stl); // always calculate the volume and reverse all normals if volume is negative + BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; stl_calculate_volume(&stl); // neighbors + BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; stl_verify_neighbors(&stl); this->repaired = true; @@ -269,9 +272,9 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) if (angle == 0.f) return; - Vec3f axis_norm = axis.cast().normalized(); - Transform3f m = Transform3f::Identity(); - m.rotate(Eigen::AngleAxisf(angle, axis_norm)); + Vec3d axis_norm = axis.normalized(); + Transform3d m = Transform3d::Identity(); + m.rotate(Eigen::AngleAxisd(angle, axis_norm)); stl_transform(&stl, m); } @@ -287,7 +290,7 @@ void TriangleMesh::mirror(const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::transform(const Transform3f& t) +void TriangleMesh::transform(const Transform3d& t) { stl_transform(&stl, t); } @@ -518,6 +521,7 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& t) const if (stl.stats.shared_vertices > 0) { + assert(stl.v_shared != nullptr); stl_vertex* vertex_ptr = stl.v_shared; for (int i = 0; i < stl.stats.shared_vertices; ++i) { @@ -855,12 +859,12 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z + max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z); // first layer, whose slice_z is > max_z #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); + printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()) - 1); #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { + for (std::vector::const_iterator it = min_layer; it != max_layer; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { @@ -1193,6 +1197,7 @@ static inline void remove_tangent_edges(std::vector &lines) if (l1.edge_type == l2.edge_type) { l1.set_skip(); break; + } } else { assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); // If this edge joins two horizontal facets, remove both of them. @@ -1207,24 +1212,134 @@ static inline void remove_tangent_edges(std::vector &lines) } } -void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const + +struct OpenPolyline { + OpenPolyline() {}; + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : + start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + void reverse() { + std::swap(start, end); + std::reverse(points.begin(), points.end()); + } + IntersectionReference start; + IntersectionReference end; + Points points; + double length; + bool consumed; +}; + +// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// Only connects segments crossing triangles of the same orientation. +static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) { -#if 0 -//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. -//#ifdef _DEBUG - for (const Line &l : lines) - assert(l.a != l.b); -#endif /* _DEBUG */ + // Build a map of lines by edge_a_id and a_id. + std::vector by_edge_a_id; + std::vector by_a_id; + by_edge_a_id.reserve(lines.size()); + by_a_id.reserve(lines.size()); + for (IntersectionLine &line : lines) { + if (! line.skip()) { + if (line.edge_a_id != -1) + by_edge_a_id.emplace_back(&line); + if (line.a_id != -1) + by_a_id.emplace_back(&line); + } + } + auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; + auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; + std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); + std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); + // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. + IntersectionLines::iterator it_line_seed = lines.begin(); + for (;;) { + // take first spare line and start a new loop + IntersectionLine *first_line = nullptr; + for (; it_line_seed != lines.end(); ++ it_line_seed) + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { + first_line = &(*it_line_seed ++); + break; + } + if (first_line == nullptr) + break; + first_line->set_skip(); + Points loop_pts; + loop_pts.emplace_back(first_line->a); + IntersectionLine *last_line = first_line; + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + IntersectionLine key; + for (;;) { + // find a line starting where last one finishes + IntersectionLine* next_line = nullptr; + if (last_line->edge_b_id != -1) { + key.edge_a_id = last_line->edge_b_id; + auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); + if (it_begin != by_edge_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr && last_line->b_id != -1) { + key.a_id = last_line->b_id; + auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); + if (it_begin != by_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr) { + // Check whether we closed this loop. + if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || + (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { + // The current loop is complete. Add it to the output. + loops.emplace_back(std::move(loop_pts)); + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + } else { + // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. + loop_pts.emplace_back(last_line->b); + open_polylines.emplace_back(OpenPolyline( + IntersectionReference(first_line->a_id, first_line->edge_a_id), + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + } + break; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop_pts.emplace_back(next_line->a); + last_line = next_line; + next_line->set_skip(); + } + } +} - remove_tangent_edges(lines); - - struct OpenPolyline { - OpenPolyline() {}; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) {} - void reverse() { - std::swap(start, end); - std::reverse(points.begin(), points.end()); +std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) +{ + std::vector out; + out.reserve(open_polylines.size()); + for (OpenPolyline &opl : open_polylines) + if (! opl.consumed) { + if (update_lengths) + opl.length = Slic3r::length(opl.points); + out.emplace_back(&opl); } std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); return out; @@ -1246,103 +1361,82 @@ static void chain_open_polylines_exact(std::vector &open_polylines int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } }; - std::vector open_polylines; - { - // Build a map of lines by edge_a_id and a_id. - std::vector by_edge_a_id; - std::vector by_a_id; - by_edge_a_id.reserve(lines.size()); - by_a_id.reserve(lines.size()); - for (IntersectionLine &line : lines) { - if (! line.skip()) { - if (line.edge_a_id != -1) - by_edge_a_id.emplace_back(&line); - if (line.a_id != -1) - by_a_id.emplace_back(&line); - } - } - auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; - auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; - std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); - std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); - // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. - IntersectionLines::iterator it_line_seed = lines.begin(); + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); + for (OpenPolyline &opl : open_polylines) { + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); + } + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; + // Try to connect the loops. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + opl->consumed = true; + OpenPolylineEnd end(opl, false); for (;;) { - // take first spare line and start a new loop - IntersectionLine *first_line = nullptr; - for (; it_line_seed != lines.end(); ++ it_line_seed) - if (it_line_seed->is_seed_candidate()) { - //if (! it_line_seed->skip()) { - first_line = &(*it_line_seed ++); - break; + // find a line starting where last one finishes + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + break; + found: + // Attach this polyline to the end of the initial polyline. + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + } + opl->length += it_next_start->polyline->length; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->length = 0.; + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } + // Check whether we closed this loop. + if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || + (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { + // The current loop is complete. Add it to the output. + //assert(opl->points.front().point_id == opl->points.back().point_id); + //assert(opl->points.front().edge_id == opl->points.back().edge_id); + // Remove the duplicate last point. + opl->points.pop_back(); + if (opl->points.size() >= 3) { + if (try_connect_reversed && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); } opl->points.clear(); break; - first_line->set_skip(); - Points loop_pts; - loop_pts.emplace_back(first_line->a); - IntersectionLine *last_line = first_line; - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - IntersectionLine key; - for (;;) { - // find a line starting where last one finishes - IntersectionLine* next_line = nullptr; - if (last_line->edge_b_id != -1) { - key.edge_a_id = last_line->edge_b_id; - auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); - if (it_begin != by_edge_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr && last_line->b_id != -1) { - key.a_id = last_line->b_id; - auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); - if (it_begin != by_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr) { - // Check whether we closed this loop. - if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || - (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { - // The current loop is complete. Add it to the output. - loops->emplace_back(std::move(loop_pts)); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - } else { - // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. - loop_pts.emplace_back(last_line->b); - open_polylines.emplace_back(OpenPolyline( - IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); - } - break; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop_pts.emplace_back(next_line->a); - last_line = next_line; - next_line->set_skip(); } // Continue with the current loop. } @@ -1356,79 +1450,73 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly { const coord_t max_gap_scaled = (coord_t)scale_(max_gap); - // Try to connect the loops. - for (OpenPolyline &opl : open_polylines) { - if (opl.consumed) - continue; - opl.consumed = true; - OpenPolylineEnd end(&opl, false); - for (;;) { - // find a line starting where last one finishes - OpenPolylineEnd* next_start = nullptr; - if (end.edge_id() != -1) { - auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower); - if (it_begin != by_edge_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower); - for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge) - if (! it_edge->polyline->consumed) { - next_start = &(*it_edge); - break; - } - } - } - if (next_start == nullptr && end.point_id() != -1) { - auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower); - if (it_begin != by_point_id.end()) { - auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower); - for (auto it_point = it_begin; it_point != it_end; ++ it_point) - if (! it_point->polyline->consumed) { - next_start = &(*it_point); - break; - } - } - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - break; - } - // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); - //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end()); + // Sort the open polylines by their length, so the new loops will be seeded from longer chains. + // Update the polyline lengths, return only not yet consumed polylines. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); + + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline *opl : sorted_by_length) { + closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + } + // Try to connect the loops. + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + OpenPolylineEnd end(opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl->consumed = true; + size_t n_segments_joined = 1; + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + // Check whether we closed this loop. + double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl->consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl->points.pop_back(); } else { // The end points are different, keep both of them. } - end = *next_start; - end.start = !end.start; - next_start->polyline->points.clear(); - next_start->polyline->consumed = true; - // Check whether we closed this loop. - const IntersectionReference &ip1 = opl.start; - const IntersectionReference &ip2 = end.ipref(); - if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || - (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { - // The current loop is complete. Add it to the output. - //assert(opl.points.front().point_id == opl.points.back().point_id); - //assert(opl.points.front().edge_id == opl.points.back().edge_id); - // Remove the duplicate last point. - opl.points.pop_back(); - if (opl.points.size() >= 3) { + if (opl->points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) // The closed polygon is patched from pieces with messed up orientation, therefore // the orientation of the patched up polygon is not known. // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - double area = 0.; - for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) - area += double(opl.points[j](0) + opl.points[i](0)) * double(opl.points[i](1) - opl.points[j](1)); - if (area < 0) - std::reverse(opl.points.begin(), opl.points.end()); - loops->emplace_back(std::move(opl.points)); - } - opl.points.clear(); - break; + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); } - // Continue with the current loop. + opl->points.clear(); + opl->consumed = true; + break; } if (next_start == nullptr) { // The current loop could not be closed. Unmark the segment. @@ -1512,19 +1600,19 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo chain_open_polylines_exact(open_polylines, *loops, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) { + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { if (pl.points.empty()) continue; - svg.draw(Polyline(pl.points), "red"); + svg.draw(Polyline(pl.points), "red"); svg.draw(pl.points.front(), "blue"); svg.draw(pl.points.back(), "blue"); } - svg.Close(); - } + svg.Close(); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Try to close gaps. @@ -1535,10 +1623,10 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); for (const OpenPolyline &pl : open_polylines) { if (pl.points.empty()) continue; @@ -1546,8 +1634,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo svg.draw(pl.points.front(), "blue"); svg.draw(pl.points.back(), "blue"); } - svg.Close(); - } + svg.Close(); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } @@ -1683,10 +1771,11 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic // p_slices = diff(p_slices, *loop); //} - // perform a safety offset to merge very close facets (TODO: find test case for this) - //double safety_offset = scale_(0.0499); // now a config value -//FIXME see https://github.com/prusa3d/Slic3r/issues/520 -// double safety_offset = scale_(0.0001); + // Perform a safety offset to merge very close facets (TODO: find test case for this) + // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 +// double safety_offset = scale_(0.0499); + // 0.0001 is set to satisfy GH #520, #1029, #1364 +// double safety_offset = scale_(0.0001); // now a config value /* The following line is commented out because it can generate wrong polygons, see for example issue #661 */ diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 5ed47286a..b6194b596 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -35,7 +35,7 @@ public: void repair(); float volume(); void check_topology(); - bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; } + bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } void WriteOBJFile(char* output_file); void scale(float factor); void scale(const Vec3d &versor); @@ -49,7 +49,7 @@ public: void mirror_x() { this->mirror(X); } void mirror_y() { this->mirror(Y); } void mirror_z() { this->mirror(Z); } - void transform(const Transform3f& t); + void transform(const Transform3d& t); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; @@ -65,6 +65,7 @@ public: void reset_repair_stats(); bool needed_repair() const; size_t facets_count() const { return this->stl.stats.number_of_facets; } + bool empty() const { return this->facets_count() == 0; } // Returns true, if there are two and more connected patches in the mesh. // Returns false, if one or zero connected patch is in the mesh. @@ -173,7 +174,7 @@ public: const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; - double safety_offset = scale_(0.0499); + double safety_offset = scale_(0.02); private: const TriangleMesh *mesh; // Map from a facet to an edge index. diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index c90ad7650..7f2c94f03 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -2,6 +2,9 @@ #define slic3r_Utils_hpp_ #include +#include +#include +#include #include "libslic3r.h" @@ -65,6 +68,8 @@ namespace PerlUtils { extern std::string path_to_parent_path(const char *src); }; +std::string string_printf(const char *format, ...); + // Timestamp formatted for header_slic3r_generated(). extern std::string timestamp_str(); // Standard "generated by Slic3r version xxx timestamp xxx" header string, @@ -74,27 +79,110 @@ inline std::string header_slic3r_generated() { return std::string("generated by // getpid platform wrapper extern unsigned get_current_pid(); +template +Real round_nearest(Real value, unsigned int decimals) +{ + Real res = (Real)0; + if (decimals == 0) + res = ::round(value); + else + { + Real power = ::pow((Real)10, (int)decimals); + res = ::round(value * power + (Real)0.5) / power; + } + return res; +} + // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html -template -inline T next_highest_power_of_2(T v) +inline uint16_t next_highest_power_of_2(uint16_t v) { - if (v != 0) - -- v; + if (v != 0) + -- v; v |= v >> 1; v |= v >> 2; v |= v >> 4; - if (sizeof(T) >= sizeof(uint16_t)) - v |= v >> 8; - if (sizeof(T) >= sizeof(uint32_t)) - v |= v >> 16; - if (sizeof(T) >= sizeof(uint64_t)) - v |= v >> 32; + v |= v >> 8; + return ++ v; +} +inline uint32_t next_highest_power_of_2(uint32_t v) +{ + if (v != 0) + -- v; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ++ v; +} +inline uint64_t next_highest_power_of_2(uint64_t v) +{ + if (v != 0) + -- v; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; return ++ v; } +// On some implementations (such as some versions of clang), the size_t is a type of its own, so we need to overload for size_t. +// Typically, though, the size_t type aliases to uint64_t / uint32_t. +// We distinguish that here and provide implementation for size_t if and only if it is a distinct type +template size_t next_highest_power_of_2(T v, + typename std::enable_if::value, T>::type = 0, // T is size_t + typename std::enable_if::value, T>::type = 0, // T is not uint64_t + typename std::enable_if::value, T>::type = 0, // T is not uint32_t + typename std::enable_if::type = 0) // T is 64 bits +{ + return next_highest_power_of_2(uint64_t(v)); +} +template size_t next_highest_power_of_2(T v, + typename std::enable_if::value, T>::type = 0, // T is size_t + typename std::enable_if::value, T>::type = 0, // T is not uint64_t + typename std::enable_if::value, T>::type = 0, // T is not uint32_t + typename std::enable_if::type = 0) // T is 32 bits +{ + return next_highest_power_of_2(uint32_t(v)); +} + + extern std::string xml_escape(std::string text); + +class ScopeGuard +{ +public: + typedef std::function Closure; +private: + bool committed; + Closure closure; + +public: + ScopeGuard() {} + ScopeGuard(Closure closure) : closure(std::move(closure)) {} + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard(ScopeGuard &&other) : closure(std::move(other.closure)) {} + + ~ScopeGuard() + { + if (closure) { closure(); } + } + + ScopeGuard& operator=(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard &&other) + { + closure = std::move(other.closure); + return *this; + } + + void reset() { closure = Closure(); } +}; + + } // namespace Slic3r #endif // slic3r_Utils_hpp_ diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2cb7656c6..6a4cc140a 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -1,6 +1,8 @@ #ifndef _libslic3r_h_ #define _libslic3r_h_ +#include "libslic3r_version.h" + // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include #include @@ -15,10 +17,6 @@ #include "Technologies.hpp" -#define SLIC3R_FORK_NAME "Slic3r++" -#define SLIC3R_VERSION "1.41.2-beta" -#define SLIC3R_BUILD "UNKNOWN" - typedef int32_t coord_t; typedef double coordf_t; @@ -46,19 +44,6 @@ typedef double coordf_t; #define scale_(val) ((val) / SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) -// Which C++ version is supported? -// For example, could optimized functions with move semantics be used? -#if __cplusplus==201402L - #define SLIC3R_CPPVER 14 - #define STDMOVE(WHAT) std::move(WHAT) -#elif __cplusplus==201103L - #define SLIC3R_CPPVER 11 - #define STDMOVE(WHAT) std::move(WHAT) -#else - #define SLIC3R_CPPVER 0 - #define STDMOVE(WHAT) (WHAT) -#endif - #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" inline std::string debug_out_path(const char *name, ...) @@ -95,6 +80,8 @@ namespace Slic3r { template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } +inline double unscaled(double v) { return v * SCALING_FACTOR; } + enum Axis { X=0, Y, Z, E, F, NUM_AXES }; template diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in new file mode 100644 index 000000000..2259b2302 --- /dev/null +++ b/src/libslic3r/libslic3r_version.h.in @@ -0,0 +1,8 @@ +#ifndef __SLIC3R_VERSION_H +#define __SLIC3R_VERSION_H + +#define SLIC3R_FORK_NAME "@SLIC3R_FORK_NAME@" +#define SLIC3R_VERSION "@SLIC3R_VERSION@" +#define SLIC3R_BUILD "@SLIC3R_BUILD@" + +#endif /* __SLIC3R_VERSION_H */ diff --git a/src/libslic3r/pchheader.cpp b/src/libslic3r/pchheader.cpp new file mode 100644 index 000000000..9ab59c53d --- /dev/null +++ b/src/libslic3r/pchheader.cpp @@ -0,0 +1 @@ +#include "pchheader.hpp" diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp new file mode 100644 index 000000000..b27dfe6a2 --- /dev/null +++ b/src/libslic3r/pchheader.hpp @@ -0,0 +1,122 @@ +#ifdef WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif +#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 +#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 +#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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "BoundingBox.hpp" +#include "ClipperUtils.hpp" +#include "Config.hpp" +#include "I18N.hpp" +#include "MultiPoint.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" +#include "SVG.hpp" + +#include "libslic3r.h" +#include "libslic3r_version.h" + +#include "clipper.hpp" + +#include + +#include diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 4ff15175b..7a51a6104 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #ifdef WIN32 #include @@ -27,6 +29,8 @@ #include +#include + namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; @@ -295,6 +299,25 @@ namespace PerlUtils { std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); } }; + +std::string string_printf(const char *format, ...) +{ + va_list args1; + va_start(args1, format); + va_list args2; + va_copy(args2, args1); + + size_t needed_size = ::vsnprintf(nullptr, 0, format, args1) + 1; + va_end(args1); + + std::string res(needed_size, '\0'); + ::vsnprintf(&res.front(), res.size(), format, args2); + va_end(args2); + + return res; +} + + std::string timestamp_str() { const auto now = boost::posix_time::second_clock::local_time(); diff --git a/src/platform/msw/slic3r.manifest.in b/src/platform/msw/slic3r.manifest.in new file mode 100644 index 000000000..ab1cc5ae2 --- /dev/null +++ b/src/platform/msw/slic3r.manifest.in @@ -0,0 +1,37 @@ + + + + Perl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/platform/msw/slic3r.rc.in b/src/platform/msw/slic3r.rc.in new file mode 100644 index 000000000..493f9f752 --- /dev/null +++ b/src/platform/msw/slic3r.rc.in @@ -0,0 +1,25 @@ +1 VERSIONINFO +FILEVERSION @SLIC3R_RC_VERSION@ +PRODUCTVERSION @SLIC3R_RC_VERSION@ +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Prusa Research" + VALUE "FileDescription", "Slic3r Prusa Edition" + VALUE "FileVersion", "@SLIC3R_BUILD_ID@" + VALUE "ProductName", "Slic3r Prusa Edition" + VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" + VALUE "InternalName", "Slic3r Prusa Edition" + VALUE "LegalCopyright", "Copyright \251 2011-2017 Alessandro Ranelucci, \251 2016 Prusa Research" + VALUE "OriginalFilename", "slic3r.exe" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x409, 1252 + } +} +2 ICON "@SLIC3R_RESOURCES_DIR@/icons/Slic3r.ico" +1 24 "slic3r.manifest" diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in new file mode 100644 index 000000000..099cb5c37 --- /dev/null +++ b/src/platform/osx/Info.plist.in @@ -0,0 +1,32 @@ + + + + + CFBundleExecutable + Slic3r + CFBundleGetInfoString + Slic3r Copyright (C) 2011-2017 Alessandro Ranellucci, (C) 2016-2018 Prusa Reseach + CFBundleIconFile + Slic3r.icns + CFBundleName + Slic3r + CFBundleShortVersionString + Slic3r @SLIC3R_BUILD_ID@ + CFBundleIdentifier + com.prusa3d.slic3r/ + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + @SLIC3R_BUILD_ID@ + CGDisableCoalescedUpdates + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + + + diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index d798b018f..e25da35e3 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -80,11 +80,9 @@ set(libqhull_SOURCES src/libqhull_r/mem_r.c src/libqhull_r/random_r.c src/libqhull_r/usermem_r.c - src/libqhull_r/userprintf_r.c src/libqhull_r/io_r.c src/libqhull_r/user_r.c src/libqhull_r/rboxlib_r.c - src/libqhull_r/userprintf_rbox_r.c # C++ interface to reentrant Qhull SOURCES: src/libqhullcpp/Coordinates.cpp diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 8174ba0a2..59b23c133 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -1,8 +1,17 @@ -#include "Config.hpp" -#include "Geometry.hpp" -#include "Model.hpp" -#include "TriangleMesh.hpp" -#include "libslic3r.h" +#ifdef WIN32 + // Why? + #define _WIN32_WINNT 0x0502 + // The standard Windows includes. + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include + #include + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +#endif /* WIN32 */ + #include #include #include @@ -10,114 +19,97 @@ #include #include #include +#include #include +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "libslic3r/Utils.hpp" + #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" using namespace Slic3r; -// wxWidgets "Hello world" Program -// For compilers that support precompilation, includes "wx/wx.h". -#include -#ifndef WX_PRECOMP - #include +/// utility function for displaying CLI usage +void printUsage(); + +#ifdef _MSC_VER +int slic3r_main_(int argc, char **argv) +#else +int main(int argc, char **argv) #endif -class MyApp: public wxApp { -public: - virtual bool OnInit(); -}; -class MyFrame: public wxFrame -{ -public: - MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); -private: - void OnHello(wxCommandEvent& event); - void OnExit(wxCommandEvent& event); - void OnAbout(wxCommandEvent& event); - wxDECLARE_EVENT_TABLE(); -}; -enum -{ - ID_Hello = 1 -}; -wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) - EVT_MENU(ID_Hello, MyFrame::OnHello) - EVT_MENU(wxID_EXIT, MyFrame::OnExit) - EVT_MENU(wxID_ABOUT, MyFrame::OnAbout) -wxEND_EVENT_TABLE() -bool MyApp::OnInit() -{ - MyFrame *frame = new MyFrame( "Hello World", wxPoint(50, 50), wxSize(450, 340) ); - frame->Show( true ); - return true; -} -MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) - : wxFrame(NULL, wxID_ANY, title, pos, size) -{ - wxMenu *menuFile = new wxMenu; - menuFile->Append(ID_Hello, "&Hello...\tCtrl-H", - "Help string shown in status bar for this menu item"); - menuFile->AppendSeparator(); - menuFile->Append(wxID_EXIT); - wxMenu *menuHelp = new wxMenu; - menuHelp->Append(wxID_ABOUT); - wxMenuBar *menuBar = new wxMenuBar; - menuBar->Append( menuFile, "&File" ); - menuBar->Append( menuHelp, "&Help" ); - SetMenuBar( menuBar ); - CreateStatusBar(); - SetStatusText( "Welcome to wxWidgets!" ); - Slic3r::Model model; - ModelObject *object = model.add_object(); - SetStatusText(Slic3r::GUI::from_u8("HHuhuh")); -} + { + const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); + if (loglevel != nullptr) { + if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) + set_logging_level(loglevel[0] - '0'); + else + boost::nowide::cerr << "Invalid SLIC3R_LOGLEVEL environment variable: " << loglevel << std::endl; + } + } -void MyFrame::OnExit(wxCommandEvent& event) -{ - Close( true ); -} -void MyFrame::OnAbout(wxCommandEvent& event) -{ - wxMessageBox( "This is a wxWidgets' Hello world sample", - "About Hello World", wxOK | wxICON_INFORMATION ); -} -void MyFrame::OnHello(wxCommandEvent& event) -{ - wxLogMessage("Hello world from wxWidgets!"); -} - - -#if 1 -int -main(int argc, char **argv) -{ - // Convert arguments to UTF-8 (needed on Windows). - // argv then points to memory owned by a. - boost::nowide::args a(argc, argv); - -#if 0 // parse all command line options into a DynamicConfig - ConfigDef config_def; - config_def.merge(cli_config_def); - config_def.merge(print_config_def); - DynamicConfig config(&config_def); + DynamicPrintAndCLIConfig config; t_config_option_keys input_files; - config.read_cli(argc, argv, &input_files); - + // if any option is unsupported, print usage and abort immediately + if (! config.read_cli(argc, argv, &input_files)) { + printUsage(); + return 0; + } + + boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); + + // Path from the Slic3r binary to its resources. +#ifdef __APPLE__ + // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r' + // The resources are packed to 'Slic3r.app/Contents/Resources' + boost::filesystem::path path_resources = path_to_binary.parent_path() / "../Resources"; +#elif defined _WIN32 + // The application is packed in the .zip archive in the root, + // The resources are packed to 'resources' + // Path from Slic3r binary to resources: + boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources"; +#else + // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r', + // The resources are packed to 'resources' + // Path from Slic3r binary to resources: + boost::filesystem::path path_resources = path_to_binary.parent_path() / "../resources"; +#endif + + set_resources_dir(path_resources.string()); + set_var_dir((path_resources / "icons").string()); + set_local_dir((path_resources / "localization").string()); + // apply command line options to a more handy CLIConfig CLIConfig cli_config; cli_config.apply(config, true); - + set_data_dir(cli_config.datadir.value); + DynamicPrintConfig print_config; - + + if ((argc == 1 || cli_config.gui.value) && ! cli_config.no_gui.value && ! cli_config.help.value && cli_config.save.value.empty()) { +#if 1 + GUI::GUI_App *gui = new GUI::GUI_App(); + GUI::GUI_App::SetInstance(gui); + wxEntry(argc, argv); +#else + std::cout << "GUI support has not been built." << "\n"; +#endif + } // load config files supplied via --load for (const std::string &file : cli_config.load.values) { - if (!boost::filesystem::exists(file)) { + if (! boost::filesystem::exists(file)) { boost::nowide::cout << "No such file: " << file << std::endl; exit(1); } - DynamicPrintConfig c; try { c.load(file); @@ -132,130 +124,157 @@ main(int argc, char **argv) // apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) print_config.apply(config, true); - print_config.normalize(); // write config if requested - if (!cli_config.save.value.empty()) print_config.save(cli_config.save.value); - + if (! cli_config.save.value.empty()) { + print_config.normalize(); + print_config.save(cli_config.save.value); + } + + if (cli_config.help) { + printUsage(); + return 0; + } + // read input file(s) if any std::vector models; for (const t_config_option_key &file : input_files) { - if (!boost::filesystem::exists(file)) { + if (! boost::filesystem::exists(file)) { boost::nowide::cerr << "No such file: " << file << std::endl; exit(1); } - Model model; try { - model = Model::read_from_file(file); + model = Model::read_from_file(file, &print_config, true); } catch (std::exception &e) { boost::nowide::cerr << file << ": " << e.what() << std::endl; exit(1); } - if (model.objects.empty()) { boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } - - model.add_default_instances(); - + model.add_default_instances(); // apply command line transform options for (ModelObject* o : model.objects) { +/* if (cli_config.scale_to_fit.is_positive_volume()) o->scale_to_fit(cli_config.scale_to_fit.value); - +*/ // TODO: honor option order? o->scale(cli_config.scale.value); o->rotate(Geometry::deg2rad(cli_config.rotate_x.value), X); o->rotate(Geometry::deg2rad(cli_config.rotate_y.value), Y); o->rotate(Geometry::deg2rad(cli_config.rotate.value), Z); } - // TODO: handle --merge models.push_back(model); } - + for (Model &model : models) { if (cli_config.info) { // --info works on unrepaired model model.print_info(); - } else if (cli_config.export_obj) { + } else if (cli_config.export_3mf) { std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file + ".obj"; - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - IO::OBJ::write(mesh, outfile); - boost::nowide::cout << "File exported to " << outfile << std::endl; - } else if (cli_config.export_pov) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file + ".pov"; - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - IO::POV::write(mesh, outfile); - boost::nowide::cout << "File exported to " << outfile << std::endl; - } else if (cli_config.export_svg) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file + ".svg"; - - SLAPrint print(&model); - print.config.apply(print_config, true); - print.slice(); - print.write_svg(outfile); - boost::nowide::cout << "SVG file exported to " << outfile << std::endl; - } else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) { + if (outfile.empty()) outfile = model.objects.front()->input_file; + // Check if the file is already a 3mf. + if(outfile.substr(outfile.find_last_of('.'), outfile.length()) == ".3mf") + outfile = outfile.substr(0, outfile.find_last_of('.')) + "_2" + ".3mf"; + else + // Remove the previous extension and add .3mf extention. + outfile = outfile.substr(0, outfile.find_last_of('.')) + ".3mf"; + store_3mf(outfile.c_str(), &model, nullptr); + boost::nowide::cout << "File file exported to " << outfile << std::endl; + } else if (cli_config.cut > 0) { model.repair(); - model.translate(0, 0, -model.bounding_box().min.z); - - if (!model.objects.empty()) { - // FIXME: cut all objects - Model out; - if (cli_config.cut_x > 0) { - model.objects.front()->cut(X, cli_config.cut_x, &out); - } else if (cli_config.cut_y > 0) { - model.objects.front()->cut(Y, cli_config.cut_y, &out); + model.translate(0, 0, - model.bounding_box().min(2)); + if (! model.objects.empty()) { + // XXX + // Model out; + // model.objects.front()->cut(cli_config.cut, &out); + // ModelObject &upper = *out.objects[0]; + // ModelObject &lower = *out.objects[1]; + // // Use the input name and trim off the extension. + // std::string outfile = cli_config.output.value; + // if (outfile.empty()) + // outfile = model.objects.front()->input_file; + // outfile = outfile.substr(0, outfile.find_last_of('.')); + // std::cerr << outfile << "\n"; + // if (upper.facets_count() > 0) + // upper.mesh().write_binary((outfile + "_upper.stl").c_str()); + // if (lower.facets_count() > 0) + // lower.mesh().write_binary((outfile + "_lower.stl").c_str()); + } + } else if (cli_config.slice) { + PrinterTechnology printer_technology = print_config.option>("printer_technology", true)->value; + std::string outfile = cli_config.output.value; + Print fff_print; + SLAPrint sla_print; + PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (! cli_config.dont_arrange) { + //FIXME make the min_object_distance configurable. + model.arrange_objects(fff_print.config().min_object_distance()); + model.center_instances_around_point(cli_config.print_center); + } + if (outfile.empty()) { + outfile = model.propose_export_file_name(); + outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip"; + } + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print_config.normalize(); + print->apply(model, print_config); + std::string err = print->validate(); + if (err.empty()) { + if (printer_technology == ptFFF) { + fff_print.export_gcode(outfile, nullptr); } else { - model.objects.front()->cut(Z, cli_config.cut, &out); + assert(printer_technology == ptSLA); + //FIXME add the output here } - - ModelObject &upper = *out.objects[0]; - ModelObject &lower = *out.objects[1]; - - if (upper.facets_count() > 0) { - TriangleMesh m = upper.mesh(); - IO::STL::write(m, upper.input_file + "_upper.stl"); - } - if (lower.facets_count() > 0) { - TriangleMesh m = lower.mesh(); - IO::STL::write(m, lower.input_file + "_lower.stl"); - } - } - } else if (cli_config.cut_grid.value.x > 0 && cli_config.cut_grid.value.y > 0) { - TriangleMesh mesh = model.mesh(); - mesh.repair(); - - TriangleMeshPtrs meshes = mesh.cut_by_grid(cli_config.cut_grid.value); - size_t i = 0; - for (TriangleMesh* m : meshes) { - std::ostringstream ss; - ss << model.objects.front()->input_file << "_" << i++ << ".stl"; - IO::STL::write(*m, ss.str()); - delete m; - } + } else + std::cerr << err << "\n"; } else { boost::nowide::cerr << "error: command not supported" << std::endl; return 1; } } -#endif - - MyApp *gui = new MyApp(); - - MyApp::SetInstance(gui); - wxEntry(argc, argv); return 0; } -#endif + +void printUsage() +{ + std::cout << "Slic3r " << SLIC3R_VERSION << " is a STL-to-GCODE translator for RepRap 3D printers" << "\n" + << "written by Alessandro Ranellucci - http://slic3r.org/ - https://github.com/slic3r/Slic3r" << "\n" +// << "Git Version " << BUILD_COMMIT << "\n\n" + << "Usage: ./slic3r [ OPTIONS ] [ file.stl ] [ file2.stl ] ..." << "\n"; + // CLI Options + std::cout << "** CLI OPTIONS **\n"; + print_cli_options(boost::nowide::cout); + std::cout << "****\n"; + // Print options + std::cout << "** PRINT OPTIONS **\n"; + print_print_options(boost::nowide::cout); + std::cout << "****\n"; +} + +#ifdef _MSC_VER +extern "C" { + __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) + { + // Convert wchar_t arguments to UTF8. + std::vector argv_narrow; + std::vector argv_ptrs(argc + 1, nullptr); + for (size_t i = 0; i < argc; ++ i) + argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); + for (size_t i = 0; i < argc; ++ i) + argv_ptrs[i] = const_cast(argv_narrow[i].data()); + // Call the UTF8 main. + return slic3r_main_(argc, argv_ptrs.data()); + } +} +#endif /* _MSC_VER */ diff --git a/src/slic3r/AppController.cpp b/src/slic3r/AppController.cpp deleted file mode 100644 index 4a36b5d7f..000000000 --- a/src/slic3r/AppController.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "AppController.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace Slic3r { - -class AppControllerBoilerplate::PriData { -public: - std::mutex m; - std::thread::id ui_thread; - - inline explicit PriData(std::thread::id uit): ui_thread(uit) {} -}; - -AppControllerBoilerplate::AppControllerBoilerplate() - :pri_data_(new PriData(std::this_thread::get_id())) {} - -AppControllerBoilerplate::~AppControllerBoilerplate() { - pri_data_.reset(); -} - -bool AppControllerBoilerplate::is_main_thread() const -{ - return pri_data_->ui_thread == std::this_thread::get_id(); -} - -namespace GUI { -PresetBundle* get_preset_bundle(); -} - -AppControllerBoilerplate::ProgresIndicatorPtr -AppControllerBoilerplate::global_progress_indicator() { - ProgresIndicatorPtr ret; - - pri_data_->m.lock(); - ret = global_progressind_; - pri_data_->m.unlock(); - - return ret; -} - -void AppControllerBoilerplate::global_progress_indicator( - AppControllerBoilerplate::ProgresIndicatorPtr gpri) -{ - pri_data_->m.lock(); - global_progressind_ = gpri; - pri_data_->m.unlock(); -} - -void ProgressIndicator::message_fmt( - const std::string &fmtstr, ...) { - std::stringstream ss; - va_list args; - va_start(args, fmtstr); - - auto fmt = fmtstr.begin(); - - while (*fmt != '\0') { - if (*fmt == 'd') { - int i = va_arg(args, int); - ss << i << '\n'; - } else if (*fmt == 'c') { - // note automatic conversion to integral type - int c = va_arg(args, int); - ss << static_cast(c) << '\n'; - } else if (*fmt == 'f') { - double d = va_arg(args, double); - ss << d << '\n'; - } - ++fmt; - } - - va_end(args); - message(ss.str()); -} - -void AppController::arrange_model() -{ - using Coord = libnest2d::TCoord; - - if(arranging_.load()) return; - - // to prevent UI reentrancies - arranging_.store(true); - - unsigned count = 0; - for(auto obj : model_->objects) count += obj->instances.size(); - - auto pind = global_progress_indicator(); - - float pmax = 1.0; - - if(pind) { - pmax = pind->max(); - - // Set the range of the progress to the object count - pind->max(count); - - pind->on_cancel([this](){ - arranging_.store(false); - }); - } - - auto dist = print_ctl()->config().min_object_distance(); - - // Create the arranger config - auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - - auto& bedpoints = print_ctl()->config().bed_shape.values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) - bed.append(Point::new_scale(v(0), v(1))); - - if(pind) pind->update(0, L("Arranging objects...")); - - try { - arr::BedShapeHint hint; - // TODO: from Sasha from GUI - hint.type = arr::BedShapeType::WHO_KNOWS; - - arr::arrange(*model_, - min_obj_distance, - bed, - hint, - false, // create many piles not just one pile - [this, pind, count](unsigned rem) { - if(pind) - pind->update(count - rem, L("Arranging objects...")); - - process_events(); - }, [this] () { return !arranging_.load(); }); - } catch(std::exception& e) { - std::cerr << e.what() << std::endl; - report_issue(IssueType::ERR, - L("Could not arrange model objects! " - "Some geometries may be invalid."), - L("Exception occurred")); - } - - // Restore previous max value - if(pind) { - pind->max(pmax); - pind->update(0, arranging_.load() ? L("Arranging done.") : - L("Arranging canceled.")); - - pind->on_cancel(/*remove cancel function*/); - } - - arranging_.store(false); -} - -} diff --git a/src/slic3r/AppController.hpp b/src/slic3r/AppController.hpp deleted file mode 100644 index 71472835e..000000000 --- a/src/slic3r/AppController.hpp +++ /dev/null @@ -1,263 +0,0 @@ -#ifndef APPCONTROLLER_HPP -#define APPCONTROLLER_HPP - -#include -#include -#include -#include -#include - -#include "GUI/ProgressIndicator.hpp" - -#include - -namespace Slic3r { - -class Model; -class Print; -class PrintObject; -class PrintConfig; -class ProgressStatusBar; -class DynamicPrintConfig; - -/** - * @brief A boilerplate class for creating application logic. It should provide - * features as issue reporting and progress indication, etc... - * - * The lower lever UI independent classes can be manipulated with a subclass - * of this controller class. We can also catch any exceptions that lower level - * methods could throw and display appropriate errors and warnings. - * - * Note that the outer and the inner interface of this class is free from any - * UI toolkit dependencies. We can implement it with any UI framework or make it - * a cli client. - */ -class AppControllerBoilerplate { -public: - - /// A Progress indicator object smart pointer - using ProgresIndicatorPtr = std::shared_ptr; - -private: - class PriData; // Some structure to store progress indication data - - // Pimpl data for thread safe progress indication features - std::unique_ptr pri_data_; - -public: - - AppControllerBoilerplate(); - ~AppControllerBoilerplate(); - - using Path = std::string; - using PathList = std::vector; - - /// Common runtime issue types - enum class IssueType { - INFO, - WARN, - WARN_Q, // Warning with a question to continue - ERR, - FATAL - }; - - /** - * @brief Query some paths from the user. - * - * It should display a file chooser dialog in case of a UI application. - * @param title Title of a possible query dialog. - * @param extensions Recognized file extensions. - * @return Returns a list of paths choosed by the user. - */ - PathList query_destination_paths( - const std::string& title, - const std::string& extensions) const; - - /** - * @brief Same as query_destination_paths but works for directories only. - */ - PathList query_destination_dirs( - const std::string& title) const; - - /** - * @brief Same as query_destination_paths but returns only one path. - */ - Path query_destination_path( - const std::string& title, - const std::string& extensions, - const std::string& hint = "") const; - - /** - * @brief Report an issue to the user be it fatal or recoverable. - * - * In a UI app this should display some message dialog. - * - * @param issuetype The type of the runtime issue. - * @param description A somewhat longer description of the issue. - * @param brief A very brief description. Can be used for message dialog - * title. - */ - bool report_issue(IssueType issuetype, - const std::string& description, - const std::string& brief); - - bool report_issue(IssueType issuetype, - const std::string& description); - - /** - * @brief Return the global progress indicator for the current controller. - * Can be empty as well. - * - * Only one thread should use the global indicator at a time. - */ - ProgresIndicatorPtr global_progress_indicator(); - - void global_progress_indicator(ProgresIndicatorPtr gpri); - - /** - * @brief A predicate telling the caller whether it is the thread that - * created the AppConroller object itself. This probably means that the - * execution is in the UI thread. Otherwise it returns false meaning that - * some worker thread called this function. - * @return Return true for the same caller thread that created this - * object and false for every other. - */ - bool is_main_thread() const; - - /** - * @brief The frontend supports asynch execution. - * - * A Graphic UI will support this, a CLI may not. This can be used in - * subclass methods to decide whether to start threads for block free UI. - * - * Note that even a progress indicator's update called regularly can solve - * the blocking UI problem in some cases even when an event loop is present. - * This is how wxWidgets gauge work but creating a separate thread will make - * the UI even more fluent. - * - * @return true if a job or method can be executed asynchronously, false - * otherwise. - */ - bool supports_asynch() const; - - void process_events(); - -protected: - - /** - * @brief Create a new progress indicator and return a smart pointer to it. - * @param statenum The number of states for the given procedure. - * @param title The title of the procedure. - * @param firstmsg The message for the first subtask to be displayed. - * @return Smart pointer to the created object. - */ - ProgresIndicatorPtr create_progress_indicator( - unsigned statenum, - const std::string& title, - const std::string& firstmsg) const; - - ProgresIndicatorPtr create_progress_indicator( - unsigned statenum, - const std::string& title) const; - - // This is a global progress indicator placeholder. In the Slic3r UI it can - // contain the progress indicator on the statusbar. - ProgresIndicatorPtr global_progressind_; -}; - -#if 0 -/** - * @brief Implementation of the printing logic. - */ -class PrintController: public AppControllerBoilerplate { - Print *print_ = nullptr; -public: - - // Must be public for perl to use it - explicit inline PrintController(Print *print): print_(print) {} - - PrintController(const PrintController&) = delete; - PrintController(PrintController&&) = delete; - - using Ptr = std::unique_ptr; - - inline static Ptr create(Print *print) { - return PrintController::Ptr( new PrintController(print) ); - } - - void slice() {} - void slice_to_png() {} - - const PrintConfig& config() const; -}; -#else -class PrintController: public AppControllerBoilerplate { -public: - using Ptr = std::unique_ptr; - explicit inline PrintController(Print *print){} - inline static Ptr create(Print *print) { - return PrintController::Ptr( new PrintController(print) ); - } - void slice() {} - void slice_to_png() {} - const PrintConfig& config() const { static PrintConfig cfg; return cfg; } -}; -#endif - -/** - * @brief Top level controller. - */ -class AppController: public AppControllerBoilerplate { - Model *model_ = nullptr; - PrintController::Ptr printctl; - std::atomic arranging_; -public: - - /** - * @brief Get the print controller object. - * - * @return Return a raw pointer instead of a smart one for perl to be able - * to use this function and access the print controller. - */ - PrintController * print_ctl() { return printctl.get(); } - - /** - * @brief Set a model object. - * - * @param model A raw pointer to the model object. This can be used from - * perl. - */ - void set_model(Model *model) { model_ = model; } - - /** - * @brief Set the print object from perl. - * - * This will create a print controller that will then be accessible from - * perl. - * @param print A print object which can be a perl-ish extension as well. - */ - void set_print(Print *print) { - printctl = PrintController::create(print); - } - - /** - * @brief Set up a global progress indicator. - * - * In perl we have a progress indicating status bar on the bottom of the - * window which is defined and created in perl. We can pass the ID-s of the - * gauge and the statusbar id and make a wrapper implementation of the - * ProgressIndicator interface so we can use this GUI widget from C++. - * - * This function should be called from perl. - * - * @param gauge_id The ID of the gague widget of the status bar. - * @param statusbar_id The ID of the status bar. - */ - void set_global_progress_indicator(ProgressStatusBar *prs); - - void arrange_model(); -}; - -} - -#endif // APPCONTROLLER_HPP diff --git a/src/slic3r/AppControllerWx.cpp b/src/slic3r/AppControllerWx.cpp deleted file mode 100644 index 4d67d5f66..000000000 --- a/src/slic3r/AppControllerWx.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "AppController.hpp" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -// This source file implements the UI dependent methods of the AppControllers. -// It will be clear what is needed to be reimplemented in case of a UI framework -// change or a CLI client creation. In this particular case we use wxWidgets to -// implement everything. - -namespace Slic3r { - -bool AppControllerBoilerplate::supports_asynch() const -{ - return true; -} - -void AppControllerBoilerplate::process_events() -{ - wxYieldIfNeeded(); -} - -AppControllerBoilerplate::PathList -AppControllerBoilerplate::query_destination_paths( - const std::string &title, - const std::string &extensions) const -{ - - wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); - dlg.SetWildcard(extensions); - - dlg.ShowModal(); - - wxArrayString paths; - dlg.GetPaths(paths); - - PathList ret(paths.size(), ""); - for(auto& p : paths) ret.push_back(p.ToStdString()); - - return ret; -} - -AppControllerBoilerplate::Path -AppControllerBoilerplate::query_destination_path( - const std::string &title, - const std::string &extensions, - const std::string& hint) const -{ - wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); - dlg.SetWildcard(extensions); - - dlg.SetFilename(hint); - - Path ret; - - if(dlg.ShowModal() == wxID_OK) { - ret = Path(dlg.GetPath()); - } - - return ret; -} - -bool AppControllerBoilerplate::report_issue(IssueType issuetype, - const std::string &description, - const std::string &brief) -{ - auto icon = wxICON_INFORMATION; - auto style = wxOK|wxCENTRE; - switch(issuetype) { - case IssueType::INFO: break; - case IssueType::WARN: icon = wxICON_WARNING; break; - case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break; - case IssueType::ERR: - case IssueType::FATAL: icon = wxICON_ERROR; - } - - auto ret = wxMessageBox(_(description), _(brief), icon | style); - return ret != wxCANCEL; -} - -bool AppControllerBoilerplate::report_issue( - AppControllerBoilerplate::IssueType issuetype, - const std::string &description) -{ - return report_issue(issuetype, description, std::string()); -} - -wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); - -namespace { - -/* - * A simple thread safe progress dialog implementation that can be used from - * the main thread as well. - */ -class GuiProgressIndicator: - public ProgressIndicator, public wxEvtHandler { - - wxProgressDialog gauge_; - using Base = ProgressIndicator; - wxString message_; - int range_; wxString title_; - bool is_asynch_ = false; - - const int id_ = wxWindow::NewControlId(); - - // status update handler - void _state( wxCommandEvent& evt) { - unsigned st = evt.GetInt(); - message_ = evt.GetString(); - _state(st); - } - - // Status update implementation - void _state( unsigned st) { - if(!gauge_.IsShown()) gauge_.ShowModal(); - Base::state(st); - if(!gauge_.Update(static_cast(st), message_)) { - cancel(); - } - } - -public: - - /// Setting whether it will be used from the UI thread or some worker thread - inline void asynch(bool is) { is_asynch_ = is; } - - /// Get the mode of parallel operation. - inline bool asynch() const { return is_asynch_; } - - inline GuiProgressIndicator(int range, const wxString& title, - const wxString& firstmsg) : - gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), - wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT), - - message_(firstmsg), - range_(range), title_(title) - { - Base::max(static_cast(range)); - Base::states(static_cast(range)); - - Bind(PROGRESS_STATUS_UPDATE_EVENT, - &GuiProgressIndicator::_state, - this, id_); - } - - virtual void state(float val) override { - state(static_cast(val)); - } - - void state(unsigned st) { - // send status update event - if(is_asynch_) { - auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_); - evt->SetInt(st); - evt->SetString(message_); - wxQueueEvent(this, evt); - } else _state(st); - } - - virtual void message(const std::string & msg) override { - message_ = _(msg); - } - - virtual void messageFmt(const std::string& fmt, ...) { - va_list arglist; - va_start(arglist, fmt); - message_ = wxString::Format(_(fmt), arglist); - va_end(arglist); - } - - virtual void title(const std::string & title) override { - title_ = _(title); - } -}; -} - -AppControllerBoilerplate::ProgresIndicatorPtr -AppControllerBoilerplate::create_progress_indicator( - unsigned statenum, - const std::string& title, - const std::string& firstmsg) const -{ - auto pri = - std::make_shared(statenum, title, firstmsg); - - // We set up the mode of operation depending of the creator thread's - // identity - pri->asynch(!is_main_thread()); - - return pri; -} - -AppControllerBoilerplate::ProgresIndicatorPtr -AppControllerBoilerplate::create_progress_indicator( - unsigned statenum, const std::string &title) const -{ - return create_progress_indicator(statenum, title, std::string()); -} - -namespace { - -class Wrapper: public ProgressIndicator, public wxEvtHandler { - ProgressStatusBar *sbar_; - using Base = ProgressIndicator; - wxString message_; - AppControllerBoilerplate& ctl_; - - void showProgress(bool show = true) { - sbar_->show_progress(show); - } - - void _state(unsigned st) { - if( st <= ProgressIndicator::max() ) { - Base::state(st); - sbar_->set_status_text(message_); - sbar_->set_progress(st); - } - } - - // status update handler - void _state( wxCommandEvent& evt) { - unsigned st = evt.GetInt(); _state(st); - } - - const int id_ = wxWindow::NewControlId(); - -public: - - inline Wrapper(ProgressStatusBar *sbar, - AppControllerBoilerplate& ctl): - sbar_(sbar), ctl_(ctl) - { - Base::max(static_cast(sbar_->get_range())); - Base::states(static_cast(sbar_->get_range())); - - Bind(PROGRESS_STATUS_UPDATE_EVENT, - &Wrapper::_state, - this, id_); - } - - virtual void state(float val) override { - state(unsigned(val)); - } - - virtual void max(float val) override { - if(val > 1.0) { - sbar_->set_range(static_cast(val)); - ProgressIndicator::max(val); - } - } - - void state(unsigned st) { - if(!ctl_.is_main_thread()) { - auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_); - evt->SetInt(st); - wxQueueEvent(this, evt); - } else { - _state(st); - } - } - - virtual void message(const std::string & msg) override { - message_ = _(msg); - } - - virtual void message_fmt(const std::string& fmt, ...) override { - va_list arglist; - va_start(arglist, fmt); - message_ = wxString::Format(_(fmt), arglist); - va_end(arglist); - } - - virtual void title(const std::string & /*title*/) override {} - - virtual void on_cancel(CancelFn fn) override { - sbar_->set_cancel_callback(fn); - Base::on_cancel(fn); - } - -}; -} - -void AppController::set_global_progress_indicator(ProgressStatusBar *prsb) -{ - if(prsb) { - global_progress_indicator(std::make_shared(prsb, *this)); - } -} - -} diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e4dfd35ea..3b6bad16d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -1,106 +1,131 @@ +project(libslic3r_gui) +cmake_minimum_required(VERSION 2.6) + +include(PrecompiledHeader) + add_library(libslic3r_gui STATIC - ${LIBDIR}/slic3r/GUI/AboutDialog.cpp - ${LIBDIR}/slic3r/GUI/AboutDialog.hpp - ${LIBDIR}/slic3r/GUI/AppConfig.cpp - ${LIBDIR}/slic3r/GUI/AppConfig.hpp - ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp - ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp - ${LIBDIR}/slic3r/GUI/BitmapCache.cpp - ${LIBDIR}/slic3r/GUI/BitmapCache.hpp - ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp - ${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp - ${LIBDIR}/slic3r/GUI/3DScene.cpp - ${LIBDIR}/slic3r/GUI/3DScene.hpp - ${LIBDIR}/slic3r/GUI/GLShader.cpp - ${LIBDIR}/slic3r/GUI/GLShader.hpp - ${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp - ${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp - ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp - ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp - ${LIBDIR}/slic3r/GUI/GLGizmo.hpp - ${LIBDIR}/slic3r/GUI/GLGizmo.cpp - ${LIBDIR}/slic3r/GUI/GLTexture.hpp - ${LIBDIR}/slic3r/GUI/GLTexture.cpp - ${LIBDIR}/slic3r/GUI/GLToolbar.hpp - ${LIBDIR}/slic3r/GUI/GLToolbar.cpp - ${LIBDIR}/slic3r/GUI/Preferences.cpp - ${LIBDIR}/slic3r/GUI/Preferences.hpp - ${LIBDIR}/slic3r/GUI/Preset.cpp - ${LIBDIR}/slic3r/GUI/Preset.hpp - ${LIBDIR}/slic3r/GUI/PresetBundle.cpp - ${LIBDIR}/slic3r/GUI/PresetBundle.hpp - ${LIBDIR}/slic3r/GUI/PresetHints.cpp - ${LIBDIR}/slic3r/GUI/PresetHints.hpp - ${LIBDIR}/slic3r/GUI/GUI.cpp - ${LIBDIR}/slic3r/GUI/GUI.hpp - ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp - ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp - ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp - ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp - ${LIBDIR}/slic3r/GUI/Tab.cpp - ${LIBDIR}/slic3r/GUI/Tab.hpp - ${LIBDIR}/slic3r/GUI/TabIface.cpp - ${LIBDIR}/slic3r/GUI/TabIface.hpp - ${LIBDIR}/slic3r/GUI/Field.cpp - ${LIBDIR}/slic3r/GUI/Field.hpp - ${LIBDIR}/slic3r/GUI/OptionsGroup.cpp - ${LIBDIR}/slic3r/GUI/OptionsGroup.hpp - ${LIBDIR}/slic3r/GUI/BedShapeDialog.cpp - ${LIBDIR}/slic3r/GUI/BedShapeDialog.hpp - ${LIBDIR}/slic3r/GUI/2DBed.cpp - ${LIBDIR}/slic3r/GUI/2DBed.hpp - ${LIBDIR}/slic3r/GUI/wxExtensions.cpp - ${LIBDIR}/slic3r/GUI/wxExtensions.hpp - ${LIBDIR}/slic3r/GUI/WipeTowerDialog.cpp - ${LIBDIR}/slic3r/GUI/WipeTowerDialog.hpp - ${LIBDIR}/slic3r/GUI/RammingChart.cpp - ${LIBDIR}/slic3r/GUI/RammingChart.hpp - ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp - ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp - ${LIBDIR}/slic3r/GUI/ButtonsDescription.cpp - ${LIBDIR}/slic3r/GUI/ButtonsDescription.hpp - ${LIBDIR}/slic3r/Config/Snapshot.cpp - ${LIBDIR}/slic3r/Config/Snapshot.hpp - ${LIBDIR}/slic3r/Config/Version.cpp - ${LIBDIR}/slic3r/Config/Version.hpp - ${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp - ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp - ${LIBDIR}/slic3r/Utils/Serial.cpp - ${LIBDIR}/slic3r/Utils/Serial.hpp - ${LIBDIR}/slic3r/GUI/ConfigWizard.cpp - ${LIBDIR}/slic3r/GUI/ConfigWizard.hpp - ${LIBDIR}/slic3r/GUI/MsgDialog.cpp - ${LIBDIR}/slic3r/GUI/MsgDialog.hpp - ${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp - ${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp - ${LIBDIR}/slic3r/GUI/FirmwareDialog.cpp - ${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp - ${LIBDIR}/slic3r/GUI/ProgressIndicator.hpp - ${LIBDIR}/slic3r/GUI/ProgressStatusBar.hpp - ${LIBDIR}/slic3r/GUI/ProgressStatusBar.cpp - ${LIBDIR}/slic3r/Utils/Http.cpp - ${LIBDIR}/slic3r/Utils/Http.hpp - ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp - ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp - ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.cpp - ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp - ${LIBDIR}/slic3r/Utils/OctoPrint.cpp - ${LIBDIR}/slic3r/Utils/OctoPrint.hpp - ${LIBDIR}/slic3r/Utils/Duet.cpp - ${LIBDIR}/slic3r/Utils/Duet.hpp - ${LIBDIR}/slic3r/Utils/PrintHost.cpp - ${LIBDIR}/slic3r/Utils/PrintHost.hpp - ${LIBDIR}/slic3r/Utils/Bonjour.cpp - ${LIBDIR}/slic3r/Utils/Bonjour.hpp - ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp - ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp - ${LIBDIR}/slic3r/Utils/Time.cpp - ${LIBDIR}/slic3r/Utils/Time.hpp - ${LIBDIR}/slic3r/Utils/HexFile.cpp - ${LIBDIR}/slic3r/Utils/HexFile.hpp - ${LIBDIR}/slic3r/AppController.hpp - ${LIBDIR}/slic3r/AppController.cpp - ${LIBDIR}/slic3r/AppControllerWx.cpp + pchheader.cpp + pchheader.hpp + GUI/AboutDialog.cpp + GUI/AboutDialog.hpp + GUI/SysInfoDialog.cpp + GUI/SysInfoDialog.hpp + GUI/AppConfig.cpp + GUI/AppConfig.hpp + GUI/BackgroundSlicingProcess.cpp + GUI/BackgroundSlicingProcess.hpp + GUI/BitmapCache.cpp + GUI/BitmapCache.hpp + GUI/ConfigSnapshotDialog.cpp + GUI/ConfigSnapshotDialog.hpp + GUI/3DScene.cpp + GUI/3DScene.hpp + GUI/GLShader.cpp + GUI/GLShader.hpp + GUI/GLCanvas3D.hpp + GUI/GLCanvas3D.cpp + GUI/GLCanvas3DManager.hpp + GUI/GLCanvas3DManager.cpp + GUI/GLGizmo.hpp + GUI/GLGizmo.cpp + GUI/GLTexture.hpp + GUI/GLTexture.cpp + GUI/GLToolbar.hpp + GUI/GLToolbar.cpp + GUI/Preferences.cpp + GUI/Preferences.hpp + GUI/Preset.cpp + GUI/Preset.hpp + GUI/PresetBundle.cpp + GUI/PresetBundle.hpp + GUI/PresetHints.cpp + GUI/PresetHints.hpp + GUI/GUI.cpp + GUI/GUI.hpp + GUI/GUI_Preview.cpp + GUI/GUI_Preview.hpp + GUI/GUI_App.cpp + GUI/GUI_App.hpp + GUI/GUI_Utils.cpp + GUI/GUI_Utils.hpp + GUI/I18N.cpp + GUI/I18N.hpp + GUI/MainFrame.cpp + GUI/MainFrame.hpp + GUI/Plater.cpp + GUI/Plater.hpp + GUI/GUI_ObjectList.cpp + GUI/GUI_ObjectList.hpp + GUI/GUI_ObjectManipulation.cpp + GUI/GUI_ObjectManipulation.hpp + GUI/GUI_ObjectSettings.cpp + GUI/GUI_ObjectSettings.hpp + GUI/LambdaObjectDialog.cpp + GUI/LambdaObjectDialog.hpp + GUI/Tab.cpp + GUI/Tab.hpp + GUI/Field.cpp + GUI/Field.hpp + GUI/OptionsGroup.cpp + GUI/OptionsGroup.hpp + GUI/BedShapeDialog.cpp + GUI/BedShapeDialog.hpp + GUI/2DBed.cpp + GUI/2DBed.hpp + GUI/wxExtensions.cpp + GUI/wxExtensions.hpp + GUI/WipeTowerDialog.cpp + GUI/WipeTowerDialog.hpp + GUI/RammingChart.cpp + GUI/RammingChart.hpp + GUI/BonjourDialog.cpp + GUI/BonjourDialog.hpp + GUI/ButtonsDescription.cpp + GUI/ButtonsDescription.hpp + GUI/ImGuiWrapper.hpp + GUI/ImGuiWrapper.cpp + Config/Snapshot.cpp + Config/Snapshot.hpp + Config/Version.cpp + Config/Version.hpp + Utils/ASCIIFolding.cpp + Utils/ASCIIFolding.hpp + Utils/Serial.cpp + Utils/Serial.hpp + GUI/ConfigWizard.cpp + GUI/ConfigWizard.hpp + GUI/MsgDialog.cpp + GUI/MsgDialog.hpp + GUI/UpdateDialogs.cpp + GUI/UpdateDialogs.hpp + GUI/FirmwareDialog.cpp + GUI/FirmwareDialog.hpp + GUI/ProgressIndicator.hpp + GUI/ProgressStatusBar.hpp + GUI/ProgressStatusBar.cpp + GUI/PrintHostDialogs.cpp + GUI/PrintHostDialogs.hpp + Utils/Http.cpp + Utils/Http.hpp + Utils/FixModelByWin10.cpp + Utils/FixModelByWin10.hpp + Utils/OctoPrint.cpp + Utils/OctoPrint.hpp + Utils/Duet.cpp + Utils/Duet.hpp + Utils/PrintHost.cpp + Utils/PrintHost.hpp + Utils/Bonjour.cpp + Utils/Bonjour.hpp + Utils/PresetUpdater.cpp + Utils/PresetUpdater.hpp + Utils/Time.cpp + Utils/Time.hpp + Utils/HexFile.cpp + Utils/HexFile.hpp ) -target_link_libraries(libslic3r_gui libslic3r avrdude) +target_link_libraries(libslic3r_gui libslic3r avrdude imgui) +if (NOT SLIC3R_SYNTAXONLY) + add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) +endif () diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 704fbcfa1..35bfde0b6 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -12,10 +12,10 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Config.hpp" -#include "../../libslic3r/FileParserError.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/FileParserError.hpp" +#include "libslic3r/Utils.hpp" #define SLIC3R_SNAPSHOTS_DIR "snapshots" #define SLIC3R_SNAPSHOT_FILE "snapshot.ini" @@ -406,7 +406,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: cfg.version.min_slic3r_version = it->min_slic3r_version; cfg.version.max_slic3r_version = it->max_slic3r_version; } - } catch (const std::runtime_error &err) { + } catch (const std::runtime_error & /* err */) { } snapshot.vendor_configs.emplace_back(std::move(cfg)); } @@ -521,7 +521,7 @@ SnapshotDB& SnapshotDB::singleton() // Update the min / max slic3r versions compatible with the configurations stored inside the snapshots // based on the min / max slic3r versions defined by the vendor specific config indices. instance.update_slic3r_versions(index_db); - } catch (std::exception &ex) { + } catch (std::exception & /* ex */) { } } return instance; diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index a85322eca..48ace7b60 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -4,10 +4,10 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Config.hpp" -#include "../../libslic3r/FileParserError.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/FileParserError.hpp" +#include "libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp index acb0ae460..e689286af 100644 --- a/src/slic3r/Config/Version.hpp +++ b/src/slic3r/Config/Version.hpp @@ -6,7 +6,7 @@ #include -#include "../../libslic3r/FileParserError.hpp" +#include "libslic3r/FileParserError.hpp" #include "../Utils/Semver.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index e19f839cd..c90d54cb0 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -1,9 +1,10 @@ #include "2DBed.hpp" #include -#include "BoundingBox.hpp" -#include "Geometry.hpp" -#include "ClipperUtils.hpp" + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/ClipperUtils.hpp" namespace Slic3r { namespace GUI { @@ -66,7 +67,7 @@ void Bed_2D::repaint() shift(1) - (cbb.max(1) - GetSize().GetHeight())); // draw bed fill - dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID)); + dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; for (auto pt: m_bed_shape) { @@ -86,10 +87,10 @@ void Bed_2D::repaint() } polylines = intersection_pl(polylines, bed_polygon); - dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID)); + dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID)); for (auto pl : polylines) { - for (size_t i = 0; i < pl.points.size()-1; i++){ + for (size_t i = 0; i < pl.points.size()-1; i++) { Point pt1 = to_pixels(unscale(pl.points[i])); Point pt2 = to_pixels(unscale(pl.points[i+1])); dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); @@ -97,8 +98,8 @@ void Bed_2D::repaint() } // draw bed contour - dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawPolygon(&pt_list, 0, 0); auto origin_px = to_pixels(Vec2d(0, 0)); @@ -107,15 +108,15 @@ void Bed_2D::repaint() auto axes_len = 50; auto arrow_len = 6; auto arrow_angle = Geometry::deg2rad(45.0); - dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red + dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxPENSTYLE_SOLID)); // red auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1)); dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1))); - for (auto angle : { -arrow_angle, arrow_angle }){ + for (auto angle : { -arrow_angle, arrow_angle }) { auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1)); dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1))); } - dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green + dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxPENSTYLE_SOLID)); // green auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len); dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1))); for (auto angle : { -arrow_angle, arrow_angle }) { @@ -124,13 +125,13 @@ void Bed_2D::repaint() } // draw origin - dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID)); - dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID)); + dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID)); + dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_SOLID)); dc.DrawCircle(origin_px(0), origin_px(1), 3); static const auto origin_label = wxString("(0,0)"); dc.SetTextForeground(wxColour(0, 0, 0)); - dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL)); + dc.SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); auto extent = dc.GetTextExtent(origin_label); const auto origin_label_x = origin_px(0) <= cw / 2 ? origin_px(0) + 1 : origin_px(0) - 1 - extent.GetWidth(); const auto origin_label_y = origin_px(1) <= ch / 2 ? origin_px(1) + 1 : origin_px(1) - 1 - extent.GetHeight(); @@ -139,8 +140,8 @@ void Bed_2D::repaint() // draw current position if (m_pos!= Vec2d(0, 0)) { auto pos_px = to_pixels(m_pos); - dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID)); - dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT)); + dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID)); + dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawCircle(pos_px(0), pos_px(1), 5); dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1)); @@ -151,12 +152,14 @@ void Bed_2D::repaint() } // convert G - code coordinates into pixels -Point Bed_2D::to_pixels(Vec2d point){ +Point Bed_2D::to_pixels(Vec2d point) +{ auto p = point * m_scale_factor + m_shift; return Point(p(0), GetSize().GetHeight() - p(1)); } -void Bed_2D::mouse_event(wxMouseEvent event){ +void Bed_2D::mouse_event(wxMouseEvent event) +{ if (!m_interactive) return; if (!m_painted) return; @@ -170,11 +173,13 @@ void Bed_2D::mouse_event(wxMouseEvent event){ } // convert pixels into G - code coordinates -Vec2d Bed_2D::to_units(Point point){ +Vec2d Bed_2D::to_units(Point point) +{ return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor); } -void Bed_2D::set_pos(Vec2d pos){ +void Bed_2D::set_pos(Vec2d pos) +{ m_pos = pos; Refresh(); } diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index d7a7f4260..463561953 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -2,7 +2,7 @@ #define slic3r_2DBed_hpp_ #include -#include "Config.hpp" +#include "libslic3r/Config.hpp" namespace Slic3r { namespace GUI { @@ -28,18 +28,19 @@ public: Bed_2D(wxWindow* parent) { Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL); + SetBackgroundStyle(wxBG_STYLE_PAINT); // to avoid assert message after wxAutoBufferedPaintDC // m_user_drawn_background = $^O ne 'darwin'; #ifdef __APPLE__ m_user_drawn_background = false; #endif /*__APPLE__*/ Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); })); // EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background}; -// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event){/*mouse_event()*/; })); - Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event){ mouse_event(event); })); - Bind(wxEVT_MOTION, ([this](wxMouseEvent event){ mouse_event(event); })); +// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event) {/*mouse_event()*/; })); + Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event) { mouse_event(event); })); + Bind(wxEVT_MOTION, ([this](wxMouseEvent event) { mouse_event(event); })); Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); })); } - ~Bed_2D(){} + ~Bed_2D() {} std::vector m_bed_shape; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index e6f038042..09c3443c3 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -2,14 +2,15 @@ #include "3DScene.hpp" -#include "../../libslic3r/ExtrusionEntity.hpp" -#include "../../libslic3r/ExtrusionEntityCollection.hpp" -#include "../../libslic3r/Geometry.hpp" -#include "../../libslic3r/GCode/PreviewData.hpp" -#include "../../libslic3r/Print.hpp" -#include "../../libslic3r/Slicing.hpp" -#include "../../slic3r/GUI/PresetBundle.hpp" -#include "GCode/Analyzer.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/GCode/Analyzer.hpp" +#include "slic3r/GUI/PresetBundle.hpp" #include #include @@ -36,7 +37,7 @@ void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); - for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) { + for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { const stl_facet &facet = mesh.stl.facet_start[i]; for (int j = 0; j < 3; ++ j) this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); @@ -52,7 +53,7 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); unsigned int vertices_count = 0; - for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) { + for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { const stl_facet &facet = mesh.stl.facet_start[i]; for (int j = 0; j < 3; ++j) this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); @@ -97,12 +98,18 @@ void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) void GLIndexedVertexArray::release_geometry() { - if (this->vertices_and_normals_interleaved_VBO_id) + if (this->vertices_and_normals_interleaved_VBO_id) { glDeleteBuffers(1, &this->vertices_and_normals_interleaved_VBO_id); - if (this->triangle_indices_VBO_id) + this->vertices_and_normals_interleaved_VBO_id = 0; + } + if (this->triangle_indices_VBO_id) { glDeleteBuffers(1, &this->triangle_indices_VBO_id); - if (this->quad_indices_VBO_id) + this->triangle_indices_VBO_id = 0; + } + if (this->quad_indices_VBO_id) { glDeleteBuffers(1, &this->quad_indices_VBO_id); + this->quad_indices_VBO_id = 0; + } this->clear(); this->shrink_to_fit(); } @@ -193,21 +200,31 @@ const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; +const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; +const float GLVolume::SLA_SUPPORT_COLOR[4] = { 0.75f, 0.75f, 0.75f, 1.0f }; +const float GLVolume::SLA_PAD_COLOR[4] = { 0.0f, 0.2f, 0.0f, 1.0f }; GLVolume::GLVolume(float r, float g, float b, float a) +#if ENABLE_MODELVOLUME_TRANSFORM + : m_transformed_bounding_box_dirty(true) +#else : m_offset(Vec3d::Zero()) - , m_rotation(0.0) - , m_scaling_factor(1.0) + , m_rotation(Vec3d::Zero()) + , m_scaling_factor(Vec3d::Ones()) + , m_mirror(Vec3d::Ones()) , m_world_matrix(Transform3f::Identity()) , m_world_matrix_dirty(true) , m_transformed_bounding_box_dirty(true) +#endif // ENABLE_MODELVOLUME_TRANSFORM + , m_sla_shift_z(0.0) , m_transformed_convex_hull_bounding_box_dirty(true) , m_convex_hull(nullptr) - , composite_id(-1) - , select_group_id(-1) - , drag_group_id(-1) + , m_convex_hull_owned(false) + // geometry_id == 0 -> invalid + , geometry_id(std::pair(0, 0)) , extruder_id(0) , selected(false) + , disabled(false) , is_active(true) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) @@ -226,6 +243,12 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(r, g, b, a); } +GLVolume::~GLVolume() +{ + if (m_convex_hull_owned) + delete m_convex_hull; +} + void GLVolume::set_render_color(float r, float g, float b, float a) { render_color[0] = r; @@ -237,7 +260,7 @@ void GLVolume::set_render_color(float r, float g, float b, float a) void GLVolume::set_render_color(const float* rgba, unsigned int size) { size = std::min((unsigned int)4, size); - for (int i = 0; i < size; ++i) + for (unsigned int i = 0; i < size; ++i) { render_color[i] = rgba[i]; } @@ -249,22 +272,58 @@ void GLVolume::set_render_color() set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); else if (hover) set_render_color(HOVER_COLOR, 4); + else if (disabled) + set_render_color(DISABLED_COLOR, 4); else if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); else set_render_color(color, 4); } -double GLVolume::get_rotation() +void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume) +{ + if (model_volume->is_modifier()) { + color[0] = 0.2f; + color[1] = 1.0f; + color[2] = 0.2f; + } + else if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } + else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; +} + +#if !ENABLE_MODELVOLUME_TRANSFORM +const Vec3d& GLVolume::get_rotation() const { return m_rotation; } -void GLVolume::set_rotation(double rotation) +void GLVolume::set_rotation(const Vec3d& rotation) { + static const double TWO_PI = 2.0 * (double)PI; + if (m_rotation != rotation) { m_rotation = rotation; + for (int i = 0; i < 3; ++i) + { + while (m_rotation(i) < 0.0) + { + m_rotation(i) += TWO_PI; + } + while (TWO_PI < m_rotation(i)) + { + m_rotation(i) -= TWO_PI; + } + } m_world_matrix_dirty = true; m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; @@ -287,58 +346,91 @@ void GLVolume::set_offset(const Vec3d& offset) } } -void GLVolume::set_scaling_factor(double factor) +const Vec3d& GLVolume::get_scaling_factor() const { - if (m_scaling_factor != factor) + return m_scaling_factor; +} + +void GLVolume::set_scaling_factor(const Vec3d& scaling_factor) +{ + if (m_scaling_factor != scaling_factor) { - m_scaling_factor = factor; + m_scaling_factor = scaling_factor; m_world_matrix_dirty = true; m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } } -void GLVolume::set_convex_hull(const TriangleMesh& convex_hull) +const Vec3d& GLVolume::get_mirror() const { - m_convex_hull = &convex_hull; + return m_mirror; } -void GLVolume::set_select_group_id(const std::string& select_by) +double GLVolume::get_mirror(Axis axis) const { - if (select_by == "object") - select_group_id = object_idx() * 1000000; - else if (select_by == "volume") - select_group_id = object_idx() * 1000000 + volume_idx() * 1000; - else if (select_by == "instance") - select_group_id = composite_id; + return m_mirror(axis); } -void GLVolume::set_drag_group_id(const std::string& drag_by) +void GLVolume::set_mirror(const Vec3d& mirror) { - if (drag_by == "object") - drag_group_id = object_idx() * 1000; - else if (drag_by == "instance") - drag_group_id = object_idx() * 1000 + instance_idx(); + if (m_mirror != mirror) + { + m_mirror = mirror; + m_world_matrix_dirty = true; + m_transformed_bounding_box_dirty = true; + m_transformed_convex_hull_bounding_box_dirty = true; + } } +void GLVolume::set_mirror(Axis axis, double mirror) +{ + if (m_mirror(axis) != mirror) + { + m_mirror(axis) = mirror; + m_world_matrix_dirty = true; + m_transformed_bounding_box_dirty = true; + m_transformed_convex_hull_bounding_box_dirty = true; + } +} +#endif // !ENABLE_MODELVOLUME_TRANSFORM + +void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned) +{ + m_convex_hull = convex_hull; + m_convex_hull_owned = owned; +} + +#if ENABLE_MODELVOLUME_TRANSFORM +Transform3d GLVolume::world_matrix() const +{ + Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); + m.translation()(2) += m_sla_shift_z; + return m; +} +#else const Transform3f& GLVolume::world_matrix() const { if (m_world_matrix_dirty) { - m_world_matrix = Transform3f::Identity(); - m_world_matrix.translate(m_offset.cast()); - m_world_matrix.rotate(Eigen::AngleAxisf((float)m_rotation, Vec3f::UnitZ())); - m_world_matrix.scale((float)m_scaling_factor); + m_world_matrix = Geometry::assemble_transform(m_offset, m_rotation, m_scaling_factor, m_mirror).cast(); m_world_matrix_dirty = false; } return m_world_matrix; } +#endif // ENABLE_MODELVOLUME_TRANSFORM const BoundingBoxf3& GLVolume::transformed_bounding_box() const { + assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); + if (m_transformed_bounding_box_dirty) { +#if ENABLE_MODELVOLUME_TRANSFORM + m_transformed_bounding_box = bounding_box.transformed(world_matrix()); +#else m_transformed_bounding_box = bounding_box.transformed(world_matrix().cast()); +#endif // ENABLE_MODELVOLUME_TRANSFORM m_transformed_bounding_box_dirty = false; } @@ -349,10 +441,17 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const { if (m_transformed_convex_hull_bounding_box_dirty) { +#if ENABLE_MODELVOLUME_TRANSFORM + if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0)) + m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix()); + else + m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix()); +#else if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0)) m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix().cast()); else m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix().cast()); +#endif // ENABLE_MODELVOLUME_TRANSFORM m_transformed_convex_hull_bounding_box_dirty = false; } @@ -402,9 +501,12 @@ void GLVolume::render() const ::glCullFace(GL_BACK); ::glPushMatrix(); - ::glTranslated(m_offset(0), m_offset(1), m_offset(2)); - ::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0); - ::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor); + +#if ENABLE_MODELVOLUME_TRANSFORM + ::glMultMatrixd(world_matrix().data()); +#else + ::glMultMatrixf(world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM if (this->indexed_vertex_array.indexed()) this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); else @@ -442,7 +544,11 @@ void GLVolume::render_using_layer_height() const glUniform1f(z_cursor_band_width_id, (GLfloat)layer_height_texture_data.edit_band_width); if (world_matrix_id >= 0) +#if ENABLE_MODELVOLUME_TRANSFORM + ::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data()); +#else ::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM GLsizei w = (GLsizei)layer_height_texture_width(); GLsizei h = (GLsizei)layer_height_texture_height(); @@ -496,13 +602,17 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c ::glUniform4fv(color_id, 1, (const GLfloat*)color); } else - ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); + ::glColor4fv(render_color); if (detection_id != -1) ::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0); if (worldmatrix_id != -1) +#if ENABLE_MODELVOLUME_TRANSFORM + ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data()); +#else ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM render(); @@ -515,22 +625,29 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c if (color_id >= 0) ::glUniform4fv(color_id, 1, (const GLfloat*)render_color); else - ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); + ::glColor4fv(render_color); if (detection_id != -1) ::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0); if (worldmatrix_id != -1) +#if ENABLE_MODELVOLUME_TRANSFORM + ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data()); +#else ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM ::glBindBuffer(GL_ARRAY_BUFFER, indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); ::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))); ::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr); ::glPushMatrix(); - ::glTranslated(m_offset(0), m_offset(1), m_offset(2)); - ::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0); - ::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor); + +#if ENABLE_MODELVOLUME_TRANSFORM + ::glMultMatrixd(world_matrix().data()); +#else + ::glMultMatrixf(world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM if (n_triangles > 0) { @@ -559,7 +676,7 @@ void GLVolume::render_legacy() const ::glDisableClientState(GL_VERTEX_ARRAY); ::glDisableClientState(GL_NORMAL_ARRAY); - ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); + ::glColor4fv(render_color); render(); ::glEnableClientState(GL_VERTEX_ARRAY); @@ -568,14 +685,17 @@ void GLVolume::render_legacy() const return; } - ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); + ::glColor4fv(render_color); ::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data() + 3); ::glNormalPointer(GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data()); ::glPushMatrix(); - ::glTranslated(m_offset(0), m_offset(1), m_offset(2)); - ::glRotated(m_rotation * 180.0 / (double)PI, 0.0, 0.0, 1.0); - ::glScaled(m_scaling_factor, m_scaling_factor, m_scaling_factor); + +#if ENABLE_MODELVOLUME_TRANSFORM + ::glMultMatrixd(world_matrix().data()); +#else + ::glMultMatrixf(world_matrix().data()); +#endif // ENABLE_MODELVOLUME_TRANSFORM if (n_triangles > 0) ::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, indexed_vertex_array.triangle_indices.data() + tverts_range.first); @@ -625,13 +745,30 @@ void GLVolume::generate_layer_height_texture(const PrintObject *print_object, bo #define LAYER_HEIGHT_TEXTURE_HEIGHT 1024 std::vector GLVolumeCollection::load_object( - const ModelObject *model_object, + const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, const std::string &color_by, - const std::string &select_by, - const std::string &drag_by, bool use_VBOs) +{ + // Object will share a single common layer height texture between all printable volumes. + std::shared_ptr layer_height_texture = std::make_shared(); + std::vector volumes_idx; + for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) + for (int instance_idx : instance_idxs) + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, color_by, use_VBOs)); + return volumes_idx; +} + +int GLVolumeCollection::load_object_volume( + const ModelObject *model_object, + // Layer height texture is shared between all printable volumes of a single ModelObject. + std::shared_ptr &layer_height_texture, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool use_VBOs) { static float colors[4][4] = { { 1.0f, 1.0f, 0.0f, 1.f }, @@ -640,72 +777,105 @@ std::vector GLVolumeCollection::load_object( { 0.5f, 0.5f, 1.0f, 1.f } }; - // Object will have a single common layer height texture for all volumes. - std::shared_ptr layer_height_texture = std::make_shared(); - - std::vector volumes_idx; - for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - - int extruder_id = -1; - if (model_volume->is_model_part()) - { - extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; - if (extruder_id == 0) - extruder_id = model_object->config.has("extruder") ? model_object->config.option("extruder")->getInt() : 0; - } - - for (int instance_idx : instance_idxs) { - const ModelInstance *instance = model_object->instances[instance_idx]; - TriangleMesh mesh = model_volume->mesh; - volumes_idx.push_back(int(this->volumes.size())); - float color[4]; - memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); - if (model_volume->is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } else if (model_volume->is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; - this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); - if (use_VBOs) - v.indexed_vertex_array.load_mesh_full_shading(mesh); - else - v.indexed_vertex_array.load_mesh_flat_shading(mesh); - - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx; - v.set_select_group_id(select_by); - v.set_drag_group_id(drag_by); - if (model_volume->is_model_part()) - { - v.set_convex_hull(model_volume->get_convex_hull()); - v.layer_height_texture = layer_height_texture; - if (extruder_id != -1) - v.extruder_id = extruder_id; - } - v.is_modifier = ! model_volume->is_model_part(); - v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); -#if ENABLE_MODELINSTANCE_3D_OFFSET - v.set_offset(instance->get_offset()); + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + const int extruder_id = model_volume->extruder_id(); + const ModelInstance *instance = model_object->instances[instance_idx]; +#if ENABLE_MODELVOLUME_TRANSFORM + const TriangleMesh& mesh = model_volume->mesh; #else - v.set_offset(Vec3d(instance->offset(0), instance->offset(1), 0.0)); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - v.set_rotation(instance->rotation); - v.set_scaling_factor(instance->scaling_factor); - } + TriangleMesh mesh = model_volume->mesh; +#endif // ENABLE_MODELVOLUME_TRANSFORM + float color[4]; + memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); +/* if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; } - - return volumes_idx; + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ + this->volumes.emplace_back(new GLVolume(color)); + GLVolume &v = *this->volumes.back(); + v.set_color_from_model_volume(model_volume); + if (use_VBOs) + v.indexed_vertex_array.load_mesh_full_shading(mesh); + else + v.indexed_vertex_array.load_mesh_flat_shading(mesh); + + // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). + v.bounding_box = v.indexed_vertex_array.bounding_box(); + v.indexed_vertex_array.finalize_geometry(use_VBOs); + v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); + if (model_volume->is_model_part()) + { + // GLVolume will reference a convex hull from model_volume! + v.set_convex_hull(&model_volume->get_convex_hull(), false); + if (extruder_id != -1) + v.extruder_id = extruder_id; + v.layer_height_texture = layer_height_texture; + } + v.is_modifier = ! model_volume->is_model_part(); + v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); +#if ENABLE_MODELVOLUME_TRANSFORM + v.set_instance_transformation(instance->get_transformation()); + v.set_volume_transformation(model_volume->get_transformation()); +#else + v.set_offset(instance->get_offset()); + v.set_rotation(instance->get_rotation()); + v.set_scaling_factor(instance->get_scaling_factor()); + v.set_mirror(instance->get_mirror()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + return int(this->volumes.size() - 1); } +// Load SLA auxiliary GLVolumes (for support trees or pad). +// This function produces volumes for multiple instances in a single shot, +// as some object specific mesh conversions may be expensive. +void GLVolumeCollection::load_object_auxiliary( + const SLAPrintObject *print_object, + int obj_idx, + // pairs of + const std::vector> &instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp, + bool use_VBOs) +{ + assert(print_object->is_step_done(milestone)); + Transform3d mesh_trafo_inv = print_object->trafo().inverse(); + // Get the support mesh. + TriangleMesh mesh = print_object->get_mesh(milestone); + mesh.transform(mesh_trafo_inv); + // Convex hull is required for out of print bed detection. + TriangleMesh convex_hull = mesh.convex_hull_3d(); + convex_hull.transform(mesh_trafo_inv); + for (const std::pair &instance_idx : instances) { + const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first]; + const SLAPrintObject::Instance &print_instance = print_object->instances()[instance_idx.second]; + this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); + GLVolume &v = *this->volumes.back(); + if (use_VBOs) + v.indexed_vertex_array.load_mesh_full_shading(mesh); + else + v.indexed_vertex_array.load_mesh_flat_shading(mesh); + // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). + v.bounding_box = v.indexed_vertex_array.bounding_box(); + v.indexed_vertex_array.finalize_geometry(use_VBOs); + v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); + v.geometry_id = std::pair(timestamp, model_instance.id().id); + // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. + v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); + v.is_modifier = false; + v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); + v.set_instance_transformation(model_instance.get_transformation()); + // Leave the volume transformation at identity. + // v.set_volume_transformation(model_volume->get_transformation()); + } +} int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) @@ -729,9 +899,9 @@ int GLVolumeCollection::load_wipe_tower_preview( // edge has y=0 and centerline of the back edge has y=depth: Pointf3s points; std::vector facets; - float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0}, - {38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}}; - int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14}, + float out_points_idx[][3] = { { 0, -depth, 0 }, { 0, 0, 0 }, { 38.453f, 0, 0 }, { 61.547f, 0, 0 }, { 100.0f, 0, 0 }, { 100.0f, -depth, 0 }, { 55.7735f, -10.0f, 0 }, { 44.2265f, 10.0f, 0 }, + { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } }; + int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; for (int i=0;i<16;++i) @@ -768,14 +938,16 @@ int GLVolumeCollection::load_wipe_tower_preview( else v.indexed_vertex_array.load_mesh_flat_shading(mesh); +#if ENABLE_MODELVOLUME_TRANSFORM + v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); +#else v.set_offset(Vec3d(pos_x, pos_y, 0.0)); +#endif // ENABLE_MODELVOLUME_TRANSFORM // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = obj_idx * 1000000; - v.select_group_id = obj_idx * 1000000; - v.drag_group_id = obj_idx * 1000; + v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.is_wipe_tower = true; v.shader_outside_printer_detection_enabled = ! size_unknown; return int(this->volumes.size() - 1); @@ -793,6 +965,7 @@ void GLVolumeCollection::render_VBOs() const GLint current_program_id; ::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id); GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1; + GLint z_range_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "z_range") : -1; GLint print_box_min_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.min") : -1; GLint print_box_max_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.max") : -1; GLint print_box_detection_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1; @@ -804,6 +977,9 @@ void GLVolumeCollection::render_VBOs() const if (print_box_max_id != -1) ::glUniform3fv(print_box_max_id, 1, (const GLfloat*)print_box_max); + if (z_range_id != -1) + ::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range); + for (GLVolume *volume : this->volumes) { if (volume->layer_height_texture_data.can_use()) @@ -863,20 +1039,20 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M for (GLVolume* volume : this->volumes) { - if ((volume != nullptr) && !volume->is_modifier && (!volume->is_wipe_tower || (volume->is_wipe_tower && volume->shader_outside_printer_detection_enabled))) - { - const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); - bool contained = print_volume.contains(bb); - all_contained &= contained; + if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + continue; - volume->is_outside = !contained; + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + bool contained = print_volume.contains(bb); + all_contained &= contained; - if ((state == ModelInstance::PVS_Inside) && volume->is_outside) - state = ModelInstance::PVS_Fully_Outside; + volume->is_outside = !contained; - if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstance::PVS_Partly_Outside; - } + if ((state == ModelInstance::PVS_Inside) && volume->is_outside) + state = ModelInstance::PVS_Fully_Outside; + + if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) + state = ModelInstance::PVS_Partly_Outside; } if (out_state != nullptr) @@ -953,11 +1129,11 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con for (GLVolume* volume : volumes) { - if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower) + if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) continue; int extruder_id = volume->extruder_id - 1; - if ((extruder_id < 0) || ((unsigned int)colors.size() <= extruder_id)) + if ((extruder_id < 0) || ((int)colors.size() <= extruder_id)) extruder_id = 0; const Color& color = colors[extruder_id]; @@ -971,24 +1147,6 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con } } -void GLVolumeCollection::set_select_by(const std::string& select_by) -{ - for (GLVolume *vol : this->volumes) - { - if (vol != nullptr) - vol->set_select_group_id(select_by); - } -} - -void GLVolumeCollection::set_drag_by(const std::string& drag_by) -{ - for (GLVolume *vol : this->volumes) - { - if (vol != nullptr) - vol->set_drag_group_id(drag_by); - } -} - std::vector GLVolumeCollection::get_current_print_zs(bool active_only) const { // Collect layer top positions of all volumes. @@ -1710,21 +1868,11 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; -void _3DScene::init_gl() -{ - s_canvas_mgr.init_gl(); -} - std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { return s_canvas_mgr.get_gl_info(format_as_html, extensions); } -bool _3DScene::use_VBOs() -{ - return s_canvas_mgr.use_VBOs(); -} - bool _3DScene::add_canvas(wxGLCanvas* canvas) { return s_canvas_mgr.add(canvas); @@ -1745,461 +1893,9 @@ bool _3DScene::init(wxGLCanvas* canvas) return s_canvas_mgr.init(canvas); } -void _3DScene::set_as_dirty(wxGLCanvas* canvas) +GUI::GLCanvas3D* _3DScene::get_canvas(wxGLCanvas* canvas) { - s_canvas_mgr.set_as_dirty(canvas); -} - -unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas) -{ - return s_canvas_mgr.get_volumes_count(canvas); -} - -void _3DScene::reset_volumes(wxGLCanvas* canvas) -{ - s_canvas_mgr.reset_volumes(canvas); -} - -void _3DScene::deselect_volumes(wxGLCanvas* canvas) -{ - s_canvas_mgr.deselect_volumes(canvas); -} - -void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id) -{ - s_canvas_mgr.select_volume(canvas, id); -} - -void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections) -{ - s_canvas_mgr.update_volumes_selection(canvas, selections); -} - -int _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) -{ - return s_canvas_mgr.check_volumes_outside_state(canvas, config); -} - -bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id) -{ - return s_canvas_mgr.move_volume_up(canvas, id); -} - -bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id) -{ - return s_canvas_mgr.move_volume_down(canvas, id); -} - -void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector& selections) -{ - s_canvas_mgr.set_objects_selections(canvas, selections); -} - -void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) -{ - s_canvas_mgr.set_config(canvas, config); -} - -void _3DScene::set_print(wxGLCanvas* canvas, Print* print) -{ - s_canvas_mgr.set_print(canvas, print); -} - -void _3DScene::set_model(wxGLCanvas* canvas, Model* model) -{ - s_canvas_mgr.set_model(canvas, model); -} - -void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) -{ - s_canvas_mgr.set_bed_shape(canvas, shape); -} - -void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas) -{ - s_canvas_mgr.set_auto_bed_shape(canvas); -} - -BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas) -{ - return s_canvas_mgr.get_volumes_bounding_box(canvas); -} - -void _3DScene::set_axes_length(wxGLCanvas* canvas, float length) -{ - s_canvas_mgr.set_axes_length(canvas, length); -} - -void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) -{ - s_canvas_mgr.set_cutting_plane(canvas, z, polygons); -} - -void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value) -{ - s_canvas_mgr.set_color_by(canvas, value); -} - -void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value) -{ - s_canvas_mgr.set_select_by(canvas, value); -} - -void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value) -{ - s_canvas_mgr.set_drag_by(canvas, value); -} - -std::string _3DScene::get_select_by(wxGLCanvas* canvas) -{ - return s_canvas_mgr.get_select_by(canvas); -} - -bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas) -{ - return s_canvas_mgr.is_layers_editing_enabled(canvas); -} - -bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas) -{ - return s_canvas_mgr.is_layers_editing_allowed(canvas); -} - -bool _3DScene::is_shader_enabled(wxGLCanvas* canvas) -{ - return s_canvas_mgr.is_shader_enabled(canvas); -} - -bool _3DScene::is_reload_delayed(wxGLCanvas* canvas) -{ - return s_canvas_mgr.is_reload_delayed(canvas); -} - -void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_layers_editing(canvas, enable); -} - -void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_warning_texture(canvas, enable); -} - -void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_legend_texture(canvas, enable); -} - -void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_picking(canvas, enable); -} - -void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_moving(canvas, enable); -} - -void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_gizmos(canvas, enable); -} - -void _3DScene::enable_toolbar(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_toolbar(canvas, enable); -} - -void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_shader(canvas, enable); -} - -void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable); -} - -void _3DScene::enable_dynamic_background(wxGLCanvas* canvas, bool enable) -{ - s_canvas_mgr.enable_dynamic_background(canvas, enable); -} - -void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) -{ - s_canvas_mgr.allow_multisample(canvas, allow); -} - -void _3DScene::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) -{ - s_canvas_mgr.enable_toolbar_item(canvas, name, enable); -} - -bool _3DScene::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) -{ - return s_canvas_mgr.is_toolbar_item_pressed(canvas, name); -} - -void _3DScene::zoom_to_bed(wxGLCanvas* canvas) -{ - s_canvas_mgr.zoom_to_bed(canvas); -} - -void _3DScene::zoom_to_volumes(wxGLCanvas* canvas) -{ - s_canvas_mgr.zoom_to_volumes(canvas); -} - -void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction) -{ - s_canvas_mgr.select_view(canvas, direction); -} - -void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) -{ - s_canvas_mgr.set_viewport_from_scene(canvas, other); -} - -void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas) -{ - s_canvas_mgr.update_volumes_colors_by_extruder(canvas); -} - -void _3DScene::update_gizmos_data(wxGLCanvas* canvas) -{ - s_canvas_mgr.update_gizmos_data(canvas); -} - -void _3DScene::render(wxGLCanvas* canvas) -{ - s_canvas_mgr.render(canvas); -} - -std::vector _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only) -{ - return s_canvas_mgr.get_current_print_zs(canvas, active_only); -} - -void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) -{ - s_canvas_mgr.set_toolpaths_range(canvas, low, high); -} - -void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback); -} - -void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_double_click_callback(canvas, callback); -} - -void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_right_click_callback(canvas, callback); -} - -void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_select_object_callback(canvas, callback); -} - -void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_model_update_callback(canvas, callback); -} - -void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_remove_object_callback(canvas, callback); -} - -void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_arrange_callback(canvas, callback); -} - -void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback); -} - -void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback); -} - -void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback); -} - -void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_increase_objects_callback(canvas, callback); -} - -void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback); -} - -void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_instance_moved_callback(canvas, callback); -} - -void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback); -} - -void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback); -} - -void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback); -} - -void _3DScene::register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_gizmo_rotate_callback(canvas, callback); -} - -void _3DScene::register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_gizmo_flatten_callback(canvas, callback); -} - -void _3DScene::register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_on_update_geometry_info_callback(canvas, callback); -} - -void _3DScene::register_action_add_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_add_callback(canvas, callback); -} - -void _3DScene::register_action_delete_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_delete_callback(canvas, callback); -} - -void _3DScene::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_deleteall_callback(canvas, callback); -} - -void _3DScene::register_action_arrange_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_arrange_callback(canvas, callback); -} - -void _3DScene::register_action_more_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_more_callback(canvas, callback); -} - -void _3DScene::register_action_fewer_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_fewer_callback(canvas, callback); -} - -void _3DScene::register_action_split_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_split_callback(canvas, callback); -} - -void _3DScene::register_action_cut_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_cut_callback(canvas, callback); -} - -void _3DScene::register_action_settings_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_settings_callback(canvas, callback); -} - -void _3DScene::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_layersediting_callback(canvas, callback); -} - -void _3DScene::register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_selectbyparts_callback(canvas, callback); -} - -static inline int hex_digit_to_int(const char c) -{ - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; -} - -static inline std::vector parse_colors(const std::vector &scolors) -{ - std::vector output(scolors.size() * 4, 1.f); - for (size_t i = 0; i < scolors.size(); ++ i) { - const std::string &scolor = scolors[i]; - const char *c = scolor.data() + 1; - if (scolor.size() == 7 && scolor.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c ++); - int digit2 = hex_digit_to_int(*c ++); - if (digit1 == -1 || digit2 == -1) - break; - output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; - } - } - } - return output; -} - -std::vector _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs) -{ - return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs); -} - -std::vector _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) -{ - return s_canvas_mgr.load_object(canvas, model, obj_idx); -} - -int _3DScene::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) -{ - return s_canvas_mgr.get_first_volume_id(canvas, obj_idx); -} - -int _3DScene::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) -{ - return s_canvas_mgr.get_in_object_volume_id(canvas, scene_vol_idx); -} - -void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) -{ - s_canvas_mgr.reload_scene(canvas, force); -} - -void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors) -{ - s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); -} - -void _3DScene::load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors) -{ - s_canvas_mgr.load_preview(canvas, str_tool_colors); -} - -void _3DScene::reset_legend_texture() -{ - s_canvas_mgr.reset_legend_texture(); + return s_canvas_mgr.get_canvas(canvas); } } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index f2d1c0786..97b0310e5 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -1,21 +1,21 @@ #ifndef slic3r_3DScene_hpp_ #define slic3r_3DScene_hpp_ -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Point.hpp" -#include "../../libslic3r/Line.hpp" -#include "../../libslic3r/TriangleMesh.hpp" -#include "../../libslic3r/Utils.hpp" -#include "../../libslic3r/Model.hpp" -#include "../../slic3r/GUI/GLCanvas3DManager.hpp" - -class wxBitmap; -class wxWindow; +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" +#include "slic3r/GUI/GLCanvas3DManager.hpp" namespace Slic3r { class Print; class PrintObject; +class SLAPrint; +class SLAPrintObject; +enum SLAPrintObjectStep : unsigned int; class Model; class ModelObject; class GCodePreviewData; @@ -249,50 +249,79 @@ public: static const float HOVER_COLOR[4]; static const float OUTSIDE_COLOR[4]; static const float SELECTED_OUTSIDE_COLOR[4]; + static const float DISABLED_COLOR[4]; + static const float SLA_SUPPORT_COLOR[4]; + static const float SLA_PAD_COLOR[4]; GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} + ~GLVolume(); private: +#if ENABLE_MODELVOLUME_TRANSFORM + Geometry::Transformation m_instance_transformation; + Geometry::Transformation m_volume_transformation; +#else // Offset of the volume to be rendered. Vec3d m_offset; - // Rotation around Z axis of the volume to be rendered. - double m_rotation; - // Scale factor of the volume to be rendered. - double m_scaling_factor; + // Rotation around three axes of the volume to be rendered. + Vec3d m_rotation; + // Scale factor along the three axes of the volume to be rendered. + Vec3d m_scaling_factor; + // Mirroring along the three axes of the volume to be rendered. + Vec3d m_mirror; // World matrix of the volume to be rendered. mutable Transform3f m_world_matrix; // Whether or not is needed to recalculate the world matrix. mutable bool m_world_matrix_dirty; +#endif // ENABLE_MODELVOLUME_TRANSFORM + // Shift in z required by sla supports+pad + double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. mutable bool m_transformed_bounding_box_dirty; // Pointer to convex hull of the original mesh, if any. + // This object may or may not own the convex hull instance based on m_convex_hull_owned const TriangleMesh* m_convex_hull; + bool m_convex_hull_owned; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; // Whether or not is needed to recalculate the transformed convex hull bounding box. mutable bool m_transformed_convex_hull_bounding_box_dirty; public: - // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box; // Color of the triangles / quads held by this volume. float color[4]; // Color used to render this volume. float render_color[4]; - // An ID containing the object ID, volume ID and instance ID. - int composite_id; - // An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance. - int select_group_id; - // An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance. - int drag_group_id; + struct CompositeID { + CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} + CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {} + // Object ID, which is equal to the index of the respective ModelObject in Model.objects array. + int object_id; + // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array. + // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject, + // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports. + // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance. + int volume_id; + // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array. + int instance_id; + }; + CompositeID composite_id; + // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, + // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(), + // and the associated ModelInstanceID. + // Valid geometry_id should always be positive. + std::pair geometry_id; // An ID containing the extruder ID (used to select color). int extruder_id; // Is this object selected? bool selected; + // Is this object disabled from selection? + bool disabled; // Whether or not this volume is active for rendering bool is_active; // Whether or not to use this volume when applying zoom_to_volumes() @@ -326,25 +355,93 @@ public: void set_render_color(const float* rgba, unsigned int size); // Sets render color in dependence of current state void set_render_color(); + // set color according to model volume + void set_color_from_model_volume(const ModelVolume *model_volume); - double get_rotation(); - void set_rotation(double rotation); +#if ENABLE_MODELVOLUME_TRANSFORM + const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } + void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } + + const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } + double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } + + void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + + const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } + double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } + + void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + + Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); } + double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); } + + void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + + const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } + double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } + + void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } + + const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } + void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } + + const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } + double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } + + void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + + const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } + double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } + + void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + + Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } + double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } + + void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + + const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } + double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } + + void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } +#else + const Vec3d& get_rotation() const; + void set_rotation(const Vec3d& rotation); + + const Vec3d& get_scaling_factor() const; + void set_scaling_factor(const Vec3d& scaling_factor); + + const Vec3d& get_mirror() const; + double get_mirror(Axis axis) const; + void set_mirror(const Vec3d& mirror); + void set_mirror(Axis axis, double mirror); const Vec3d& get_offset() const; void set_offset(const Vec3d& offset); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + double get_sla_shift_z() const { return m_sla_shift_z; } + void set_sla_shift_z(double z) { m_sla_shift_z = z; } - void set_scaling_factor(double factor); + void set_convex_hull(const TriangleMesh *convex_hull, bool owned); - void set_convex_hull(const TriangleMesh& convex_hull); - - void set_select_group_id(const std::string& select_by); - void set_drag_group_id(const std::string& drag_by); - - int object_idx() const { return this->composite_id / 1000000; } - int volume_idx() const { return (this->composite_id / 1000) % 1000; } - int instance_idx() const { return this->composite_id % 1000; } + int object_idx() const { return this->composite_id.object_id; } + int volume_idx() const { return this->composite_id.volume_id; } + int instance_idx() const { return this->composite_id.instance_id; } +#if ENABLE_MODELVOLUME_TRANSFORM + Transform3d world_matrix() const; +#else const Transform3f& world_matrix() const; +#endif // ENABLE_MODELVOLUME_TRANSFORM const BoundingBoxf3& transformed_bounding_box() const; const BoundingBoxf3& transformed_convex_hull_bounding_box() const; @@ -394,17 +491,26 @@ public: } void reset_layer_height_texture_data() { layer_height_texture_data.reset(); } + +#if ENABLE_MODELVOLUME_TRANSFORM + void set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } +#endif // ENABLE_MODELVOLUME_TRANSFORM }; +typedef std::vector GLVolumePtrs; + class GLVolumeCollection { // min and max vertex of the print box volume float print_box_min[3]; float print_box_max[3]; + // z range for clipping in shaders + float z_range[2]; + public: - std::vector volumes; - + GLVolumePtrs volumes; + GLVolumeCollection() {}; ~GLVolumeCollection() { clear(); }; @@ -413,10 +519,28 @@ public: int obj_idx, const std::vector &instance_idxs, const std::string &color_by, - const std::string &select_by, - const std::string &drag_by, bool use_VBOs); + int load_object_volume( + const ModelObject *model_object, + std::shared_ptr &layer_height_texture, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool use_VBOs); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject *print_object, + int obj_idx, + // pairs of + const std::vector> &instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp, + bool use_VBOs); + int load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); @@ -442,6 +566,8 @@ public: print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z; } + void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; } + // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null bool check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state); @@ -449,9 +575,6 @@ public: void update_colors_by_extruder(const DynamicPrintConfig* config); - void set_select_by(const std::string& select_by); - void set_drag_by(const std::string& drag_by); - // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection std::vector get_current_print_zs(bool active_only) const; @@ -465,9 +588,7 @@ class _3DScene static GUI::GLCanvas3DManager s_canvas_mgr; public: - static void init_gl(); static std::string get_gl_info(bool format_as_html, bool extensions); - static bool use_VBOs(); static bool add_canvas(wxGLCanvas* canvas); static bool remove_canvas(wxGLCanvas* canvas); @@ -475,116 +596,7 @@ public: static bool init(wxGLCanvas* canvas); - static void set_as_dirty(wxGLCanvas* canvas); - - static unsigned int get_volumes_count(wxGLCanvas* canvas); - static void reset_volumes(wxGLCanvas* canvas); - static void deselect_volumes(wxGLCanvas* canvas); - static void select_volume(wxGLCanvas* canvas, unsigned int id); - static void update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections); - static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config); - static bool move_volume_up(wxGLCanvas* canvas, unsigned int id); - static bool move_volume_down(wxGLCanvas* canvas, unsigned int id); - - static void set_objects_selections(wxGLCanvas* canvas, const std::vector& selections); - - static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); - static void set_print(wxGLCanvas* canvas, Print* print); - static void set_model(wxGLCanvas* canvas, Model* model); - - static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); - static void set_auto_bed_shape(wxGLCanvas* canvas); - - static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); - - static void set_axes_length(wxGLCanvas* canvas, float length); - - static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); - - static void set_color_by(wxGLCanvas* canvas, const std::string& value); - static void set_select_by(wxGLCanvas* canvas, const std::string& value); - static void set_drag_by(wxGLCanvas* canvas, const std::string& value); - - static std::string get_select_by(wxGLCanvas* canvas); - - static bool is_layers_editing_enabled(wxGLCanvas* canvas); - static bool is_layers_editing_allowed(wxGLCanvas* canvas); - static bool is_shader_enabled(wxGLCanvas* canvas); - - static bool is_reload_delayed(wxGLCanvas* canvas); - - static void enable_layers_editing(wxGLCanvas* canvas, bool enable); - static void enable_warning_texture(wxGLCanvas* canvas, bool enable); - static void enable_legend_texture(wxGLCanvas* canvas, bool enable); - static void enable_picking(wxGLCanvas* canvas, bool enable); - static void enable_moving(wxGLCanvas* canvas, bool enable); - static void enable_gizmos(wxGLCanvas* canvas, bool enable); - static void enable_toolbar(wxGLCanvas* canvas, bool enable); - static void enable_shader(wxGLCanvas* canvas, bool enable); - static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); - static void enable_dynamic_background(wxGLCanvas* canvas, bool enable); - static void allow_multisample(wxGLCanvas* canvas, bool allow); - - static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); - static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name); - - static void zoom_to_bed(wxGLCanvas* canvas); - static void zoom_to_volumes(wxGLCanvas* canvas); - static void select_view(wxGLCanvas* canvas, const std::string& direction); - static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); - - static void update_volumes_colors_by_extruder(wxGLCanvas* canvas); - static void update_gizmos_data(wxGLCanvas* canvas); - - static void render(wxGLCanvas* canvas); - - static std::vector get_current_print_zs(wxGLCanvas* canvas, bool active_only); - static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); - - static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); - static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); - static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); - static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); - static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); - static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); - static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); - static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); - static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); - static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); - static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); - static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); - static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); - static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); - static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); - static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); - static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); - static void register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback); - static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); - - static void register_action_add_callback(wxGLCanvas* canvas, void* callback); - static void register_action_delete_callback(wxGLCanvas* canvas, void* callback); - static void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); - static void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); - static void register_action_more_callback(wxGLCanvas* canvas, void* callback); - static void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); - static void register_action_split_callback(wxGLCanvas* canvas, void* callback); - static void register_action_cut_callback(wxGLCanvas* canvas, void* callback); - static void register_action_settings_callback(wxGLCanvas* canvas, void* callback); - static void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); - static void register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback); - - static std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); - static std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); - - static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx); - static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx); - - static void reload_scene(wxGLCanvas* canvas, bool force); - - static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors); - static void load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors); - - static void reset_legend_texture(); + static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas); static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index cd88e0f0a..32c43aab1 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -1,6 +1,7 @@ #include "AboutDialog.hpp" +#include "I18N.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { @@ -41,7 +42,7 @@ AboutDialog::AboutDialog() // logo wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp)); - hsizer->Add(logo, 1, wxALIGN_CENTRE_VERTICAL | wxEXPAND | wxTOP | wxBOTTOM, 35); + hsizer->Add(logo, 1, wxEXPAND | wxTOP | wxBOTTOM, 35); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); #ifdef __WXMSW__ diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 4ba4cebb1..28e6e1018 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -1,5 +1,5 @@ -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" #include "AppConfig.hpp" #include @@ -18,7 +18,6 @@ #include #include - namespace Slic3r { static const std::string VENDOR_PREFIX = "vendor:"; @@ -129,7 +128,6 @@ void AppConfig::save() { // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r - const auto path = config_path(); std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); @@ -165,7 +163,6 @@ void AppConfig::save() c.close(); rename_file(path_pid, path); - m_dirty = false; } @@ -252,6 +249,7 @@ void AppConfig::reset_selections() if (it != m_storage.end()) { it->second.erase("print"); it->second.erase("filament"); + it->second.erase("sla_print"); it->second.erase("sla_material"); it->second.erase("printer"); m_dirty = true; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 99997e390..891e5f0dc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -1,32 +1,40 @@ #include "BackgroundSlicingProcess.hpp" -#include "GUI.hpp" +#include "GUI_App.hpp" -#include +#include #include #include +// For zipped archive creation +#include +#include +#include + // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. -#include "../../libslic3r/Print.hpp" -#include "../../libslic3r/Utils.hpp" -#include "../../libslic3r/GCode/PostProcessor.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/GCode/PostProcessor.hpp" //#undef NDEBUG #include #include +#include +#include #include +#include +#include +#include #include namespace Slic3r { -namespace GUI { - extern wxPanel *g_wxPlater; -}; - BackgroundSlicingProcess::BackgroundSlicingProcess() { - m_temp_output_path = wxStandardPaths::Get().GetTempDir().utf8_str().data(); - m_temp_output_path += (boost::format(".%1%.gcode") % get_current_pid()).str(); + boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data()); + temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str(); + m_temp_output_path = temp_path.string(); } BackgroundSlicingProcess::~BackgroundSlicingProcess() @@ -36,8 +44,125 @@ BackgroundSlicingProcess::~BackgroundSlicingProcess() boost::nowide::remove(m_temp_output_path.c_str()); } +bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech) +{ + bool changed = false; + if (m_print == nullptr || m_print->technology() != tech) { + if (m_print != nullptr) + this->reset(); + switch (tech) { + case ptFFF: m_print = m_fff_print; break; + case ptSLA: m_print = m_sla_print; break; + } + changed = true; + } + assert(m_print != nullptr); + return changed; +} + +PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const +{ + return m_print->technology(); +} + +static bool isspace(int ch) +{ + return std::isspace(ch) != 0; +} + +// This function may one day be merged into the Print, but historically the print was separated +// from the G-code generator. +void BackgroundSlicingProcess::process_fff() +{ + assert(m_print == m_fff_print); + m_print->process(); + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); + if (this->set_step_started(bspsGCodeFinalize)) { + if (! m_export_path.empty()) { + //FIXME localize the messages + // Perform the final post-processing of the export path by applying the print statistics over the file name. + std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); + if (copy_file(m_temp_output_path, export_path) != 0) + throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); + m_print->set_status(95, "Running post-processing scripts"); + run_post_process_scripts(export_path, m_fff_print->config()); + m_print->set_status(100, "G-code file exported to " + export_path); + } else { + m_print->set_status(100, "Slicing complete"); + } + this->set_step_done(bspsGCodeFinalize); + } +} + +// Pseudo type for specializing LayerWriter trait class +struct SLAZipFmt {}; + +// The implementation of creating zipped archives with wxWidgets +template<> class LayerWriter { + wxFileName fpath; + wxFFileOutputStream zipfile; + wxZipOutputStream zipstream; + wxStdOutputStream pngstream; + +public: + + inline LayerWriter(const std::string& zipfile_path): + fpath(zipfile_path), + zipfile(zipfile_path), + zipstream(zipfile), + pngstream(zipstream) + { + if(!is_ok()) + throw std::runtime_error("Cannot create zip file."); + } + + ~LayerWriter() { + // In case of an error (disk space full) zipstream destructor would + // crash. + pngstream.clear(); + zipstream.CloseEntry(); + } + + inline void next_entry(const std::string& fname) { + zipstream.PutNextEntry(fname); + } + + inline std::string get_name() const { + return fpath.GetName().ToStdString(); + } + + template inline LayerWriter& operator<<(const T& arg) { + pngstream << arg; return *this; + } + + bool is_ok() const { + return pngstream.good() && zipstream.IsOk() && zipfile.IsOk(); + } + + inline void close() { + zipstream.Close(); + zipfile.Close(); + } +}; + +void BackgroundSlicingProcess::process_sla() +{ + assert(m_print == m_sla_print); + m_print->process(); + if (this->set_step_started(bspsGCodeFinalize)) { + if (! m_export_path.empty()) { + m_sla_print->export_raster(m_export_path); + m_print->set_status(100, "Zip file exported to " + m_export_path); + } + this->set_step_done(bspsGCodeFinalize); + } +} + void BackgroundSlicingProcess::thread_proc() { + assert(m_print != nullptr); + assert(m_print == m_fff_print || m_print == m_sla_print); std::unique_lock lck(m_mutex); // Let the caller know we are ready to run the background processing task. m_state = STATE_IDLE; @@ -57,18 +182,12 @@ void BackgroundSlicingProcess::thread_proc() std::string error; try { assert(m_print != nullptr); - m_print->process(); - if (! m_print->canceled()) { - wxQueueEvent(GUI::g_wxPlater, new wxCommandEvent(m_event_sliced_id)); - m_print->export_gcode(m_temp_output_path, m_gcode_preview_data); - if (! m_print->canceled() && ! m_output_path.empty()) { - if (copy_file(m_temp_output_path, m_output_path) != 0) - throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); - m_print->set_status(95, "Running post-processing scripts"); - run_post_process_scripts(m_output_path, m_print->config()); - } - } - } catch (CanceledException &ex) { + switch(m_print->technology()) { + case ptFFF: this->process_fff(); break; + case ptSLA: this->process_sla(); break; + default: m_print->process(); break; + } + } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); } catch (std::exception &ex) { @@ -78,10 +197,14 @@ void BackgroundSlicingProcess::thread_proc() } lck.lock(); m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED; - wxCommandEvent evt(m_event_finished_id); - evt.SetString(error); - evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0)); - wxQueueEvent(GUI::g_wxPlater, evt.Clone()); + if (m_print->cancel_status() != Print::CANCELED_INTERNAL) { + // Only post the canceled event, if canceled by user. + // Don't post the canceled event, if canceled from Print::apply(). + wxCommandEvent evt(m_event_finished_id); + evt.SetString(error); + evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0)); + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); + } m_print->restart(); lck.unlock(); // Let the UI thread wake up if it is waiting for the background task to finish. @@ -93,6 +216,15 @@ void BackgroundSlicingProcess::thread_proc() // End of the background processing thread. The UI thread should join m_thread now. } +void BackgroundSlicingProcess::thread_proc_safe() +{ + try { + this->thread_proc(); + } catch (...) { + wxTheApp->OnUnhandledException(); + } +} + void BackgroundSlicingProcess::join_background_thread() { std::unique_lock lck(m_mutex); @@ -113,11 +245,15 @@ void BackgroundSlicingProcess::join_background_thread() bool BackgroundSlicingProcess::start() { + if (m_print->empty()) + // The print is empty (no object in Model, or all objects are out of the print bed). + return false; + std::unique_lock lck(m_mutex); if (m_state == STATE_INITIAL) { // The worker thread is not running yet. Start it. assert(! m_thread.joinable()); - m_thread = std::thread([this]{this->thread_proc();}); + m_thread = std::thread([this]{this->thread_proc_safe();}); // Wait until the worker thread is ready to execute the background processing task. m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; }); } @@ -128,6 +264,7 @@ bool BackgroundSlicingProcess::start() if (! this->idle()) throw std::runtime_error("Cannot start a background task, the worker thread is not idle."); m_state = STATE_STARTED; + m_print->set_cancel_callback([this](){ this->stop_internal(); }); lck.unlock(); m_condition.notify_one(); return true; @@ -135,33 +272,152 @@ bool BackgroundSlicingProcess::start() bool BackgroundSlicingProcess::stop() { + // m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it. std::unique_lock lck(m_mutex); if (m_state == STATE_INITIAL) { - this->m_output_path.clear(); +// this->m_export_path.clear(); return false; } - assert(this->running()); +// assert(this->running()); if (m_state == STATE_STARTED || m_state == STATE_RUNNING) { m_print->cancel(); // Wait until the background processing stops by being canceled. m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; }); // In the "Canceled" state. Reset the state to "Idle". m_state = STATE_IDLE; + m_print->set_cancel_callback([](){}); } else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) { // In the "Finished" or "Canceled" state. Reset the state to "Idle". m_state = STATE_IDLE; + m_print->set_cancel_callback([](){}); } - this->m_output_path.clear(); +// this->m_export_path.clear(); return true; } +bool BackgroundSlicingProcess::reset() +{ + bool stopped = this->stop(); + this->reset_export(); + m_print->clear(); + this->invalidate_all_steps(); + return stopped; +} + +// To be called by Print::apply() through the Print::m_cancel_callback to stop the background +// processing before changing any data of running or finalized milestones. +// This function shall not trigger any UI update through the wxWidgets event. +void BackgroundSlicingProcess::stop_internal() +{ + // m_print->state_mutex() shall be held. Unfortunately there is no interface to test for it. + if (m_state == STATE_IDLE) + // The worker thread is waiting on m_mutex/m_condition for wake up. The following lock of the mutex would block. + return; + std::unique_lock lck(m_mutex); + assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED); + if (m_state == STATE_STARTED || m_state == STATE_RUNNING) { + // At this point of time the worker thread may be blocking on m_print->state_mutex(). + // Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up, + // it throws the CanceledException(). + m_print->cancel_internal(); + // Allow the worker thread to wake up if blocking on a milestone. + m_print->state_mutex().unlock(); + // Wait until the background processing stops by being canceled. + m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; }); + // Lock it back to be in a consistent state. + m_print->state_mutex().lock(); + } + // In the "Canceled" state. Reset the state to "Idle". + m_state = STATE_IDLE; + m_print->set_cancel_callback([](){}); +} + +bool BackgroundSlicingProcess::empty() const +{ + assert(m_print != nullptr); + return m_print->empty(); +} + +std::string BackgroundSlicingProcess::validate() +{ + assert(m_print != nullptr); + return m_print->validate(); +} + // Apply config over the print. Returns false, if the new config values caused any of the already // processed steps to be invalidated, therefore the task will need to be restarted. -bool BackgroundSlicingProcess::apply_config(const DynamicPrintConfig &config) +Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const DynamicPrintConfig &config) { - this->stop(); - bool invalidated = m_print->apply_config(config); + assert(m_print != nullptr); + assert(config.opt_enum("printer_technology") == m_print->technology()); + Print::ApplyStatus invalidated = m_print->apply(model, config); return invalidated; } +// Set the output path of the G-code. +void BackgroundSlicingProcess::schedule_export(const std::string &path) +{ + assert(m_export_path.empty()); + if (! m_export_path.empty()) + return; + + // Guard against entering the export step before changing the export path. + tbb::mutex::scoped_lock lock(m_print->state_mutex()); + this->invalidate_step(bspsGCodeFinalize); + m_export_path = path; +} + +void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job) +{ + assert(m_export_path.empty()); + if (! m_export_path.empty()) + return; + + const boost::filesystem::path path = boost::filesystem::temp_directory_path() + / boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode"); + + // Guard against entering the export step before changing the export path. + tbb::mutex::scoped_lock lock(m_print->state_mutex()); + this->invalidate_step(bspsGCodeFinalize); + m_export_path = path.string(); + m_upload_job = std::move(upload_job); +} + +void BackgroundSlicingProcess::reset_export() +{ + assert(! this->running()); + if (! this->running()) { + m_export_path.clear(); + // invalidate_step expects the mutex to be locked. + tbb::mutex::scoped_lock lock(m_print->state_mutex()); + this->invalidate_step(bspsGCodeFinalize); + } +} + +bool BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step) +{ + return m_step_state.set_started(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); }); +} + +void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step) +{ + m_step_state.set_done(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); }); +} + +bool BackgroundSlicingProcess::is_step_done(BackgroundSlicingProcessStep step) const +{ + return m_step_state.is_done(step, m_print->state_mutex()); +} + +bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step) +{ + bool invalidated = m_step_state.invalidate(step, [this](){ this->stop_internal(); }); + return invalidated; +} + +bool BackgroundSlicingProcess::invalidate_all_steps() +{ + return m_step_state.invalidate_all([this](){ this->stop_internal(); }); +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index cc7a6db30..222ed147e 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -6,11 +6,34 @@ #include #include +#include + +#include "libslic3r/Print.hpp" +#include "slic3r/Utils/PrintHost.hpp" + namespace Slic3r { class DynamicPrintConfig; class GCodePreviewData; -class Print; +class Model; +class SLAPrint; + +class SlicingStatusEvent : public wxEvent +{ +public: + SlicingStatusEvent(wxEventType eventType, int winid, const PrintBase::SlicingStatus &status) : + wxEvent(winid, eventType), status(std::move(status)) {} + virtual wxEvent *Clone() const { return new SlicingStatusEvent(*this); } + + PrintBase::SlicingStatus status; +}; + +wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); + +// Print step IDs for keeping track of the print state. +enum BackgroundSlicingProcessStep { + bspsGCodeFinalize, bspsCount, +}; // Support for the GUI background processing (Slicing and G-code generation). // As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits. @@ -21,27 +44,56 @@ public: // Stop the background processing and finalize the bacgkround processing thread, remove temp files. ~BackgroundSlicingProcess(); - void set_print(Print *print) { m_print = print; } + void set_fff_print(Print *print) { m_fff_print = print; } + void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } // The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished // and the background processing will transition into G-code export. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. - void set_sliced_event(int event_id) { m_event_sliced_id = event_id; } + void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; } // The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_finished_event(int event_id) { m_event_finished_id = event_id; } - // Set the output path of the G-code. - void set_output_path(const std::string &path) { m_output_path = path; } + // Activate either m_fff_print or m_sla_print. + // Return true if changed. + bool select_technology(PrinterTechnology tech); + + // Get the currently active printer technology. + PrinterTechnology current_printer_technology() const; + // Get the current print. It is either m_fff_print or m_sla_print. + const PrintBase* current_print() const { return m_print; } + const Print* fff_print() const { return m_fff_print; } + const SLAPrint* sla_print() const { return m_sla_print; } + // Start the background processing. Returns false if the background processing was already running. bool start(); // Cancel the background processing. Returns false if the background processing was not running. // A stopped background processing may be restarted with start(). bool stop(); + // Cancel the background processing and reset the print. Returns false if the background processing was not running. + // Useful when the Model or configuration is being changed drastically. + bool reset(); // Apply config over the print. Returns false, if the new config values caused any of the already // processed steps to be invalidated, therefore the task will need to be restarted. - bool apply_config(const DynamicPrintConfig &config); + Print::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config); + // After calling apply, the empty() call will report whether there is anything to slice. + bool empty() const; + // Validate the print. Returns an empty string if valid, returns an error message if invalid. + // Call validate before calling start(). + std::string validate(); + + // Set the export path of the G-code. + // Once the path is set, the G-code + void schedule_export(const std::string &path); + // Set print host upload job data to be enqueued to the PrintHostJobQueue + // after current print slicing is complete + void schedule_upload(Slic3r::PrintHostJob upload_job); + // Clear m_export_path. + void reset_export(); + // Once the G-code export is scheduled, the apply() methods will do nothing. + bool is_export_scheduled() const { return ! m_export_path.empty(); } enum State { // m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet). @@ -62,16 +114,42 @@ public: State state() const { return m_state; } bool idle() const { return m_state == STATE_IDLE; } bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; } + // Returns true if the last step of the active print was finished with success. + // The "finished" flag is reset by the apply() method, if it changes the state of the print. + // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), + // and it does not account for the OctoPrint scheduling. + bool finished() const { return m_print->finished(); } private: void thread_proc(); + void thread_proc_safe(); void join_background_thread(); + // To be called by Print::apply() through the Print::m_cancel_callback to stop the background + // processing before changing any data of running or finalized milestones. + // This function shall not trigger any UI update through the wxWidgets event. + void stop_internal(); - Print *m_print = nullptr; + // Helper to wrap the FFF slicing & G-code generation. + void process_fff(); + + // Temporary: for mimicking the fff file export behavior with the raster output + void process_sla(); + + // Currently active print. It is one of m_fff_print and m_sla_print. + PrintBase *m_print = nullptr; + // Non-owned pointers to Print instances. + Print *m_fff_print = nullptr; + SLAPrint *m_sla_print = nullptr; // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; - std::string m_output_path; + // Output path provided by the user. The output path may be set even if the slicing is running, + // but once set, it cannot be re-set. + std::string m_export_path; + // Print host upload job to schedule after slicing is complete, used by schedule_upload(), + // empty by default (ie. no upload to schedule) + PrintHostJob m_upload_job; // Thread, on which the background processing is executed. The thread will always be present // and ready to execute the slicing process. std::thread m_thread; @@ -80,10 +158,20 @@ private: std::condition_variable m_condition; State m_state = STATE_INITIAL; + PrintState m_step_state; + mutable tbb::mutex m_step_state_mutex; + bool set_step_started(BackgroundSlicingProcessStep step); + void set_step_done(BackgroundSlicingProcessStep step); + bool is_step_done(BackgroundSlicingProcessStep step) const; + bool invalidate_step(BackgroundSlicingProcessStep step); + bool invalidate_all_steps(); + // If the background processing stop was requested, throw CanceledException. + void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } + // wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue. - int m_event_sliced_id = 0; + int m_event_slicing_completed_id = 0; // wxWidgets command ID to be sent to the platter to inform that the task finished. - int m_event_finished_id = 0; + int m_event_finished_id = 0; }; }; // namespace Slic3r diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index e04f2b370..dbc9e36d8 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -1,12 +1,14 @@ #include "BedShapeDialog.hpp" +#include +#include #include #include -#include -#include "Polygon.hpp" -#include "BoundingBox.hpp" -#include -#include "Model.hpp" + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Polygon.hpp" + #include "boost/nowide/iostream.hpp" #include @@ -28,7 +30,7 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) main_sizer->SetSizeHints(this); // needed to actually free memory - this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e){ + this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e) { EndModal(wxID_OK); Destroy(); })); @@ -115,14 +117,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){ +ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) +{ auto panel = new wxPanel(m_shape_options_book); ConfigOptionsGroupShp optgroup; optgroup = std::make_shared(panel, _(L("Settings"))); optgroup->label_width = 100; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { update_shape(); }; @@ -234,14 +237,15 @@ void BedShapePanel::update_shape() Vec2d rect_origin(Vec2d::Zero()); try{ rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); } - catch (const std::exception &e){ + catch (const std::exception & /* e */) { return; } - try{ + try { rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin")); } - catch (const std::exception &e){ - return;} + catch (const std::exception & /* e */) { + return; + } auto x = rect_size(0); auto y = rect_size(1); @@ -269,7 +273,7 @@ void BedShapePanel::update_shape() try{ diameter = boost::any_cast(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter")); } - catch (const std::exception &e){ + catch (const std::exception & /* e */) { return; } if (diameter == 0.0) return ; @@ -277,7 +281,7 @@ void BedShapePanel::update_shape() auto twopi = 2 * PI; auto edges = 60; std::vector points; - for (size_t i = 1; i <= 60; ++i){ + for (size_t i = 1; i <= 60; ++i) { auto angle = i * twopi / edges; points.push_back(Vec2d(r*cos(angle), r*sin(angle))); } @@ -291,14 +295,8 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); - std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" }; - wxString MODEL_WILDCARD; - for (auto file_type: file_types) - MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|"; - auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog->ShowModal() != wxID_OK) { dialog->Destroy(); return; diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index d8ba5a912..84752c3fc 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -5,7 +5,7 @@ #include "OptionsGroup.hpp" #include "2DBed.hpp" - +#include "I18N.hpp" #include #include @@ -22,8 +22,8 @@ class BedShapePanel : public wxPanel std::vector m_optgroups; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY){} - ~BedShapePanel(){} + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} + ~BedShapePanel() {} void build_panel(ConfigOptionPoints* default_pt); @@ -42,8 +42,8 @@ class BedShapeDialog : public wxDialog BedShapePanel* m_panel; public: BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")), - wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){} - ~BedShapeDialog(){ } + wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} + ~BedShapeDialog() {} void build_dialog(ConfigOptionPoints* default_pt); std::vector GetValue() { return m_panel->GetValue(); } diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 93853458e..16baa1629 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -97,7 +97,9 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg if (bmp->GetWidth() > 0) { if (bmp->GetDepth() == 32) { wxAlphaPixelData data(*const_cast(bmp)); - data.UseAlpha(); + //FIXME The following method is missing from wxWidgets 3.1.1. + // It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha(). + //data.UseAlpha(); if (data) { for (int r = 0; r < bmp->GetHeight(); ++ r) { wxAlphaPixelData::Iterator src(data); diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index bec9a7ad2..0cb70d28b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -6,8 +6,8 @@ #include #endif -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" #include "GUI.hpp" diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 11cfea642..68e353ebe 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -12,14 +12,15 @@ #include #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/Bonjour.hpp" - namespace Slic3r { -struct BonjourReplyEvent : public wxEvent +class BonjourReplyEvent : public wxEvent { +public: BonjourReply reply; BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) : diff --git a/src/slic3r/GUI/ButtonsDescription.cpp b/src/slic3r/GUI/ButtonsDescription.cpp index 5739fc90e..fb4c24286 100644 --- a/src/slic3r/GUI/ButtonsDescription.cpp +++ b/src/slic3r/GUI/ButtonsDescription.cpp @@ -5,6 +5,8 @@ #include #include "GUI.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" namespace Slic3r { namespace GUI { @@ -36,15 +38,15 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic // Text color description auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value"))); - sys_label->SetForegroundColour(get_label_clr_sys()); - auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_sys()); + sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys()); + auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_sys()); sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e) { sys_label->SetForegroundColour(sys_colour->GetColour()); sys_label->Refresh(); })); size_t t= 0; - while (t < 3){ + while (t < 3) { grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); ++t; } @@ -53,8 +55,8 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset"))); - mod_label->SetForegroundColour(get_label_clr_modified()); - auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_modified()); + mod_label->SetForegroundColour(wxGetApp().get_label_clr_modified()); + auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_modified()); mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e) { mod_label->SetForegroundColour(mod_colour->GetColour()); @@ -70,8 +72,8 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) { - set_label_clr_sys(sys_colour->GetColour()); - set_label_clr_modified(mod_colour->GetColour()); + wxGetApp().set_label_clr_sys(sys_colour->GetColour()); + wxGetApp().set_label_clr_modified(mod_colour->GetColour()); EndModal(wxID_OK); }); diff --git a/src/slic3r/GUI/ButtonsDescription.hpp b/src/slic3r/GUI/ButtonsDescription.hpp index 4858eaaea..81baaf191 100644 --- a/src/slic3r/GUI/ButtonsDescription.hpp +++ b/src/slic3r/GUI/ButtonsDescription.hpp @@ -14,7 +14,7 @@ class ButtonsDescription : public wxDialog t_icon_descriptions* m_icon_descriptions; public: ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions); - ~ButtonsDescription(){} + ~ButtonsDescription() {} }; diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index efcbf05bd..dc396895b 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -1,9 +1,10 @@ #include "ConfigSnapshotDialog.hpp" +#include "I18N.hpp" #include "../Config/Snapshot.hpp" #include "../Utils/Time.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/Utils.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index c024f7f13..028c0011c 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -382,7 +382,7 @@ void PageVendors::on_variant_checked() PageFirmware::PageFirmware(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))), - gcode_opt(print_config_def.options["gcode_flavor"]), + gcode_opt(*print_config_def.get("gcode_flavor")), gcode_picker(nullptr) { append_text(_(L("Choose the type of firmware used by your printer."))); @@ -442,13 +442,13 @@ PageDiameters::PageDiameters(ConfigWizard *parent) : { spin_nozzle->SetDigits(2); spin_nozzle->SetIncrement(0.1); - const auto &def_nozzle = print_config_def.options["nozzle_diameter"]; + const auto &def_nozzle = *print_config_def.get("nozzle_diameter"); auto *default_nozzle = dynamic_cast(def_nozzle.default_value); spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); spin_filam->SetDigits(2); spin_filam->SetIncrement(0.25); - const auto &def_filam = print_config_def.options["filament_diameter"]; + const auto &def_filam = *print_config_def.get("filament_diameter"); auto *default_filam = dynamic_cast(def_filam.default_value); spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); @@ -492,13 +492,13 @@ PageTemperatures::PageTemperatures(ConfigWizard *parent) : spin_bed(new wxSpinCtrlDouble(this, wxID_ANY)) { spin_extr->SetIncrement(5.0); - const auto &def_extr = print_config_def.options["temperature"]; + const auto &def_extr = *print_config_def.get("temperature"); spin_extr->SetRange(def_extr.min, def_extr.max); auto *default_extr = dynamic_cast(def_extr.default_value); spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); spin_bed->SetIncrement(5.0); - const auto &def_bed = print_config_def.options["bed_temperature"]; + const auto &def_bed = *print_config_def.get("bed_temperature"); spin_bed->SetRange(def_bed.min, def_bed.max); auto *default_bed = dynamic_cast(def_bed.default_value); spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); @@ -833,8 +833,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : topsizer->AddSpacer(INDEX_MARGIN); topsizer->Add(p->hscroll, 1, wxEXPAND); - p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back"))); - p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >"))); + p->btn_prev = new wxButton(this, wxID_ANY, _(L("< &Back"))); + p->btn_next = new wxButton(this, wxID_ANY, _(L("&Next >"))); p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); p->btn_cancel = new wxButton(this, wxID_CANCEL); p->btnsizer->AddStretchSpacer(); diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index 73fce7cd2..6ac1bcbd8 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -39,7 +39,7 @@ private: struct priv; std::unique_ptr p; - friend class ConfigWizardPage; + friend struct ConfigWizardPage; }; diff --git a/src/slic3r/GUI/Event.hpp b/src/slic3r/GUI/Event.hpp new file mode 100644 index 000000000..429ef99b0 --- /dev/null +++ b/src/slic3r/GUI/Event.hpp @@ -0,0 +1,66 @@ +#ifndef slic3r_Events_hpp_ +#define slic3r_Events_hpp_ + +#include +#include + + +namespace Slic3r { + +namespace GUI { + + +struct SimpleEvent : public wxEvent +{ + SimpleEvent(wxEventType type, wxObject* origin = nullptr) : wxEvent(0, type) + { + m_propagationLevel = wxEVENT_PROPAGATE_MAX; + SetEventObject(origin); + } + + virtual wxEvent* Clone() const + { + return new SimpleEvent(GetEventType(), GetEventObject()); + } +}; + +template struct ArrayEvent : public wxEvent +{ + std::array data; + + ArrayEvent(wxEventType type, std::array data, wxObject* origin = nullptr) + : wxEvent(0, type), data(std::move(data)) + { + m_propagationLevel = wxEVENT_PROPAGATE_MAX; + SetEventObject(origin); + } + + virtual wxEvent* Clone() const + { + return new ArrayEvent(GetEventType(), data, GetEventObject()); + } +}; +template struct ArrayEvent : public wxEvent +{ + T data; + + ArrayEvent(wxEventType type, T data, wxObject* origin = nullptr) + : wxEvent(0, type), data(std::move(data)) + { + m_propagationLevel = wxEVENT_PROPAGATE_MAX; + SetEventObject(origin); + } + + virtual wxEvent* Clone() const + { + return new ArrayEvent(GetEventType(), data, GetEventObject()); + } +}; + +template using Event = ArrayEvent; + + +} +} + +#endif // slic3r_Events_hpp_ diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 4d6fa3476..b80ed42dd 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1,240 +1,271 @@ -#include "GUI.hpp"//"slic3r_gui.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" #include "Field.hpp" -//#include +#include "libslic3r/PrintConfig.hpp" + #include #include #include -#include "PrintConfig.hpp" #include namespace Slic3r { namespace GUI { - wxString double_to_string(double const value) +wxString double_to_string(double const value, const int max_precision /*= 4*/) +{ + if (value - int(value) == 0) + return wxString::Format(_T("%i"), int(value)); + + int precision = max_precision; + for (size_t p = 1; p < max_precision; p++) { - if (value - int(value) == 0) - return wxString::Format(_T("%i"), int(value)); - else { - int precision = 4; - for (size_t p = 1; p < 4; p++) - { - double cur_val = pow(10, p)*value; - if (cur_val - int(cur_val) == 0) { - precision = p; - break; - } - } - return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); + double cur_val = pow(10, p)*value; + if (cur_val - int(cur_val) == 0) { + precision = p; + break; } } + return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); +} - void Field::PostInitialize(){ - auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) { - m_Undo_btn->SetBackgroundColour(color); - m_Undo_to_sys_btn->SetBackgroundColour(color); +void Field::PostInitialize() +{ + auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) { + m_Undo_btn->SetBackgroundColour(color); + m_Undo_to_sys_btn->SetBackgroundColour(color); + } + m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_initial_value(); })); + m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_sys_value(); })); + + //set default bitmap + wxBitmap bmp; + bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); + set_undo_bitmap(&bmp); + set_undo_to_sys_bitmap(&bmp); + + switch (m_opt.type) + { + case coPercents: + case coFloats: + case coStrings: + case coBools: + case coInts: { + auto tag_pos = m_opt_id.find("#"); + if (tag_pos != std::string::npos) + m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size())); + break; + } + default: + break; + } + + BUILD(); +} + +void Field::on_kill_focus(wxEvent& event) +{ + // Without this, there will be nasty focus bugs on Windows. + // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all + // non-command events to allow the default handling to take place." + event.Skip(); + // call the registered function if it is available + if (m_on_kill_focus!=nullptr) + m_on_kill_focus(m_opt_id); +} + +void Field::on_set_focus(wxEvent& event) +{ + // to allow the default behavior + event.Skip(); + // call the registered function if it is available + if (m_on_set_focus!=nullptr) + m_on_set_focus(m_opt_id); +} + +void Field::on_change_field() +{ +// std::cerr << "calling Field::_on_change \n"; + if (m_on_change != nullptr && !m_disable_change_event) + m_on_change(m_opt_id, get_value()); +} + +void Field::on_back_to_initial_value() +{ + if (m_back_to_initial_value != nullptr && m_is_modified_value) + m_back_to_initial_value(m_opt_id); +} + +void Field::on_back_to_sys_value() +{ + if (m_back_to_sys_value != nullptr && m_is_nonsys_value) + m_back_to_sys_value(m_opt_id); +} + +wxString Field::get_tooltip_text(const wxString& default_string) +{ + wxString tooltip_text(""); + wxString tooltip = _(m_opt.tooltip); + if (tooltip.length() > 0) + tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + + _(L("parameter name")) + "\t: " + m_opt_id; + + return tooltip_text; +} + +bool Field::is_matched(const std::string& string, const std::string& pattern) +{ + std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl + return std::regex_match(string, regex_pattern); +} + +void Field::get_value_by_opt_type(wxString& str) +{ + switch (m_opt.type) { + case coInt: + m_value = wxAtoi(str); + break; + case coPercent: + case coPercents: + case coFloats: + case coFloat:{ + if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%') + str.RemoveLast(); + else if (!str.IsEmpty() && str.Last() == '%') { + wxString label = m_Label->GetLabel(); + if (label.Last() == '\n') label.RemoveLast(); + while (label.Last() == ' ') label.RemoveLast(); + if (label.Last() == ':') label.RemoveLast(); + show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label)); + set_value(double_to_string(m_opt.min), true); + m_value = double(m_opt.min); + break; } - m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); - m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); - - //set default bitmap - wxBitmap bmp; - bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG); - set_undo_bitmap(&bmp); - set_undo_to_sys_bitmap(&bmp); - - switch (m_opt.type) + double val; + if(!str.ToCDouble(&val)) { - case coPercents: - case coFloats: - case coStrings: - case coBools: - case coInts: { - auto tag_pos = m_opt_id.find("#"); - if (tag_pos != std::string::npos) - m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size())); - break; + show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits"))); + set_value(double_to_string(val), true); } - default: - break; - } - - BUILD(); - } - - void Field::on_kill_focus(wxEvent& event) { - // Without this, there will be nasty focus bugs on Windows. - // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all - // non-command events to allow the default handling to take place." - event.Skip(); - // call the registered function if it is available - if (m_on_kill_focus!=nullptr) - m_on_kill_focus(); - } - void Field::on_change_field() - { -// std::cerr << "calling Field::_on_change \n"; - if (m_on_change != nullptr && !m_disable_change_event) - m_on_change(m_opt_id, get_value()); - } - - void Field::on_back_to_initial_value(){ - if (m_back_to_initial_value != nullptr && m_is_modified_value) - m_back_to_initial_value(m_opt_id); - } - - void Field::on_back_to_sys_value(){ - if (m_back_to_sys_value != nullptr && m_is_nonsys_value) - m_back_to_sys_value(m_opt_id); - } - - wxString Field::get_tooltip_text(const wxString& default_string) - { - wxString tooltip_text(""); - wxString tooltip = _(m_opt.tooltip); - if (tooltip.length() > 0) - tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + - _(L("parameter name")) + "\t: " + m_opt_id; - - return tooltip_text; - } - - bool Field::is_matched(const std::string& string, const std::string& pattern) - { - std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl - return std::regex_match(string, regex_pattern); - } - - void Field::get_value_by_opt_type(wxString& str) - { - switch (m_opt.type){ - case coInt: - m_value = wxAtoi(str); - break; - case coPercent: - case coPercents: - case coFloats: - case coFloat:{ - if (m_opt.type == coPercent && str.Last() == '%') - str.RemoveLast(); - else if (str.Last() == '%') { - wxString label = m_Label->GetLabel(); - if (label.Last() == '\n') label.RemoveLast(); - while (label.Last() == ' ') label.RemoveLast(); - if (label.Last() == ':') label.RemoveLast(); - show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label)); - set_value(double_to_string(m_opt.min), true); - m_value = double(m_opt.min); - break; - } - double val; - if(!str.ToCDouble(&val)) - { - show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits"))); - set_value(double_to_string(val), true); - } - if (m_opt.min > val || val > m_opt.max) - { - show_error(m_parent, _(L("Input value is out of range"))); - if (m_opt.min > val) val = m_opt.min; - if (val > m_opt.max) val = m_opt.max; - set_value(double_to_string(val), true); - } - m_value = val; - break; } - case coString: - case coStrings: - case coFloatOrPercent: - m_value = str.ToStdString(); - break; - default: - break; - } - } - - void TextCtrl::BUILD() { - auto size = wxSize(wxDefaultSize); - if (m_opt.height >= 0) size.SetHeight(m_opt.height); - if (m_opt.width >= 0) size.SetWidth(m_opt.width); - - wxString text_value = wxString(""); - - switch (m_opt.type) { - case coFloatOrPercent: + if (m_opt.min > val || val > m_opt.max) { - text_value = double_to_string(m_opt.default_value->getFloat()); - if (static_cast(m_opt.default_value)->percent) - text_value += "%"; - break; + show_error(m_parent, _(L("Input value is out of range"))); + if (m_opt.min > val) val = m_opt.min; + if (val > m_opt.max) val = m_opt.max; + set_value(double_to_string(val), true); } - case coPercent: - { - text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat())); + m_value = val; + break; } + case coString: + case coStrings: + case coFloatOrPercent: + m_value = str.ToStdString(); + break; + default: + break; + } +} + +bool TextCtrl::is_defined_input_value() const +{ + if (static_cast(window)->GetValue().empty() && m_opt.type != coString && m_opt.type != coStrings) + return false; + return true; +} + +void TextCtrl::BUILD() { + auto size = wxSize(wxDefaultSize); + if (m_opt.height >= 0) size.SetHeight(m_opt.height); + if (m_opt.width >= 0) size.SetWidth(m_opt.width); + + wxString text_value = wxString(""); + + switch (m_opt.type) { + case coFloatOrPercent: + { + text_value = double_to_string(m_opt.default_value->getFloat()); + if (static_cast(m_opt.default_value)->percent) text_value += "%"; - break; - } - case coPercents: - case coFloats: - case coFloat: - { - double val = m_opt.type == coFloats ? - static_cast(m_opt.default_value)->get_at(m_opt_idx) : - m_opt.type == coFloat ? - m_opt.default_value->getFloat() : - static_cast(m_opt.default_value)->get_at(m_opt_idx); - text_value = double_to_string(val); - break; - } - case coString: - text_value = static_cast(m_opt.default_value)->value; - break; - case coStrings: - { - const ConfigOptionStrings *vec = static_cast(m_opt.default_value); - if (vec == nullptr || vec->empty()) break; //for the case of empty default value - text_value = vec->get_at(m_opt_idx); - break; - } - default: - break; - } + break; + } + case coPercent: + { + text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat())); + text_value += "%"; + break; + } + case coPercents: + case coFloats: + case coFloat: + { + double val = m_opt.type == coFloats ? + static_cast(m_opt.default_value)->get_at(m_opt_idx) : + m_opt.type == coFloat ? + m_opt.default_value->getFloat() : + static_cast(m_opt.default_value)->get_at(m_opt_idx); + text_value = double_to_string(val); + break; + } + case coString: + text_value = static_cast(m_opt.default_value)->value; + break; + case coStrings: + { + const ConfigOptionStrings *vec = static_cast(m_opt.default_value); + if (vec == nullptr || vec->empty()) break; //for the case of empty default value + text_value = vec->get_at(m_opt_idx); + break; + } + default: + break; + } - auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, (m_opt.multiline ? wxTE_MULTILINE : 0)); + const long style = m_opt.multiline ? wxTE_MULTILINE : 0; + auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); - temp->SetToolTip(get_tooltip_text(text_value)); - - temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event) - { - //! to allow the default handling - event.Skip(); - //! eliminating the g-code pop up text description - bool flag = false; + temp->SetToolTip(get_tooltip_text(text_value)); + + temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId()); + + temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event) + { + //! to allow the default handling + event.Skip(); + //! eliminating the g-code pop up text description + bool flag = false; #ifdef __WXGTK__ - // I have no idea why, but on GTK flag works in other way - flag = true; + // I have no idea why, but on GTK flag works in other way + flag = true; #endif // __WXGTK__ - temp->GetToolTip()->Enable(flag); - }), temp->GetId()); + temp->GetToolTip()->Enable(flag); + }), temp->GetId()); + temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) + { #if !defined(__WXGTK__) - temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) - { - e.Skip();// on_kill_focus(e); - temp->GetToolTip()->Enable(true); - }), temp->GetId()); + e.Skip(); + temp->GetToolTip()->Enable(true); #endif // __WXGTK__ - - temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) - { +// if (!is_defined_input_value()) + if (is_defined_input_value()) + on_change_field(); + else + on_kill_focus(e); + }), temp->GetId()); + /* + temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) + { #ifdef __WXGTK__ - if (bChangedValueEvent) + if (bChangedValueEvent) #endif //__WXGTK__ - on_change_field(); - }), temp->GetId()); + if(is_defined_input_value()) + on_change_field(); + }), temp->GetId()); #ifdef __WXGTK__ // to correct value updating on GTK we should: @@ -243,37 +274,37 @@ namespace Slic3r { namespace GUI { temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); #endif //__WXGTK__ - - // select all text using Ctrl+A - temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) - { - if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) - temp->SetSelection(-1, -1); //select all - event.Skip(); - })); - - // recast as a wxWindow to fit the calling convention - window = dynamic_cast(temp); - } - - boost::any& TextCtrl::get_value() +*/ + // select all text using Ctrl+A + temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) { - wxString ret_str = static_cast(window)->GetValue(); - get_value_by_opt_type(ret_str); + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + temp->SetSelection(-1, -1); //select all + event.Skip(); + })); - return m_value; - } + // recast as a wxWindow to fit the calling convention + window = dynamic_cast(temp); +} - void TextCtrl::enable() { dynamic_cast(window)->Enable(); dynamic_cast(window)->SetEditable(true); } - void TextCtrl::disable() { dynamic_cast(window)->Disable(); dynamic_cast(window)->SetEditable(false); } +boost::any& TextCtrl::get_value() +{ + wxString ret_str = static_cast(window)->GetValue(); + get_value_by_opt_type(ret_str); + + return m_value; +} + +void TextCtrl::enable() { dynamic_cast(window)->Enable(); dynamic_cast(window)->SetEditable(true); } +void TextCtrl::disable() { dynamic_cast(window)->Disable(); dynamic_cast(window)->SetEditable(false); } #ifdef __WXGTK__ - void TextCtrl::change_field_value(wxEvent& event) - { - if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP) - on_change_field(); - event.Skip(); - }; +void TextCtrl::change_field_value(wxEvent& event) +{ + if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP) + on_change_field(); + event.Skip(); +}; #endif //__WXGTK__ void CheckBox::BUILD() { @@ -346,7 +377,15 @@ void SpinCtrl::BUILD() { 0, min_val, max_val, default_value); // temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); -// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId()); + temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) + { + if (tmp_value < 0) + on_kill_focus(e); + else { + e.Skip(); + on_change_field(); + } + }), temp->GetId()); temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value @@ -357,7 +396,8 @@ void SpinCtrl::BUILD() { std::string value = e.GetString().utf8_str().data(); if (is_matched(value, "^\\d+$")) tmp_value = std::stoi(value); - on_change_field(); + else tmp_value = -9999; +// on_change_field(); // # We don't reset tmp_value here because _on_change might put callbacks // # in the CallAfter queue, and we want the tmp value to be available from // # them as well. @@ -383,10 +423,10 @@ void Choice::BUILD() { // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); - if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()){ + if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()) { } else{ - for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){ + for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels) { const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el; temp->Append(str); } @@ -401,7 +441,7 @@ void Choice::BUILD() { void Choice::set_selection() { wxString text_value = wxString(""); - switch (m_opt.type){ + switch (m_opt.type) { case coFloat: case coPercent: { double val = m_opt.default_value->getFloat(); @@ -480,12 +520,12 @@ void Choice::set_value(const boost::any& value, bool change_event) { m_disable_change_event = !change_event; - switch (m_opt.type){ + switch (m_opt.type) { case coInt: case coFloat: case coPercent: case coString: - case coStrings:{ + case coStrings: { wxString text_value; if (m_opt.type == coInt) text_value = wxString::Format(_T("%i"), int(boost::any_cast(value))); @@ -503,11 +543,11 @@ void Choice::set_value(const boost::any& value, bool change_event) dynamic_cast(window)->SetSelection(idx); break; } - case coEnum:{ + case coEnum: { int val = boost::any_cast(value); if (m_opt_id.compare("top_fill_pattern") == 0 || m_opt_id.compare("bottom_fill_pattern") == 0 ) { - if (!m_opt.enum_values.empty()){ + if (!m_opt.enum_values.empty()) { std::string key; t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); for (auto it : map_names) { @@ -599,7 +639,7 @@ boost::any& Choice::get_value() int ret_enum = static_cast(window)->GetSelection(); if (m_opt_id.compare("top_fill_pattern") == 0 || m_opt_id.compare("bottom_fill_pattern") == 0 ) { - if (!m_opt.enum_values.empty()){ + if (!m_opt.enum_values.empty()) { std::string key = m_opt.enum_values[ret_enum]; t_config_enum_values map_names = ConfigOptionEnum::get_enum_values(); int value = map_names.at(key); @@ -629,6 +669,8 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("infill_dense_algo") == 0) m_value = static_cast(ret_enum); + else if (m_opt_id.compare("display_orientation") == 0) + m_value = static_cast(ret_enum); } return m_value; @@ -640,18 +682,25 @@ void ColourPicker::BUILD() if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); + // Validate the color + wxString clr_str(static_cast(m_opt.default_value)->get_at(m_opt_idx)); + wxColour clr(clr_str); + if (! clr.IsOk()) { + clr = wxTransparentColour; + } + auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); - + // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); - temp->SetToolTip(get_tooltip_text(clr)); + temp->SetToolTip(get_tooltip_text(clr_str)); } -boost::any& ColourPicker::get_value(){ +boost::any& ColourPicker::get_value() +{ // boost::any m_value; auto colour = static_cast(window)->GetColour(); @@ -738,7 +787,7 @@ void StaticText::BUILD() wxString legend(static_cast(m_opt.default_value)->value); auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size); - temp->SetFont(bold_font()); + temp->SetFont(wxGetApp().bold_font()); // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); @@ -770,7 +819,7 @@ void SliderCtrl::BUILD() temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) { - if (!m_disable_change_event){ + if (!m_disable_change_event) { int val = boost::any_cast(get_value()); m_textctrl->SetLabel(wxString::Format("%d", val)); on_change_field(); @@ -779,7 +828,7 @@ void SliderCtrl::BUILD() m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { std::string value = e.GetString().utf8_str().data(); - if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){ + if (is_matched(value, "^-?\\d+(\\.\\d*)?$")) { m_disable_change_event = true; m_slider->SetValue(stoi(value)*m_scale); m_disable_change_event = false; diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index c38658e2b..0097d3ec0 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -13,12 +13,11 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/Utils.hpp" -//#include "slic3r_gui.hpp" #include "GUI.hpp" -#include "Utils.hpp" #ifdef __WXMSW__ #define wxMSW true @@ -30,11 +29,11 @@ namespace Slic3r { namespace GUI { class Field; using t_field = std::unique_ptr; -using t_kill_focus = std::function; -using t_change = std::function; +using t_kill_focus = std::function; +using t_change = std::function; using t_back_to_init = std::function; -wxString double_to_string(double const value); +wxString double_to_string(double const value, const int max_precision = 4); class MyButton : public wxButton { @@ -54,10 +53,13 @@ public: virtual bool AcceptsFocusFromKeyboard() const { return false; } + void set_as_hidden() { + Hide(); + hidden = true; + } + virtual bool Show(bool show = true) override { - if (!show) - hidden = true; - return wxButton::Show(!hidden); + return wxButton::Show(hidden ? false : show); } }; @@ -74,6 +76,8 @@ protected: //! in another case we can't unfocused control at all void on_kill_focus(wxEvent& event); /// Call the attached on_change method. + void on_set_focus(wxEvent& event); + /// Call the attached on_change method. void on_change_field(); /// Call the attached m_back_to_initial_value method. void on_back_to_initial_value(); @@ -87,6 +91,9 @@ public: /// Function object to store callback passed in from owning object. t_kill_focus m_on_kill_focus {nullptr}; + /// Function object to store callback passed in from owning object. + t_kill_focus m_on_set_focus {nullptr}; + /// Function object to store callback passed in from owning object. t_change m_on_change {nullptr}; @@ -126,6 +133,7 @@ public: Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; + virtual ~Field() {} /// If you don't know what you are getting back, check both methods for nullptr. virtual wxSizer* getSizer() { return nullptr; } @@ -136,7 +144,7 @@ public: /// Factory method for generating new derived classes. template - static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) // interface for creating shared objects + static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id)// interface for creating shared objects { auto p = Slic3r::make_unique(parent, opt, id); p->PostInitialize(); @@ -218,7 +226,7 @@ protected: // current value boost::any m_value; - + friend class OptionsGroup; }; @@ -258,7 +266,8 @@ public: } boost::any& get_value() override; - + bool is_defined_input_value() const ; + virtual void enable(); virtual void disable(); virtual wxWindow* getWindow() { return window; } diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index d5ac64d90..418d1a3c9 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -13,6 +13,7 @@ #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" #include "GUI.hpp" +#include "I18N.hpp" #include "MsgDialog.hpp" #include "../Utils/HexFile.hpp" #include "../Utils/Serial.hpp" @@ -35,6 +36,7 @@ #include #include #include +#include "GUI_App.hpp" namespace fs = boost::filesystem; @@ -213,7 +215,7 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks) modal_response = wxID_NONE; txt_stdout->Clear(); set_txt_status(label_status_flashing); - txt_status->SetForegroundColour(GUI::get_label_clr_modified()); + txt_status->SetForegroundColour(GUI::wxGetApp().get_label_clr_modified()); port_picker->Disable(); btn_rescan->Disable(); hex_picker->Disable(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6c83073da..33938b8e8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,27 +1,39 @@ +#include "slic3r/GUI/GLGizmo.hpp" #include "GLCanvas3D.hpp" -#include "../../admesh/stl.h" -#include "../../libslic3r/libslic3r.h" -#include "../../slic3r/GUI/3DScene.hpp" -#include "../../slic3r/GUI/GLShader.hpp" -#include "../../slic3r/GUI/GUI.hpp" -#include "../../slic3r/GUI/PresetBundle.hpp" -#include "../../slic3r/GUI/GLGizmo.hpp" -#include "../../libslic3r/ClipperUtils.hpp" -#include "../../libslic3r/PrintConfig.hpp" -#include "../../libslic3r/GCode/PreviewData.hpp" +#include "admesh/stl.h" +#include "libslic3r/libslic3r.h" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/BackgroundSlicingProcess.hpp" +#include "slic3r/GUI/GLShader.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +//#include "slic3r/GUI/GLGizmo.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "I18N.hpp" #include #include -#include #include #include #include #include +#include +#include // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. -#include "../../libslic3r/Print.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" + +#include "wxExtensions.hpp" #include #include @@ -48,14 +60,18 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f }; static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f; static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; +static const float GIZMO_RESET_BUTTON_HEIGHT = 22.0f; +static const float GIZMO_RESET_BUTTON_WIDTH = 70.f; static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; -static const float DEFAULT_BG_COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; -static const float ERROR_BG_COLOR[3] = { 144.0f / 255.0f, 49.0f / 255.0f, 10.0f / 255.0f }; +static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; +static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; +static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; +static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; namespace Slic3r { namespace GUI { @@ -259,8 +275,13 @@ GLCanvas3D::Camera::Camera() , zoom(1.0f) , phi(45.0f) // , distance(0.0f) +#if !ENABLE_CONSTRAINED_CAMERA_TARGET , target(0.0, 0.0, 0.0) +#endif // !ENABLE_CONSTRAINED_CAMERA_TARGET , m_theta(45.0f) +#if ENABLE_CONSTRAINED_CAMERA_TARGET + , m_target(Vec3d::Zero()) +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET { } @@ -278,16 +299,32 @@ std::string GLCanvas3D::Camera::get_type_as_string() const }; } -float GLCanvas3D::Camera::get_theta() const -{ - return m_theta; -} - void GLCanvas3D::Camera::set_theta(float theta) { m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); } +#if ENABLE_CONSTRAINED_CAMERA_TARGET +void GLCanvas3D::Camera::set_target(const Vec3d& target, GLCanvas3D& canvas) +{ + m_target = target; + m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0)); + m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1)); + m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2)); + if (!m_target.isApprox(target)) + canvas.viewport_changed(); +} + +void GLCanvas3D::Camera::set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas) +{ + if (m_scene_box != box) + { + m_scene_box = box; + canvas.viewport_changed(); + } +} +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET + GLCanvas3D::Bed::Bed() : m_type(Custom) { @@ -295,7 +332,7 @@ GLCanvas3D::Bed::Bed() bool GLCanvas3D::Bed::is_prusa() const { - return (m_type == MK2) || (m_type == MK3); + return (m_type == MK2) || (m_type == MK3) || (m_type == SL1); } bool GLCanvas3D::Bed::is_custom() const @@ -314,6 +351,7 @@ bool GLCanvas3D::Bed::set_shape(const Pointfs& shape) if (m_shape == shape && m_type == new_type) // No change, no need to update the UI. return false; + m_shape = shape; m_type = new_type; @@ -356,12 +394,17 @@ void GLCanvas3D::Bed::render(float theta) const { case MK2: { - _render_mk2(theta); + _render_prusa("mk2", theta); break; } case MK3: { - _render_mk3(theta); + _render_prusa("mk3", theta); + break; + } + case SL1: + { + _render_prusa("sl1", theta); break; } default: @@ -410,7 +453,7 @@ void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& } // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped - Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON))); + Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, (float)SCALED_EPSILON))); // append bed contours Lines contour_lines = to_lines(poly); @@ -424,28 +467,37 @@ GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const { EType type = Custom; - const PresetBundle* bundle = get_preset_bundle(); + auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr) { const Preset* curr = &bundle->printers.get_selected_preset(); while (curr != nullptr) { - if (curr->config.has("bed_shape") && _are_equal(m_shape, dynamic_cast(curr->config.option("bed_shape"))->values)) - { - if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research")) - { - if (boost::contains(curr->name, "MK2")) - { - type = MK2; - break; - } - else if (boost::contains(curr->name, "MK3")) - { - type = MK3; - break; - } - } - } + if (curr->config.has("bed_shape")) + { + if (boost::contains(curr->name, "SL1")) + { + //FIXME add a condition on the size of the print bed? + type = SL1; + break; + } + else if (_are_equal(m_shape, dynamic_cast(curr->config.option("bed_shape"))->values)) + { + if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research")) + { + if (boost::contains(curr->name, "MK2")) + { + type = MK2; + break; + } + else if (boost::contains(curr->name, "MK3")) + { + type = MK3; + break; + } + } + } + } curr = bundle->printers.get_preset_parent(*curr); } @@ -454,9 +506,9 @@ GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const return type; } -void GLCanvas3D::Bed::_render_mk2(float theta) const +void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const { - std::string filename = resources_dir() + "/icons/bed/mk2_top.png"; + std::string filename = resources_dir() + "/icons/bed/" + key + "_top.png"; if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) { if (!m_top_texture.load_from_file(filename, true)) @@ -466,7 +518,7 @@ void GLCanvas3D::Bed::_render_mk2(float theta) const } } - filename = resources_dir() + "/icons/bed/mk2_bottom.png"; + filename = resources_dir() + "/icons/bed/" + key + "_bottom.png"; if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) { if (!m_bottom_texture.load_from_file(filename, true)) @@ -476,36 +528,6 @@ void GLCanvas3D::Bed::_render_mk2(float theta) const } } - _render_prusa(theta); -} - -void GLCanvas3D::Bed::_render_mk3(float theta) const -{ - std::string filename = resources_dir() + "/icons/bed/mk3_top.png"; - if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) - { - if (!m_top_texture.load_from_file(filename, true)) - { - _render_custom(); - return; - } - } - - filename = resources_dir() + "/icons/bed/mk3_bottom.png"; - if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) - { - if (!m_bottom_texture.load_from_file(filename, true)) - { - _render_custom(); - return; - } - } - - _render_prusa(theta); -} - -void GLCanvas3D::Bed::_render_prusa(float theta) const -{ unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { @@ -559,7 +581,7 @@ void GLCanvas3D::Bed::_render_custom() const ::glEnableClientState(GL_VERTEX_ARRAY); - ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f); + ::glColor4f(0.35f, 0.35f, 0.35f, 0.4f); ::glNormal3d(0.0f, 0.0f, 1.0f); ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); @@ -596,7 +618,8 @@ bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2) } GLCanvas3D::Axes::Axes() - : origin(0, 0, 0), length(0.0f) + : origin(Vec3d::Zero()) + , length(0.0f) { } @@ -611,11 +634,11 @@ void GLCanvas3D::Axes::render(bool depth_test) const ::glBegin(GL_LINES); // draw line for x axis ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2)); + ::glVertex3dv(origin.data()); ::glVertex3f((GLfloat)origin(0) + length, (GLfloat)origin(1), (GLfloat)origin(2)); // draw line for y axis ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2)); + ::glVertex3dv(origin.data()); ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1) + length, (GLfloat)origin(2)); ::glEnd(); // draw line for Z axis @@ -625,75 +648,11 @@ void GLCanvas3D::Axes::render(bool depth_test) const ::glBegin(GL_LINES); ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2)); + ::glVertex3dv(origin.data()); ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2) + length); ::glEnd(); } -GLCanvas3D::CuttingPlane::CuttingPlane() - : m_z(-1.0f) -{ -} - -bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons) -{ - m_z = z; - - // grow slices in order to display them better - ExPolygons expolygons = offset_ex(polygons, scale_(0.1)); - Lines lines = to_lines(expolygons); - return m_lines.set_from_lines(lines, m_z); -} - -void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const -{ - _render_plane(bb); - _render_contour(); -} - -void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const -{ - if (m_z >= 0.0f) - { - ::glDisable(GL_CULL_FACE); - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - float margin = 20.0f; - float min_x = bb.min(0) - margin; - float max_x = bb.max(0) + margin; - float min_y = bb.min(1) - margin; - float max_y = bb.max(1) + margin; - - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, m_z); - ::glVertex3f(max_x, min_y, m_z); - ::glVertex3f(max_x, max_y, m_z); - ::glVertex3f(min_x, max_y, m_z); - ::glEnd(); - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); - } -} - -void GLCanvas3D::CuttingPlane::_render_contour() const -{ - ::glEnableClientState(GL_VERTEX_ARRAY); - - if (m_z >= 0.0f) - { - unsigned int lines_vcount = m_lines.get_vertices_count(); - - ::glLineWidth(2.0f); - ::glColor3f(0.0f, 0.0f, 0.0f); - ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices()); - ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount); - } - - ::glDisableClientState(GL_VERTEX_ARRAY); -} GLCanvas3D::Shader::Shader() : m_shader(nullptr) @@ -1085,16 +1044,16 @@ const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX) GLCanvas3D::Mouse::Drag::Drag() : start_position_2D(Invalid_2D_Point) , start_position_3D(Invalid_3D_Point) - , volume_center_offset(0, 0, 0) - , move_with_shift(false) , move_volume_idx(-1) - , gizmo_volume_idx(-1) { } GLCanvas3D::Mouse::Mouse() : dragging(false) + , left_down(false) , position(DBL_MAX, DBL_MAX) + , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) + , ignore_up_event(false) { } @@ -1118,9 +1077,1433 @@ bool GLCanvas3D::Mouse::is_start_position_3D_defined() const return (drag.start_position_3D != Drag::Invalid_3D_Point); } -const float GLCanvas3D::Gizmos::OverlayTexturesScale = 0.75f; -const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f * OverlayTexturesScale; -const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayTexturesScale; +#if ENABLE_MODELVOLUME_TRANSFORM +GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache() + : position(Vec3d::Zero()) + , rotation(Vec3d::Zero()) + , scaling_factor(Vec3d::Ones()) + , mirror(Vec3d::Ones()) + , rotation_matrix(Transform3d::Identity()) + , scale_matrix(Transform3d::Identity()) + , mirror_matrix(Transform3d::Identity()) +{ +} + +GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) +{ + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +} + +GLCanvas3D::Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) + : m_volume(volume_transform) + , m_instance(instance_transform) +{ +} +#else +GLCanvas3D::Selection::VolumeCache::VolumeCache() + : m_position(Vec3d::Zero()) + , m_rotation(Vec3d::Zero()) + , m_scaling_factor(Vec3d::Ones()) +{ + m_rotation_matrix = Transform3d::Identity(); + m_scale_matrix = Transform3d::Identity(); +} + +GLCanvas3D::Selection::VolumeCache::VolumeCache(const Vec3d& position, const Vec3d& rotation, const Vec3d& scaling_factor) + : m_position(position) + , m_rotation(rotation) + , m_scaling_factor(scaling_factor) +{ + m_rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), m_rotation); + m_scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_scaling_factor); +} +#endif // ENABLE_MODELVOLUME_TRANSFORM + +GLCanvas3D::Selection::Selection() + : m_volumes(nullptr) + , m_model(nullptr) + , m_mode(Instance) + , m_type(Empty) + , m_valid(false) + , m_bounding_box_dirty(true) +{ +} + +void GLCanvas3D::Selection::set_volumes(GLVolumePtrs* volumes) +{ + m_volumes = volumes; + _update_valid(); +} + +void GLCanvas3D::Selection::set_model(Model* model) +{ + m_model = model; + _update_valid(); +} + +void GLCanvas3D::Selection::add(unsigned int volume_idx, bool as_single_selection) +{ + if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + return; + + const GLVolume* volume = (*m_volumes)[volume_idx]; + // wipe tower is already selected + if (is_wipe_tower() && volume->is_wipe_tower) + return; + + // resets the current list if needed + bool needs_reset = as_single_selection; + needs_reset |= volume->is_wipe_tower; + needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; + needs_reset |= !is_modifier() && volume->is_modifier; + needs_reset |= is_modifier() && !volume->is_modifier; + + if (needs_reset) + clear(); + + if (volume->is_modifier) + m_mode = Volume; + + switch (m_mode) + { + case Volume: + { + if (volume->volume_idx() >= 0 && (is_empty() || (volume->instance_idx() == get_instance_idx()))) + _add_volume(volume_idx); + + break; + } + case Instance: + { + _add_instance(volume->object_idx(), volume->instance_idx()); + break; + } +#if !ENABLE_MODELVOLUME_TRANSFORM + case Object: + { + _add_object(volume->object_idx()); + break; + } +#endif // !ENABLE_MODELVOLUME_TRANSFORM + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::remove(unsigned int volume_idx) +{ + if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + return; + + GLVolume* volume = (*m_volumes)[volume_idx]; + + switch (m_mode) + { + case Volume: + { + _remove_volume(volume_idx); + break; + } + case Instance: + { + _remove_instance(volume->object_idx(), volume->instance_idx()); + break; + } +#if !ENABLE_MODELVOLUME_TRANSFORM + case Object: + { + _remove_object(volume->object_idx()); + break; + } +#endif // !ENABLE_MODELVOLUME_TRANSFORM + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::add_object(unsigned int object_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + _add_object(object_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::remove_object(unsigned int object_idx) +{ + if (!m_valid) + return; + + _remove_object(object_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + _add_instance(object_idx, instance_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + if (!m_valid) + return; + + _remove_instance(object_idx, instance_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Volume; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + { + if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) + _add_volume(i); + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) +{ + if (!m_valid) + return; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + _remove_volume(i); + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::add_all() +{ + if (!m_valid) + return; + + m_mode = Instance; + clear(); + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if (!(*m_volumes)[i]->is_wipe_tower) + _add_volume(i); + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::clear() +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + (*m_volumes)[i]->selected = false; + } + + m_list.clear(); + + _update_type(); + m_bounding_box_dirty = true; +} + +// Update the selection based on the map from old indices to new indices after m_volumes changed. +// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. +void GLCanvas3D::Selection::volumes_changed(const std::vector &map_volume_old_to_new) +{ + assert(m_valid); + + // 1) Update the selection set. + IndicesList list_new; + std::vector> model_instances; + for (unsigned int idx : m_list) { + if (map_volume_old_to_new[idx] != size_t(-1)) { + unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; + list_new.insert(new_idx); + if (m_mode == Instance) { + // Save the object_idx / instance_idx pair of selected old volumes, + // so we may add the newly added volumes of the same object_idx / instance_idx pair + // to the selection. + const GLVolume *volume = (*m_volumes)[new_idx]; + model_instances.emplace_back(volume->object_idx(), volume->instance_idx()); + } + } + } + m_list = std::move(list_new); + + if (! model_instances.empty()) { + // Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair + // to the selection. + assert(m_mode == Instance); + sort_remove_duplicates(model_instances); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) { + const GLVolume* volume = (*m_volumes)[i]; + for (const std::pair &model_instance : model_instances) + if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second) + this->_add_volume(i); + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +bool GLCanvas3D::Selection::is_single_full_instance() const +{ + if (m_type == SingleFullInstance) + return true; + + if (m_type == SingleFullObject) + return get_instance_idx() != -1; + + if (m_list.empty() || m_volumes->empty()) + return false; + + int object_idx = m_valid ? get_object_idx() : -1; + if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + return false; + + int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); + + std::set volumes_idxs; + for (unsigned int i : m_list) + { + const GLVolume* v = (*m_volumes)[i]; + int volume_idx = v->volume_idx(); + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx) && (volume_idx >= 0)) + volumes_idxs.insert(volume_idx); + } + + return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); +} + +bool GLCanvas3D::Selection::is_from_single_object() const +{ + int idx = get_object_idx(); + return (0 <= idx) && (idx < 1000); +} + +int GLCanvas3D::Selection::get_object_idx() const +{ + return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; +} + +int GLCanvas3D::Selection::get_instance_idx() const +{ + if (m_cache.content.size() == 1) + { + const InstanceIdxsList& idxs = m_cache.content.begin()->second; + if (idxs.size() == 1) + return *idxs.begin(); + } + + return -1; +} + +const GLCanvas3D::Selection::InstanceIdxsList& GLCanvas3D::Selection::get_instance_idxs() const +{ + assert(m_cache.content.size() == 1); + return m_cache.content.begin()->second; +} + +const GLVolume* GLCanvas3D::Selection::get_volume(unsigned int volume_idx) const +{ + return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; +} + +const BoundingBoxf3& GLCanvas3D::Selection::get_bounding_box() const +{ + if (m_bounding_box_dirty) + _calc_bounding_box(); + + return m_bounding_box; +} + +void GLCanvas3D::Selection::start_dragging() +{ + if (!m_valid) + return; + + _set_caches(); +} + +void GLCanvas3D::Selection::translate(const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) + { + Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + } + else if (m_mode == Instance) + (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); +#else + (*m_volumes)[i]->set_offset(m_cache.volumes_data[i].get_position() + displacement); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + if (is_single_full_instance()) +#if ENABLE_WORLD_ROTATIONS + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); + (*m_volumes)[i]->set_instance_rotation(new_rotation); + } +#else +#if ENABLE_MODELVOLUME_TRANSFORM + (*m_volumes)[i]->set_instance_rotation(rotation); +#else + (*m_volumes)[i]->set_rotation(rotation); +#endif // ENABLE_MODELVOLUME_TRANSFORM +#endif // ENABLE_WORLD_ROTATIONS +#if ENABLE_MODELVOLUME_TRANSFORM + else if (is_single_volume() || is_single_modifier()) +#if ENABLE_WORLD_ROTATIONS + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Transform3d& inst_m = m_cache.volumes_data[i].get_instance_rotation_matrix(); + Vec3d new_rotation = Geometry::extract_euler_angles(inst_m.inverse() * m * inst_m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + (*m_volumes)[i]->set_volume_rotation(new_rotation); + } +#else + (*m_volumes)[i]->set_volume_rotation(rotation); +#endif // ENABLE_WORLD_ROTATIONS +#endif // ENABLE_MODELVOLUME_TRANSFORM + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); +#if ENABLE_MODELVOLUME_TRANSFORM + if (m_mode == Instance) + { + // extracts rotations from the composed transformation + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); + if (!local) + (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + (*m_volumes)[i]->set_instance_rotation(new_rotation); + } + else if (m_mode == Volume) + { + // extracts rotations from the composed transformation + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (!local) + { + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + } + (*m_volumes)[i]->set_volume_rotation(new_rotation); + } +#else + // extracts rotations from the composed transformation + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_rotation_matrix()); + (*m_volumes)[i]->set_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_position() - m_cache.dragging_center)); + (*m_volumes)[i]->set_rotation(new_rotation); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::flattening_rotate(const Vec3d& normal) +{ + // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out + // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. + // The function assumes that is_from_single_object() holds. + + if (!m_valid) + return; + + for (unsigned int i : m_list) + { +#if ENABLE_MODELVOLUME_TRANSFORM + Transform3d wst = m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_volume_scale_matrix(); + Vec3d scaling_factor = Vec3d(1./wst(0,0), 1./wst(1,1), 1./wst(2,2)); + + Vec3d rotation = Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_volume_rotation_matrix()); + Vec3d transformed_normal = Geometry::assemble_transform(Vec3d::Zero(), rotation, scaling_factor) * normal; + transformed_normal.normalize(); + + Vec3d axis = transformed_normal(2) > 0.999f ? Vec3d(1., 0., 0.) : Vec3d(transformed_normal.cross(Vec3d(0., 0., -1.))); + axis.normalize(); + + Transform3d extra_rotation = Transform3d::Identity(); + extra_rotation.rotate(Eigen::AngleAxisd(acos(-transformed_normal(2)), axis)); + + Vec3d new_rotation = Geometry::extract_euler_angles(extra_rotation * m_cache.volumes_data[i].get_instance_rotation_matrix() ); + (*m_volumes)[i]->set_instance_rotation(new_rotation); +#else + Transform3d wst = m_cache.volumes_data[i].get_scale_matrix() * m_cache.volumes_data[i].get_scale_matrix(); + Vec3d scaling_factor = Vec3d(1. / wst(0, 0), 1. / wst(1, 1), 1. / wst(2, 2)); + + Vec3d rotation = Geometry::extract_euler_angles(m_cache.volumes_data[i].get_rotation_matrix() * m_cache.volumes_data[i].get_rotation_matrix()); + Vec3d transformed_normal = Geometry::assemble_transform(Vec3d::Zero(), rotation, scaling_factor) * normal; + transformed_normal.normalize(); + + Vec3d axis = transformed_normal(2) > 0.999f ? Vec3d(1., 0., 0.) : Vec3d(transformed_normal.cross(Vec3d(0., 0., -1.))); + axis.normalize(); + + Transform3d extra_rotation = Transform3d::Identity(); + extra_rotation.rotate(Eigen::AngleAxisd(acos(-transformed_normal(2)), axis)); + + Vec3d new_rotation = Geometry::extract_euler_angles(extra_rotation * m_cache.volumes_data[i].get_rotation_matrix()); + (*m_volumes)[i]->set_rotation(new_rotation); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::scale(const Vec3d& scale, bool local) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + if (is_single_full_instance()) +#if ENABLE_MODELVOLUME_TRANSFORM + (*m_volumes)[i]->set_instance_scaling_factor(scale); +#else + (*m_volumes)[i]->set_scaling_factor(scale); +#endif // ENABLE_MODELVOLUME_TRANSFORM +#if ENABLE_MODELVOLUME_TRANSFORM + else if (is_single_volume() || is_single_modifier()) + (*m_volumes)[i]->set_volume_scaling_factor(scale); +#endif // ENABLE_MODELVOLUME_TRANSFORM + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); +#if ENABLE_MODELVOLUME_TRANSFORM + if (m_mode == Instance) + { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (!local) + (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + (*m_volumes)[i]->set_instance_scaling_factor(new_scale); + } + else if (m_mode == Volume) + { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (!local) + { + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + } + (*m_volumes)[i]->set_volume_scaling_factor(new_scale); + } +#else + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + (*m_volumes)[i]->set_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_position() - m_cache.dragging_center)); + (*m_volumes)[i]->set_scaling_factor(new_scale); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + +#if ENABLE_ENSURE_ON_BED_WHILE_SCALING + _ensure_on_bed(); +#endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::mirror(Axis axis) +{ + if (!m_valid) + return; + + bool single_full_instance = is_single_full_instance(); + + for (unsigned int i : m_list) + { + if (single_full_instance) +#if ENABLE_MODELVOLUME_TRANSFORM + (*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis)); + else if (m_mode == Volume) + (*m_volumes)[i]->set_volume_mirror(axis, -(*m_volumes)[i]->get_volume_mirror(axis)); +#else + (*m_volumes)[i]->set_mirror(axis, -(*m_volumes)[i]->get_mirror(axis)); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::translate(unsigned int object_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_instance_offset(v->get_instance_offset() + displacement); +#else + v->set_offset(v->get_offset() + displacement); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx) + continue; + +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_instance_offset(v->get_instance_offset() + displacement); +#else + v->set_offset(v->get_offset() + displacement); +#endif // ENABLE_MODELVOLUME_TRANSFORM + done.insert(j); + } + } + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_instance_offset(v->get_instance_offset() + displacement); +#else + v->set_offset(v->get_offset() + displacement); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->instance_idx() != instance_idx)) + continue; + +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_instance_offset(v->get_instance_offset() + displacement); +#else + v->set_offset(v->get_offset() + displacement); +#endif // ENABLE_MODELVOLUME_TRANSFORM + done.insert(j); + } + } + + m_bounding_box_dirty = true; +} + +void GLCanvas3D::Selection::erase() +{ + if (!m_valid) + return; + + if (is_single_full_object()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); + else if (is_multiple_full_object()) + { + std::vector items; + items.reserve(m_cache.content.size()); + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + items.emplace_back(ItemType::itObject, it->first, 0); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_single_full_instance()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); + else if (is_multiple_full_instance()) + { + std::set> instances_idxs; + for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) + { + for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) + { + instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); + } + } + + std::vector items; + items.reserve(instances_idxs.size()); + for (const std::pair& i : instances_idxs) + { + items.emplace_back(ItemType::itInstance, i.first, i.second); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_mixed()) + { + std::set items_set; + std::map volumes_in_obj; + + for (auto i : m_list) { + const auto gl_vol = (*m_volumes)[i]; + const auto glv_obj_idx = gl_vol->object_idx(); + const auto model_object = m_model->objects[glv_obj_idx]; + + if (model_object->instances.size() == 1) { + if (model_object->volumes.size() == 1) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else { + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); + volumes_in_obj[glv_obj_idx] = ++idx; + } + continue; + } + + const auto glv_ins_idx = gl_vol->instance_idx(); + + for (auto obj_ins : m_cache.content) { + if (obj_ins.first == glv_obj_idx) { + if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { + if (obj_ins.second.size() == model_object->instances.size()) + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + else + items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); + + break; + } + } + } + } + + std::vector items; + items.reserve(items_set.size()); + for (const ItemForDelete& i : items_set) { + if (i.type == ItemType::itVolume ) { + const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); + if (vol_in_obj_cnt == m_model->objects[i.obj_idx]->volumes.size()) { + if (i.sub_obj_idx == vol_in_obj_cnt - 1) + items.emplace_back(ItemType::itObject, i.obj_idx, 0); + continue; + } + } + items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else + { + std::set> volumes_idxs; + for (unsigned int i : m_list) + { + const GLVolume* v = (*m_volumes)[i]; + // Only remove volumes associated with ModelVolumes from the object list. + // Temporary meshes (SLA supports or pads) are not managed by the object list. + if (v->volume_idx() >= 0) + volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); + } + + std::vector items; + items.reserve(volumes_idxs.size()); + for (const std::pair& v : volumes_idxs) + { + items.emplace_back(ItemType::itVolume, v.first, v.second); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } +} + +void GLCanvas3D::Selection::render() const +{ + if (is_empty()) + return; + + // render cumulative bounding box of selected volumes + _render_selected_volumes(); + _render_synchronized_volumes(); +} + +void GLCanvas3D::Selection::_update_valid() +{ + m_valid = (m_volumes != nullptr) && (m_model != nullptr); +} + +void GLCanvas3D::Selection::_update_type() +{ + m_cache.content.clear(); + m_type = Mixed; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int obj_idx = volume->object_idx(); + int inst_idx = volume->instance_idx(); + ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); + if (obj_it == m_cache.content.end()) + obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; + + obj_it->second.insert(inst_idx); + } + + bool requires_disable = false; + + if (!m_valid) + m_type = Invalid; + else + { + if (m_list.empty()) + m_type = Empty; + else if (m_list.size() == 1) + { + const GLVolume* first = (*m_volumes)[*m_list.begin()]; + if (first->is_wipe_tower) + m_type = WipeTower; + else if (first->is_modifier) + { + m_type = SingleModifier; + requires_disable = true; + } + else + { + const ModelObject* model_object = m_model->objects[first->object_idx()]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + if (volumes_count * instances_count == 1) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (volumes_count == 1) // instances_count > 1 + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + m_type = SingleVolume; + requires_disable = true; + } + } + } + else + { + if (m_cache.content.size() == 1) // single object + { + const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; + unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int sla_volumes_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + unsigned int volumes_count = model_volumes_count + sla_volumes_count; + unsigned int instances_count = (unsigned int)model_object->instances.size(); + unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); + if (volumes_count * instances_count == (unsigned int)m_list.size()) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (selected_instances_count == 1) + { + if (volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + unsigned int modifiers_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + ++modifiers_count; + } + + if (modifiers_count == 0) + { + m_type = MultipleVolume; + requires_disable = true; + } + else if (modifiers_count == (unsigned int)m_list.size()) + { + m_type = MultipleModifier; + requires_disable = true; + } + } + } + else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) + { + m_type = MultipleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + } + else + { + int sels_cntr = 0; + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + const ModelObject* model_object = m_model->objects[it->first]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + sels_cntr += volumes_count * instances_count; + } + if (sels_cntr == (unsigned int)m_list.size()) + { + m_type = MultipleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + } + } + } + + int object_idx = get_object_idx(); + int instance_idx = get_instance_idx(); + for (GLVolume* v : *m_volumes) + { + v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; + } + +#if ENABLE_SELECTION_DEBUG_OUTPUT + std::cout << "Selection: "; + std::cout << "mode: "; + switch (m_mode) + { + case Volume: + { + std::cout << "Volume"; + break; + } + case Instance: + { + std::cout << "Instance"; + break; + } + } + + std::cout << " - type: "; + + switch (m_type) + { + case Invalid: + { + std::cout << "Invalid" << std::endl; + break; + } + case Empty: + { + std::cout << "Empty" << std::endl; + break; + } + case WipeTower: + { + std::cout << "WipeTower" << std::endl; + break; + } + case SingleModifier: + { + std::cout << "SingleModifier" << std::endl; + break; + } + case MultipleModifier: + { + std::cout << "MultipleModifier" << std::endl; + break; + } + case SingleVolume: + { + std::cout << "SingleVolume" << std::endl; + break; + } + case MultipleVolume: + { + std::cout << "MultipleVolume" << std::endl; + break; + } + case SingleFullObject: + { + std::cout << "SingleFullObject" << std::endl; + break; + } + case MultipleFullObject: + { + std::cout << "MultipleFullObject" << std::endl; + break; + } + case SingleFullInstance: + { + std::cout << "SingleFullInstance" << std::endl; + break; + } + case MultipleFullInstance: + { + std::cout << "MultipleFullInstance" << std::endl; + break; + } + case Mixed: + { + std::cout << "Mixed" << std::endl; + break; + } + } +#endif // ENABLE_SELECTION_DEBUG_OUTPUT +} + +void GLCanvas3D::Selection::_set_caches() +{ + m_cache.volumes_data.clear(); + for (unsigned int i : m_list) + { + const GLVolume* v = (*m_volumes)[i]; +#if ENABLE_MODELVOLUME_TRANSFORM + m_cache.volumes_data.emplace(i, VolumeCache(v->get_volume_transformation(), v->get_instance_transformation())); +#else + m_cache.volumes_data.emplace(i, VolumeCache(v->get_offset(), v->get_rotation(), v->get_scaling_factor())); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + m_cache.dragging_center = get_bounding_box().center(); +} + +void GLCanvas3D::Selection::_add_volume(unsigned int volume_idx) +{ + m_list.insert(volume_idx); + (*m_volumes)[volume_idx]->selected = true; +} + +void GLCanvas3D::Selection::_add_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + _add_volume(i); + } +} + +void GLCanvas3D::Selection::_add_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) + _add_volume(i); + } +} + +void GLCanvas3D::Selection::_remove_volume(unsigned int volume_idx) +{ + IndicesList::iterator v_it = m_list.find(volume_idx); + if (v_it == m_list.end()) + return; + + m_list.erase(v_it); + + (*m_volumes)[volume_idx]->selected = false; +} + +void GLCanvas3D::Selection::_remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + _remove_volume(i); + } +} + +void GLCanvas3D::Selection::_remove_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) + _remove_volume(i); + } +} + +void GLCanvas3D::Selection::_calc_bounding_box() const +{ + m_bounding_box = BoundingBoxf3(); + if (m_valid) + { + for (unsigned int i : m_list) + { + m_bounding_box.merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); + } + } + m_bounding_box_dirty = false; +} + +void GLCanvas3D::Selection::_render_selected_volumes() const +{ + float color[3] = { 1.0f, 1.0f, 1.0f }; + _render_bounding_box(get_bounding_box(), color); +} + +void GLCanvas3D::Selection::_render_synchronized_volumes() const +{ + if (m_mode == Instance) + return; + + float color[3] = { 1.0f, 1.0f, 0.0f }; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + int instance_idx = volume->instance_idx(); + int volume_idx = volume->volume_idx(); + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (i == j) + continue; + + const GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + continue; + + _render_bounding_box(v->transformed_convex_hull_bounding_box(), color); + } + } +} + +void GLCanvas3D::Selection::_render_bounding_box(const BoundingBoxf3& box, float* color) const +{ + if (color == nullptr) + return; + + Vec3f b_min = box.min.cast(); + Vec3f b_max = box.max.cast(); + Vec3f size = 0.2f * box.size().cast(); + + ::glEnable(GL_DEPTH_TEST); + + ::glColor3fv(color); + ::glLineWidth(2.0f); + + ::glBegin(GL_LINES); + + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); + + ::glEnd(); +} + +void GLCanvas3D::Selection::_synchronize_unselected_instances() +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; + + int instance_idx = volume->instance_idx(); +#if ENABLE_MODELVOLUME_TRANSFORM + const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); + const Vec3d& mirror = volume->get_instance_mirror(); +#else + const Vec3d& rotation = volume->get_rotation(); + const Vec3d& scaling_factor = volume->get_scaling_factor(); + const Vec3d& mirror = volume->get_mirror(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) + continue; + +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), v->get_instance_rotation()(2))); + v->set_instance_scaling_factor(scaling_factor); + v->set_instance_mirror(mirror); +#else + v->set_rotation(Vec3d(rotation(0), rotation(1), v->get_rotation()(2))); + v->set_scaling_factor(scaling_factor); + v->set_mirror(mirror); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + done.insert(j); + } + } +} + +void GLCanvas3D::Selection::_synchronize_unselected_volumes() +{ + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; + + int volume_idx = volume->volume_idx(); +#if ENABLE_MODELVOLUME_TRANSFORM + const Vec3d& offset = volume->get_volume_offset(); + const Vec3d& rotation = volume->get_volume_rotation(); + const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); + const Vec3d& mirror = volume->get_volume_mirror(); +#else + const Vec3d& offset = volume->get_offset(); + const Vec3d& rotation = volume->get_rotation(); + const Vec3d& scaling_factor = volume->get_scaling_factor(); + const Vec3d& mirror = volume->get_mirror(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + // Process unselected volumes. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (j == i) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + continue; + +#if ENABLE_MODELVOLUME_TRANSFORM + v->set_volume_offset(offset); + v->set_volume_rotation(rotation); + v->set_volume_scaling_factor(scaling_factor); + v->set_volume_mirror(mirror); +#else + v->set_offset(offset); + v->set_rotation(Vec3d(rotation)); + v->set_scaling_factor(scaling_factor); + v->set_mirror(mirror); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + } +} + +#if ENABLE_ENSURE_ON_BED_WHILE_SCALING +void GLCanvas3D::Selection::_ensure_on_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (GLVolume* volume : *m_volumes) + { + if (!volume->is_wipe_tower && !volume->is_modifier) + { + double min_z = volume->transformed_convex_hull_bounding_box().min(2); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : *m_volumes) + { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} +#endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING + +const float GLCanvas3D::Gizmos::OverlayIconsScale = 1.0f; +const float GLCanvas3D::Gizmos::OverlayBorder = 5.0f; +const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayIconsScale; GLCanvas3D::Gizmos::Gizmos() : m_enabled(false) @@ -1142,11 +2525,6 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) if (!gizmo->init()) return false; -#if !ENABLE_MODELINSTANCE_3D_OFFSET - // temporary disable z grabber - gizmo->disable_grabber(2); -#endif // !ENABLE_MODELINSTANCE_3D_OFFSET - m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); gizmo = new GLGizmoScale3D(parent); @@ -1156,16 +2534,6 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) if (!gizmo->init()) return false; - // temporary disable x grabbers - gizmo->disable_grabber(0); - gizmo->disable_grabber(1); - // temporary disable y grabbers - gizmo->disable_grabber(2); - gizmo->disable_grabber(3); - // temporary disable z grabbers - gizmo->disable_grabber(4); - gizmo->disable_grabber(5); - m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); gizmo = new GLGizmoRotate3D(parent); @@ -1181,10 +2549,6 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) return false; } - // temporary disable x and y grabbers - gizmo->disable_grabber(0); - gizmo->disable_grabber(1); - m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); gizmo = new GLGizmoFlatten(parent); @@ -1198,6 +2562,44 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); + gizmo = new GLGizmoCut(parent); + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + _reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); + + gizmo = new GLGizmoSlaSupports(parent); + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + _reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo)); + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_background_texture.metadata.filename = "toolbar_background.png"; + m_background_texture.metadata.left = 16; + m_background_texture.metadata.top = 16; + m_background_texture.metadata.right = 16; + m_background_texture.metadata.bottom = 16; + + if (!m_background_texture.metadata.filename.empty()) + { + if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) + { + _reset(); + return false; + } + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return true; } @@ -1212,57 +2614,60 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable) m_enabled = enable; } -void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos) +std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection) { + std::string name = ""; + if (!m_enabled) - return; + return name; float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); - for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - if (it->second == nullptr) + if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius - if (it->second->get_state() != GLGizmoBase::On) + if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) { - bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size; + bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); + if (inside) + name = it->second->get_name(); } - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); } + + return name; } -void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos) +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection) { if (!m_enabled) return; float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); - for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - if (it->second == nullptr) + if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius - if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size) + bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); + if (it->second->is_activable(selection) && inside) { if ((it->second->get_state() == GLGizmoBase::On)) { - it->second->set_state(GLGizmoBase::Off); + it->second->set_state(GLGizmoBase::Hover); m_current = Undefined; } - else + else if ((it->second->get_state() == GLGizmoBase::Hover)) { it->second->set_state(GLGizmoBase::On); m_current = it->first; @@ -1271,7 +2676,24 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec else it->second->set_state(GLGizmoBase::Off); - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); + } + + GizmosMap::iterator it = m_gizmos.find(m_current); + if ((it != m_gizmos.end()) && (it->second != nullptr) && (it->second->get_state() != GLGizmoBase::On)) + it->second->set_state(GLGizmoBase::On); +} + +void GLCanvas3D::Gizmos::update_on_off_state(const Selection& selection) +{ + GizmosMap::iterator it = m_gizmos.find(m_current); + if ((it != m_gizmos.end()) && (it->second != nullptr)) + { + if (!it->second->is_activable(selection)) + { + it->second->set_state(GLGizmoBase::Off); + m_current = Undefined; + } } } @@ -1304,6 +2726,21 @@ void GLCanvas3D::Gizmos::set_hover_id(int id) } } +void GLCanvas3D::Gizmos::enable_grabber(EType type, unsigned int id, bool enable) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(type); + if (it != m_gizmos.end()) + { + if (enable) + it->second->enable_grabber(id); + else + it->second->disable_grabber(id); + } +} + bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const { if (!m_enabled) @@ -1311,20 +2748,18 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - if (it->second == nullptr) + if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius - if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size) + if ((OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size)) return true; - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); } return false; @@ -1339,14 +2774,14 @@ bool GLCanvas3D::Gizmos::grabber_contains_mouse() const return (curr != nullptr) ? (curr->get_hover_id() != -1) : false; } -void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray) +void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray, bool shift_down, const Point* mouse_pos) { if (!m_enabled) return; GLGizmoBase* curr = _get_current(); if (curr != nullptr) - curr->update(mouse_ray); + curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos, shift_down)); } GLCanvas3D::Gizmos::EType GLCanvas3D::Gizmos::get_current_type() const @@ -1363,55 +2798,89 @@ bool GLCanvas3D::Gizmos::is_running() const return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } +bool GLCanvas3D::Gizmos::handle_shortcut(int key, const Selection& selection) +{ + if (!m_enabled) + return false; + + bool handled = false; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + int it_key = it->second->get_shortcut_key(); + + if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96))) + { + if ((it->second->get_state() == GLGizmoBase::On)) + { + it->second->set_state(GLGizmoBase::Off); + m_current = Undefined; + handled = true; + } + else if ((it->second->get_state() == GLGizmoBase::Off)) + { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + handled = true; + } + } + else + it->second->set_state(GLGizmoBase::Off); + } + + return handled; +} + bool GLCanvas3D::Gizmos::is_dragging() const { + if (!m_enabled) + return false; + GLGizmoBase* curr = _get_current(); return (curr != nullptr) ? curr->is_dragging() : false; } -void GLCanvas3D::Gizmos::start_dragging(const BoundingBoxf3& box) +void GLCanvas3D::Gizmos::start_dragging(const GLCanvas3D::Selection& selection) { + if (!m_enabled) + return; + GLGizmoBase* curr = _get_current(); if (curr != nullptr) - curr->start_dragging(box); + curr->start_dragging(selection); } void GLCanvas3D::Gizmos::stop_dragging() { + if (!m_enabled) + return; + GLGizmoBase* curr = _get_current(); if (curr != nullptr) curr->stop_dragging(); } -Vec3d GLCanvas3D::Gizmos::get_position() const +Vec3d GLCanvas3D::Gizmos::get_displacement() const { if (!m_enabled) return Vec3d::Zero(); GizmosMap::const_iterator it = m_gizmos.find(Move); - return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_position() : Vec3d::Zero(); + return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_displacement() : Vec3d::Zero(); } -void GLCanvas3D::Gizmos::set_position(const Vec3d& position) +Vec3d GLCanvas3D::Gizmos::get_scale() const { if (!m_enabled) - return; - - GizmosMap::const_iterator it = m_gizmos.find(Move); - if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_position(position); -} - -float GLCanvas3D::Gizmos::get_scale() const -{ - if (!m_enabled) - return 1.0f; + return Vec3d::Ones(); GizmosMap::const_iterator it = m_gizmos.find(Scale); - return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale_x() : 1.0f; + return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale() : Vec3d::Ones(); } -void GLCanvas3D::Gizmos::set_scale(float scale) +void GLCanvas3D::Gizmos::set_scale(const Vec3d& scale) { if (!m_enabled) return; @@ -1421,23 +2890,23 @@ void GLCanvas3D::Gizmos::set_scale(float scale) reinterpret_cast(it->second)->set_scale(scale); } -float GLCanvas3D::Gizmos::get_angle_z() const +Vec3d GLCanvas3D::Gizmos::get_rotation() const { if (!m_enabled) - return 0.0f; + return Vec3d::Zero(); GizmosMap::const_iterator it = m_gizmos.find(Rotate); - return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle_z() : 0.0f; + return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_rotation() : Vec3d::Zero(); } -void GLCanvas3D::Gizmos::set_angle_z(float angle_z) +void GLCanvas3D::Gizmos::set_rotation(const Vec3d& rotation) { if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(Rotate); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_angle_z(angle_z); + reinterpret_cast(it->second)->set_rotation(rotation); } Vec3d GLCanvas3D::Gizmos::get_flattening_normal() const @@ -1459,28 +2928,63 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -void GLCanvas3D::Gizmos::render_current_gizmo(const BoundingBoxf3& box) const +#if ENABLE_SLA_SUPPORT_GIZMO_MOD +void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) +#else +void GLCanvas3D::Gizmos::set_model_object_ptr(ModelObject* model_object) +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD { if (!m_enabled) return; - ::glDisable(GL_DEPTH_TEST); - - if (box.radius() > 0.0) - _render_current_gizmo(box); + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); +#else + reinterpret_cast(it->second)->set_model_object_ptr(model_object); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } -void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const +void GLCanvas3D::Gizmos::clicked_on_object(const Vec2d& mouse_position) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) + reinterpret_cast(it->second)->clicked_on_object(mouse_position); +} + +void GLCanvas3D::Gizmos::delete_current_grabber(bool delete_all) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) + reinterpret_cast(it->second)->delete_current_grabber(delete_all); +} + +void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const +{ + if (!m_enabled) + return; + + _render_current_gizmo(selection); +} + +void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const GLCanvas3D::Selection& selection) const { if (!m_enabled) return; GLGizmoBase* curr = _get_current(); if (curr != nullptr) - curr->render_for_picking(box); + curr->render_for_picking(selection); } -void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas) const +void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas, const GLCanvas3D::Selection& selection) const { if (!m_enabled) return; @@ -1490,11 +2994,20 @@ void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas) const ::glPushMatrix(); ::glLoadIdentity(); - _render_overlay(canvas); + _render_overlay(canvas, selection); ::glPopMatrix(); } +#if !ENABLE_IMGUI +void GLCanvas3D::Gizmos::create_external_gizmo_widgets(wxWindow *parent) +{ + for (auto &entry : m_gizmos) { + entry.second->create_external_gizmo_widgets(parent); + } +} +#endif // not ENABLE_IMGUI + void GLCanvas3D::Gizmos::_reset() { for (GizmosMap::value_type& gizmo : m_gizmos) @@ -1506,46 +3019,160 @@ void GLCanvas3D::Gizmos::_reset() m_gizmos.clear(); } -void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas) const +void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanvas3D::Selection& selection) const { if (m_gizmos.empty()) return; float cnv_w = (float)canvas.get_canvas_size().get_width(); +#if ENABLE_IMGUI + float cnv_h = (float)canvas.get_canvas_size().get_height(); +#endif // ENABLE_IMGUI float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = _get_total_overlay_height(); - float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom; - float top_y = 0.5f * height * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = OverlayBorder * inv_zoom; + + float top_x = (-0.5f * cnv_w) * inv_zoom; + float top_y = (0.5f * height) * inv_zoom; + + float left = top_x; + float top = top_y; + float right = left + _get_total_overlay_width() * inv_zoom; + float bottom = top - height * inv_zoom; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + bg_uv_left = bg_uv_i_left; + bg_i_left = bg_left; + + if ((OverlayBorder > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((OverlayBorder > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((OverlayBorder > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((OverlayBorder > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + top_x += OverlayBorder * inv_zoom; + top_y -= OverlayBorder * inv_zoom; +#else + float top_x = (OverlayBorder - 0.5f * cnv_w) * inv_zoom; + float top_y = (0.5f * height - OverlayBorder) * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_gap_y = OverlayGapY * inv_zoom; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale * inv_zoom; - GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); - top_y -= (tex_size + scaled_gap_y); + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale * inv_zoom; + GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + icon_size, top_y - icon_size, top_y); +#if ENABLE_IMGUI + if (it->second->get_state() == GLGizmoBase::On) + it->second->render_input_window(2.0f * OverlayBorder + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); +#endif // ENABLE_IMGUI + top_y -= (icon_size + scaled_gap_y); } } -void GLCanvas3D::Gizmos::_render_current_gizmo(const BoundingBoxf3& box) const +void GLCanvas3D::Gizmos::_render_current_gizmo(const GLCanvas3D::Selection& selection) const { GLGizmoBase* curr = _get_current(); if (curr != nullptr) - curr->render(box); + curr->render(selection); } float GLCanvas3D::Gizmos::_get_total_overlay_height() const { - float height = 0.0f; + float height = 2.0f * OverlayBorder; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - height += (float)it->second->get_textures_size(); - if (std::distance(it, m_gizmos.end()) > 1) - height += OverlayGapY; + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + height += (float)it->second->get_textures_size() * OverlayIconsScale + OverlayGapY; } - return height; + return height - OverlayGapY; +} + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +float GLCanvas3D::Gizmos::_get_total_overlay_width() const +{ + float max_icon_width = 0.0f; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * OverlayIconsScale); + } + + return max_icon_width + 2.0f * OverlayBorder; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const +{ + GizmosMap::const_iterator it = m_gizmos.find(m_current); + return (it != m_gizmos.end()) ? it->second : nullptr; } const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 }; @@ -1586,17 +3213,12 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) // generates bitmap wxBitmap bitmap(m_width, m_height); -#if defined(__APPLE__) || defined(_MSC_VER) - bitmap.UseAlpha(); -#endif - memDC.SelectObject(bitmap); memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2]))); memDC.Clear(); - memDC.SetTextForeground(*wxWHITE); - // draw message + memDC.SetTextForeground(*wxWHITE); memDC.DrawText(msg, 0, 0); memDC.SelectObject(wxNullBitmap); @@ -1668,7 +3290,8 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const } const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; -const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 }; +const unsigned char GLCanvas3D::LegendTexture::Default_Background_Color[3] = { (unsigned char)(DEFAULT_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[2] * 255.0f) }; +const unsigned char GLCanvas3D::LegendTexture::Error_Background_Color[3] = { (unsigned char)(ERROR_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[2] * 255.0f) }; const unsigned char GLCanvas3D::LegendTexture::Opacity = 255; GLCanvas3D::LegendTexture::LegendTexture() @@ -1678,13 +3301,38 @@ GLCanvas3D::LegendTexture::LegendTexture() { } -bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors) +bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors) { reset(); // collects items to render auto title = _(preview_data.get_legend_title()); - const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors); + + std::vector> cp_legend_values; + if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) + { + const auto& config = wxGetApp().preset_bundle->full_config(); + const std::vector& color_print_values = config.option("colorprint_heights")->values; + const int values_cnt = color_print_values.size(); + if (values_cnt > 0) { + auto print_zs = canvas.get_current_print_zs(true); + auto z = 0; + for (auto i = 0; i < values_cnt; ++i) + { + double prev_z = -1.0; + for (z; z < print_zs.size(); ++z) + if (fabs(color_print_values[i] - print_zs[z]) < EPSILON) { + prev_z = print_zs[z - 1]; + break; + } + if (prev_z < 0) + continue; + + cp_legend_values.push_back(std::pair(prev_z, color_print_values[i])); + } + } + } + const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors, /*color_print_values*/cp_legend_values); unsigned int items_count = (unsigned int)items.size(); if (items_count == 0) @@ -1692,8 +3340,11 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c return false; wxMemoryDC memDC; + wxMemoryDC mask_memDC; + // select default font memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + mask_memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); // calculates texture size wxCoord w, h; @@ -1715,28 +3366,35 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c if (items_count > 1) m_original_height += (items_count - 1) * Px_Square_Contour; - int pow_of_two_size = next_highest_power_of_2(std::max(m_original_width, m_original_height)); + int pow_of_two_size = (int)next_highest_power_of_2(std::max(m_original_width, m_original_height)); m_width = pow_of_two_size; m_height = pow_of_two_size; // generates bitmap wxBitmap bitmap(m_width, m_height); - -#if defined(__APPLE__) || defined(_MSC_VER) - bitmap.UseAlpha(); -#endif + wxBitmap mask(m_width, m_height); memDC.SelectObject(bitmap); - memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2]))); - memDC.Clear(); + mask_memDC.SelectObject(mask); - memDC.SetTextForeground(*wxWHITE); + memDC.SetBackground(wxBrush(use_error_colors ? *wxWHITE : *wxBLACK)); + mask_memDC.SetBackground(wxBrush(*wxBLACK)); + + memDC.Clear(); + mask_memDC.Clear(); // draw title + memDC.SetTextForeground(use_error_colors ? *wxWHITE : *wxBLACK); + mask_memDC.SetTextForeground(*wxWHITE); + int title_x = Px_Border; int title_y = Px_Border; memDC.DrawText(title, title_x, title_y); + mask_memDC.DrawText(title, title_x, title_y); + + mask_memDC.SetPen(wxPen(*wxWHITE)); + mask_memDC.SetBrush(wxBrush(*wxWHITE)); // draw icons contours as background int squares_contour_x = Px_Border; @@ -1752,6 +3410,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.SetPen(pen); memDC.SetBrush(brush); memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); + mask_memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); // draw items (colored icon + text) int icon_x = squares_contour_x + Px_Square_Contour; @@ -1788,16 +3447,18 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c // draw text memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); + mask_memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); // update y icon_y += icon_y_step; } memDC.SelectObject(wxNullBitmap); + mask_memDC.SelectObject(wxNullBitmap); // Convert the bitmap into a linear data ready to be loaded into the GPU. wxImage image = bitmap.ConvertToImage(); - image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]); + wxImage mask_image = mask.ConvertToImage(); // prepare buffer std::vector data(4 * m_width * m_height, 0); @@ -1810,7 +3471,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c *px_ptr++ = image.GetRed(w, h); *px_ptr++ = image.GetGreen(w, h); *px_ptr++ = image.GetBlue(w, h); - *px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity; + *px_ptr++ = (mask_image.GetRed(w, h) + mask_image.GetGreen(w, h) + mask_image.GetBlue(w, h)) / 3; } } @@ -1861,19 +3522,38 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const } } -GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const -{ - GizmosMap::const_iterator it = m_gizmos.find(m_current); - return (it != m_gizmos.end()) ? it->second : nullptr; -} +#if ENABLE_REMOVE_TABS_FROM_PLATER +wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MODEL_UPDATE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); +wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); +wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) , m_context(nullptr) - , m_timer(nullptr) + , m_in_render(false) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , m_toolbar(GLToolbar::Normal) +#else , m_toolbar(*this) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_REMOVE_TABS_FROM_PLATER + , m_view_toolbar(nullptr) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + , m_use_clipping_planes(false) , m_config(nullptr) - , m_print(nullptr) + , m_process(nullptr) , m_model(nullptr) , m_dirty(true) , m_initialized(false) @@ -1889,35 +3569,47 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_shader_enabled(false) , m_dynamic_background_enabled(false) , m_multisample_allowed(false) + , m_regenerate_volumes(true) + , m_moving(false) , m_color_by("volume") - , m_select_by("object") - , m_drag_by("instance") , m_reload_delayed(false) +#if !ENABLE_IMGUI + , m_external_gizmo_widgets_parent(nullptr) +#endif // not ENABLE_IMGUI { if (m_canvas != nullptr) { +#if !ENABLE_USE_UNIQUE_GLCONTEXT m_context = new wxGLContext(m_canvas); - m_timer = new wxTimer(m_canvas); +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT + m_timer.SetOwner(m_canvas); } + + m_selection.set_volumes(&m_volumes.volumes); } GLCanvas3D::~GLCanvas3D() { reset_volumes(); - if (m_timer != nullptr) - { - delete m_timer; - m_timer = nullptr; - } - +#if !ENABLE_USE_UNIQUE_GLCONTEXT if (m_context != nullptr) { delete m_context; m_context = nullptr; } +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT +} - _deregister_callbacks(); +void GLCanvas3D::post_event(wxEvent &&event) +{ + event.SetEventObject(m_canvas); + wxPostEvent(m_canvas, event); +} + +void GLCanvas3D::viewport_changed() +{ + post_event(SimpleEvent(EVT_GLCANVAS_VIEWPORT_CHANGED)); } bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) @@ -1985,17 +3677,33 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (!m_volumes.empty()) m_volumes.finalize_geometry(m_use_VBOs); - if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) - return false; + if (m_gizmos.is_enabled()) { + if (! m_gizmos.init(*this)) { + std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; + return false; + } + +#if !ENABLE_IMGUI + if (m_external_gizmo_widgets_parent != nullptr) { + m_gizmos.create_external_gizmo_widgets(m_external_gizmo_widgets_parent); + m_canvas->GetParent()->Layout(); + } +#endif // not ENABLE_IMGUI + } if (!_init_toolbar()) return false; +#if ENABLE_REMOVE_TABS_FROM_PLATER + post_event(SimpleEvent(EVT_GLCANVAS_INIT)); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + m_initialized = true; return true; } +#if !ENABLE_USE_UNIQUE_GLCONTEXT bool GLCanvas3D::set_current() { if ((m_canvas != nullptr) && (m_context != nullptr)) @@ -2003,6 +3711,7 @@ bool GLCanvas3D::set_current() return false; } +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT void GLCanvas3D::set_as_dirty() { @@ -2018,10 +3727,13 @@ void GLCanvas3D::reset_volumes() { if (!m_volumes.empty()) { +#if !ENABLE_USE_UNIQUE_GLCONTEXT // ensures this canvas is current if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT + m_selection.clear(); m_volumes.release_geometry(); m_volumes.clear(); m_dirty = true; @@ -2031,99 +3743,36 @@ void GLCanvas3D::reset_volumes() _reset_warning_texture(); } -void GLCanvas3D::deselect_volumes() +#if ENABLE_REMOVE_TABS_FROM_PLATER +int GLCanvas3D::check_volumes_outside_state() const { - for (GLVolume* vol : m_volumes.volumes) - { - if (vol != nullptr) - vol->selected = false; - } + ModelInstance::EPrintVolumeState state; + m_volumes.check_outside_state(m_config, &state); + return (int)state; } - -void GLCanvas3D::select_volume(unsigned int id) -{ - if (id < (unsigned int)m_volumes.volumes.size()) - { - GLVolume* vol = m_volumes.volumes[id]; - if (vol != nullptr) - vol->selected = true; - } -} - -void GLCanvas3D::update_volumes_selection(const std::vector& selections) -{ - if (m_model == nullptr) - return; - - if (selections.empty()) - return; - - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) - { - if ((selections[obj_idx] == 1) && (obj_idx < (unsigned int)m_objects_volumes_idxs.size())) - { - const std::vector& volume_idxs = m_objects_volumes_idxs[obj_idx]; - for (int v : volume_idxs) - { - select_volume(v); - } - } - } -} - +#else int GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const { ModelInstance::EPrintVolumeState state; m_volumes.check_outside_state(config, &state); return (int)state; } - -bool GLCanvas3D::move_volume_up(unsigned int id) -{ - if ((id > 0) && (id < (unsigned int)m_volumes.volumes.size())) - { - std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]); - std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id); - std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id); - std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); - return true; - } - - return false; -} - -bool GLCanvas3D::move_volume_down(unsigned int id) -{ - if ((id >= 0) && (id + 1 < (unsigned int)m_volumes.volumes.size())) - { - std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]); - std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id); - std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id); - std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); - return true; - } - - return false; -} - -void GLCanvas3D::set_objects_selections(const std::vector& selections) -{ - m_objects_selections = selections; -} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER void GLCanvas3D::set_config(DynamicPrintConfig* config) { m_config = config; } -void GLCanvas3D::set_print(Print* print) +void GLCanvas3D::set_process(BackgroundSlicingProcess *process) { - m_print = print; + m_process = process; } void GLCanvas3D::set_model(Model* model) { m_model = model; + m_selection.set_model(m_model); } void GLCanvas3D::set_bed_shape(const Pointfs& shape) @@ -2135,74 +3784,21 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape) set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); if (new_shape) - { - // forces the selection of the proper camera target - if (m_volumes.volumes.empty()) - zoom_to_bed(); - else - zoom_to_volumes(); - } + zoom_to_bed(); m_dirty = true; } -void GLCanvas3D::set_auto_bed_shape() -{ - // draw a default square bed around object center - const BoundingBoxf3& bbox = volumes_bounding_box(); - double max_size = bbox.max_size(); - const Vec3d center = bbox.center(); - - Pointfs bed_shape; - bed_shape.reserve(4); - bed_shape.emplace_back(center(0) - max_size, center(1) - max_size); - bed_shape.emplace_back(center(0) + max_size, center(1) - max_size); - bed_shape.emplace_back(center(0) + max_size, center(1) + max_size); - bed_shape.emplace_back(center(0) - max_size, center(1) + max_size); - - set_bed_shape(bed_shape); - - // Set the origin for painting of the coordinate system axes. - m_axes.origin = Vec3d(center(0), center(1), (double)GROUND_Z); -} - void GLCanvas3D::set_axes_length(float length) { m_axes.length = length; } -void GLCanvas3D::set_cutting_plane(float z, const ExPolygons& polygons) -{ - m_cutting_plane.set(z, polygons); -} - void GLCanvas3D::set_color_by(const std::string& value) { m_color_by = value; } -void GLCanvas3D::set_select_by(const std::string& value) -{ - m_select_by = value; - m_volumes.set_select_by(value); -} - -void GLCanvas3D::set_drag_by(const std::string& value) -{ - m_drag_by = value; - m_volumes.set_drag_by(value); -} - -const std::string& GLCanvas3D::get_select_by() const -{ - return m_select_by; -} - -const std::string& GLCanvas3D::get_drag_by() const -{ - return m_drag_by; -} - float GLCanvas3D::get_camera_zoom() const { return m_camera.zoom; @@ -2219,6 +3815,15 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const return bb; } +#if ENABLE_CONSTRAINED_CAMERA_TARGET +BoundingBoxf3 GLCanvas3D::scene_bounding_box() const +{ + BoundingBoxf3 bb = volumes_bounding_box(); + bb.merge(m_bed.get_bounding_box()); + return bb; +} +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET + bool GLCanvas3D::is_layers_editing_enabled() const { return m_layers_editing.is_enabled(); @@ -2229,11 +3834,6 @@ bool GLCanvas3D::is_layers_editing_allowed() const return m_layers_editing.is_allowed(); } -bool GLCanvas3D::is_shader_enabled() const -{ - return m_shader_enabled; -} - bool GLCanvas3D::is_reload_delayed() const { return m_reload_delayed; @@ -2257,6 +3857,7 @@ void GLCanvas3D::enable_legend_texture(bool enable) void GLCanvas3D::enable_picking(bool enable) { m_picking_enabled = enable; + m_selection.set_mode(Selection::Instance); } void GLCanvas3D::enable_moving(bool enable) @@ -2319,6 +3920,14 @@ void GLCanvas3D::zoom_to_volumes() m_apply_zoom_to_volumes_filter = false; } +#if ENABLE_MODIFIED_CAMERA_TARGET +void GLCanvas3D::zoom_to_selection() +{ + if (!m_selection.is_empty()) + _zoom_to_bounding_box(m_selection.get_bounding_box()); +} +#endif // ENABLE_MODIFIED_CAMERA_TARGET + void GLCanvas3D::select_view(const std::string& direction) { const float* dir_vec = nullptr; @@ -2338,12 +3947,12 @@ void GLCanvas3D::select_view(const std::string& direction) else if (direction == "rear") dir_vec = VIEW_REAR; - if ((dir_vec != nullptr) && !empty(volumes_bounding_box())) + if (dir_vec != nullptr) { m_camera.phi = dir_vec[0]; m_camera.set_theta(dir_vec[1]); - m_on_viewport_changed_callback.call(); + viewport_changed(); if (m_canvas != nullptr) m_canvas->Refresh(); @@ -2354,7 +3963,12 @@ void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other) { m_camera.phi = other.m_camera.phi; m_camera.set_theta(other.m_camera.get_theta()); +#if ENABLE_CONSTRAINED_CAMERA_TARGET + m_camera.set_scene_box(other.m_camera.get_scene_box(), *this); + m_camera.set_target(other.m_camera.get_target(), *this); +#else m_camera.target = other.m_camera.target; +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET m_camera.zoom = other.m_camera.zoom; m_dirty = true; } @@ -2365,42 +3979,35 @@ void GLCanvas3D::update_volumes_colors_by_extruder() m_volumes.update_colors_by_extruder(m_config); } -void GLCanvas3D::update_gizmos_data() +// Returns a Rect object denoting size and position of the Reset button used by a gizmo. +// Returns in either screen or viewport coords. +#if !ENABLE_IMGUI +Rect GLCanvas3D::get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const { - if (!m_gizmos.is_enabled()) - return; - - int id = _get_first_selected_object_id(); - if ((id != -1) && (m_model != nullptr)) - { - ModelObject* model_object = m_model->objects[id]; - if (model_object != nullptr) - { - ModelInstance* model_instance = model_object->instances[0]; - if (model_instance != nullptr) - { -#if ENABLE_MODELINSTANCE_3D_OFFSET - m_gizmos.set_position(model_instance->get_offset()); -#else - m_gizmos.set_position(Vec3d(model_instance->offset(0), model_instance->offset(1), 0.0)); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - m_gizmos.set_scale(model_instance->scaling_factor); - m_gizmos.set_angle_z(model_instance->rotation); - m_gizmos.set_flattening_data(model_object); - } - } - } - else - { - m_gizmos.set_position(Vec3d::Zero()); - m_gizmos.set_scale(1.0f); - m_gizmos.set_angle_z(0.0f); - m_gizmos.set_flattening_data(nullptr); - } + const Size& cnv_size = canvas.get_canvas_size(); + float w = (viewport ? -0.5f : 0.f) * (float)cnv_size.get_width(); + float h = (viewport ? 0.5f : 1.f) * (float)cnv_size.get_height(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = viewport ? ((zoom != 0.0f) ? 1.0f / zoom : 0.0f) : 1.f; + const float gap = 30.f; + return Rect((w + gap + 80.f) * inv_zoom, (viewport ? -1.f : 1.f) * (h - GIZMO_RESET_BUTTON_HEIGHT) * inv_zoom, + (w + gap + 80.f + GIZMO_RESET_BUTTON_WIDTH) * inv_zoom, (viewport ? -1.f : 1.f) * (h * inv_zoom)); } +bool GLCanvas3D::gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const +{ + const Rect& rect = get_gizmo_reset_rect(canvas, false); + return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); +} +#endif // not ENABLE_IMGUI + void GLCanvas3D::render() { + wxCHECK_RET(!m_in_render, "GLCanvas3D::render() called recursively"); + m_in_render = true; + Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); + (void)in_render_guard; + if (m_canvas == nullptr) return; @@ -2408,7 +4015,11 @@ void GLCanvas3D::render() return; // ensures this canvas is current and initialized +#if ENABLE_USE_UNIQUE_GLCONTEXT + if (!_set_current() || !_3DScene::init(m_canvas)) +#else if (!set_current() || !_3DScene::init(m_canvas)) +#endif // ENABLE_USE_UNIQUE_GLCONTEXT return; if (m_force_zoom_to_bed_enabled) @@ -2424,10 +4035,19 @@ void GLCanvas3D::render() float theta = m_camera.get_theta(); bool is_custom_bed = m_bed.is_custom(); +#if !ENABLE_REMOVE_TABS_FROM_PLATER + set_tooltip(""); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER + +#if ENABLE_IMGUI + wxGetApp().imgui()->new_frame(); +#endif // ENABLE_IMGUI + // picking pass _picking_pass(); // draw scene + ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _render_background(); if (is_custom_bed) // untextured bed needs to be rendered before objects @@ -2436,26 +4056,86 @@ void GLCanvas3D::render() // disable depth testing so that axes are not covered by ground _render_axes(false); } + _render_objects(); + _render_sla_slices(); + _render_selection(); + if (!is_custom_bed) // textured bed needs to be rendered after objects { _render_axes(true); _render_bed(theta); } + // we need to set the mouse's scene position here because the depth buffer + // could be invalidated by the following gizmo render methods + // this position is used later into on_mouse() to drag the objects + m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); + _render_current_gizmo(); - _render_cutting_plane(); +#if ENABLE_SHOW_CAMERA_TARGET + _render_camera_target(); +#endif // ENABLE_SHOW_CAMERA_TARGET // draw overlays _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + _resize_toolbars(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER _render_toolbar(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + _render_view_toolbar(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER _render_layer_editing_overlay(); +#if ENABLE_IMGUI + wxGetApp().imgui()->render(); +#endif // ENABLE_IMGUI + m_canvas->SwapBuffers(); } +void GLCanvas3D::select_all() +{ + m_selection.add_all(); + m_dirty = true; +} + +void GLCanvas3D::delete_selected() +{ + m_selection.erase(); +} + +void GLCanvas3D::ensure_on_bed(unsigned int object_idx) +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (GLVolume* volume : m_volumes.volumes) + { + if ((volume->object_idx() == object_idx) && !volume->is_modifier) + { + double min_z = volume->transformed_convex_hull_bounding_box().min(2); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : m_volumes.volumes) + { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + std::vector GLCanvas3D::get_current_print_zs(bool active_only) const { return m_volumes.get_current_print_zs(active_only); @@ -2475,7 +4155,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.push_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -2490,86 +4170,312 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) return std::vector(); } -int GLCanvas3D::get_first_volume_id(int obj_idx) const +void GLCanvas3D::mirror_selection(Axis axis) { - for (int i = 0; i < (int)m_volumes.volumes.size(); ++i) - { - if ((m_volumes.volumes[i] != nullptr) && (m_volumes.volumes[i]->object_idx() == obj_idx)) - return i; - } - - return -1; + m_selection.mirror(axis); + do_mirror(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); } -int GLCanvas3D::get_in_object_volume_id(int scene_vol_idx) const -{ - return ((0 <= scene_vol_idx) && (scene_vol_idx < (int)m_volumes.volumes.size())) ? m_volumes.volumes[scene_vol_idx]->volume_idx() : -1; -} - -void GLCanvas3D::reload_scene(bool force) +// Reload the 3D scene of +// 1) Model / ModelObjects / ModelInstances / ModelVolumes +// 2) Print bed +// 3) SLA support meshes for their respective ModelObjects / ModelInstances +// 4) Wipe tower preview +// 5) Out of bed collision status & message overlay (texture) +void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) { if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) return; - - reset_volumes(); - +#if !ENABLE_USE_UNIQUE_GLCONTEXT // ensures this canvas is current if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT + + struct ModelVolumeState { + ModelVolumeState(const GLVolume *volume) : + model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} + ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : + model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} + ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) : + model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} + bool new_geometry() const { return this->volume_idx == size_t(-1); } + const ModelVolume *model_volume; + // ModelID of ModelVolume + ModelID of ModelInstance + // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance + std::pair geometry_id; + GLVolume::CompositeID composite_id; + // Volume index in the new GLVolume vector. + size_t volume_idx; + }; + std::vector model_volume_state; + std::vector aux_volume_state; + + // SLA steps to pull the preview meshes for. + typedef std::array SLASteps; + SLASteps sla_steps = { slaposSupportTree, slaposBasePool }; + struct SLASupportState { + std::array::value> step; + }; + // State of the sla_steps for all SLAPrintObjects. + std::vector sla_support_state; + + std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1)); + std::vector glvolumes_new; + glvolumes_new.reserve(m_volumes.volumes.size()); + auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; }; + + m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh; + + PrinterTechnology printer_technology = m_process->current_printer_technology(); + int volume_idx_wipe_tower_old = -1; + + if (m_regenerate_volumes) + { + // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). + // First initialize model_volumes_new_sorted & model_instances_new_sorted. + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { + const ModelObject *model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { + const ModelInstance *model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); + } + } + } + if (printer_technology == ptSLA) { + const SLAPrint *sla_print = this->sla_print(); + #ifdef _DEBUG + // Verify that the SLAPrint object is synchronized with m_model. + check_model_ids_equal(*m_model, sla_print->model()); + #endif /* _DEBUG */ + sla_support_state.reserve(sla_print->objects().size()); + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState state; + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (! print_object->has_mesh(sla_steps[istep])) + // Consider the DONE step without a valid mesh as invalid for the purpose + // of mesh visualization. + state.step[istep].state = PrintStateBase::INVALID; + else + for (const ModelInstance *model_instance : print_object->model_object()->instances) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + } + } + sla_support_state.emplace_back(state); + } + } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); + std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); + // Release all ModelVolume based GLVolumes not found in the current Model. + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { + GLVolume *volume = m_volumes.volumes[volume_id]; + ModelVolumeState key(volume); + ModelVolumeState *mvs = nullptr; + if (volume->volume_idx() < 0) { + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } + if (mvs == nullptr || force_full_scene_refresh) { + // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); + volume_idx_wipe_tower_old = (int)volume_id; + } + volume->release_geometry(); + if (! m_reload_delayed) + delete volume; + } else { + // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); + map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); + mvs->volume_idx = glvolumes_new.size(); + glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + + volume->is_modifier = !mvs->model_volume->is_model_part(); + volume->set_color_from_model_volume(mvs->model_volume); + + // updates volumes transformations + volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); + volume->set_volume_transformation(mvs->model_volume->get_transformation()); + } + } + } + } + + if (m_reload_delayed) + return; set_bed_shape(dynamic_cast(m_config->option("bed_shape"))->values); - if (!m_canvas->IsShown() && !force) + if (m_regenerate_volumes) { - m_reload_delayed = true; - return; - } - - m_reload_delayed = false; - - m_objects_volumes_idxs.clear(); - - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) - { - m_objects_volumes_idxs.push_back(load_object(*m_model, obj_idx)); - } - - // 1st call to reset if no objects left - update_gizmos_data(); - update_volumes_selection(m_objects_selections); - // 2nd call to restore selection, if any - if (!m_objects_selections.empty()) - update_gizmos_data(); - - if (m_config->has("nozzle_diameter")) - { - // Should the wipe tower be visualized ? - unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); - - bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; - bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; - bool co = dynamic_cast(m_config->option("complete_objects"))->value; - - if ((extruders_count > 1) && semm && wt && !co) - { - // Height of a print (Show at least a slab) - double height = std::max(m_model->bounding_box().max(2), 10.0); - - float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; - float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; - float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; - float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - - float depth = m_print->get_wipe_tower_depth(); - if (!m_print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1) ; - - m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), - m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + m_volumes.volumes = std::move(glvolumes_new); + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { + const ModelObject &model_object = *m_model->objects[obj_idx]; + // Object will share a single common layer height texture between all printable volumes. + std::shared_ptr layer_height_texture; + for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // New volume. + if (model_volume.is_model_part() && ! layer_height_texture) { + // New object part needs to have the layer height texture assigned, which is shared with the other volumes of the same part. + // Search for the layer height texture in the other volumes. + for (int iv = volume_idx; iv < (int)model_object.volumes.size(); ++ iv) { + const ModelVolume &mv = *model_object.volumes[iv]; + if (mv.is_model_part()) + for (int ii = instance_idx; ii < (int)model_object.instances.size(); ++ ii) { + const ModelInstance &mi = *model_object.instances[ii]; + ModelVolumeState key(mv.id(), mi.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (! it->new_geometry()) { + // Found an old printable GLVolume (existing before this function was called). + assert(m_volumes.volumes[it->volume_idx]->geometry_id == key.geometry_id); + // Reuse the layer height texture. + const GLVolume *volume = m_volumes.volumes[it->volume_idx]; + assert(volume->layer_height_texture); + layer_height_texture = volume->layer_height_texture; + goto iv_end; + } + } + } + iv_end: + if (! layer_height_texture) + layer_height_texture = std::make_shared(); + } + m_volumes.load_object_volume(&model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized); + m_volumes.volumes.back()->geometry_id = key.geometry_id; + } else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + assert(existing_volume.geometry_id == key.geometry_id); + // Update the Object/Volume/Instance indices into the current Model. + existing_volume.composite_id = it->composite_id; + if (model_volume.is_model_part() && ! layer_height_texture) { + assert(existing_volume.layer_height_texture); + // cache its layer height texture + layer_height_texture = existing_volume.layer_height_texture; + } + } + } + } } - } + if (printer_technology == ptSLA) { + size_t idx = 0; + const SLAPrint *sla_print = this->sla_print(); + std::vector shift_zs(m_model->objects.size(), 0); + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState &state = sla_support_state[idx ++]; + const ModelObject *model_object = print_object->model_object(); + // Find an index of the ModelObject + int object_idx; + if (std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; })) + continue; + // There may be new SLA volumes added to the scene for this print_object. + // Find the object index of this print_object in the Model::objects list. + auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); + assert(it != sla_print->model().objects.end()); + object_idx = it - sla_print->model().objects.begin(); + // Cache the Z offset to be applied to all volumes with this object_idx. + shift_zs[object_idx] = print_object->get_current_elevation(); + // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. + // pairs of + std::vector> instances[std::tuple_size::value]; + for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { + const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; + // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. + auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), + [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); + assert(it != model_object->instances.end()); + int instance_idx = it - model_object->instances.begin(); + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) + if (state.step[istep].state == PrintStateBase::DONE) { + ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) + instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + else + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + } + } - update_volumes_colors_by_extruder(); + // stores the current volumes count + size_t volumes_count = m_volumes.volumes.size(); + + for (size_t istep = 0; istep < sla_steps.size(); ++istep) + if (!instances[istep].empty()) + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_use_VBOs && m_initialized); + } + + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + } + + if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) + { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + + bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(m_config->option("complete_objects"))->value; + + if ((extruders_count > 1) && semm && wt && !co) + { + // Height of a print (Show at least a slab) + double height = std::max(m_model->bounding_box().max(2), 10.0); + + float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; + + const Print *print = m_process->fff_print(); + float depth = print->get_wipe_tower_depth(); + if (!print->is_step_done(psWipeTower)) + depth = (900.f/w) * (float)(extruders_count - 1) ; + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), + print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; + } + } + + update_volumes_colors_by_extruder(); + // Update selection indices based on the old/new GLVolumeCollection. + m_selection.volumes_changed(map_glvolume_old_to_new); + } + + _update_gizmos_data(); + + // Update the toolbar + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) @@ -2581,36 +4487,50 @@ void GLCanvas3D::reload_scene(bool force) { enable_warning_texture(true); _generate_warning_texture(L("Detected object outside print volume")); - m_on_enable_action_buttons_callback.call(state == ModelInstance::PVS_Fully_Outside); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); } else { enable_warning_texture(false); m_volumes.reset_outside_state(); _reset_warning_texture(); - m_on_enable_action_buttons_callback.call(!m_model->objects.empty()); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); } } else { enable_warning_texture(false); _reset_warning_texture(); - m_on_enable_action_buttons_callback.call(false); + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } + + // restore to default value + m_regenerate_volumes = true; + +#if ENABLE_CONSTRAINED_CAMERA_TARGET + m_camera.set_scene_box(scene_bounding_box(), *this); + m_camera.set_target(m_camera.get_target(), *this); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET + + // and force this canvas to be redrawn. + m_dirty = true; } void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors) { - if ((m_canvas != nullptr) && (m_print != nullptr)) + const Print *print = this->fff_print(); + if ((m_canvas != nullptr) && (print != nullptr)) { +#if !ENABLE_USE_UNIQUE_GLCONTEXT // ensures that this canvas is current if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT + + std::vector tool_colors = _parse_colors(str_tool_colors); if (m_volumes.empty()) { - std::vector tool_colors = _parse_colors(str_tool_colors); - m_gcode_preview_volume_index.reset(); _load_gcode_extrusion_paths(preview_data, tool_colors); @@ -2618,34 +4538,45 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _load_gcode_retractions(preview_data); _load_gcode_unretractions(preview_data); - if (m_volumes.empty()) - reset_legend_texture(); - else + if (!m_volumes.empty()) { - _generate_legend_texture(preview_data, tool_colors); - // removes empty volumes m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(), [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end()); - _load_shells(); + _load_shells_fff(); } _update_toolpath_volumes_outside_state(); } _update_gcode_volumes_visibility(preview_data); _show_warning_texture_if_needed(); + + if (m_volumes.empty()) + reset_legend_texture(); + else + _generate_legend_texture(preview_data, tool_colors); + } +} + +void GLCanvas3D::load_sla_preview() +{ + const SLAPrint* print = this->sla_print(); + if ((m_canvas != nullptr) && (print != nullptr)) + { + _load_shells_sla(); } } void GLCanvas3D::load_preview(const std::vector& str_tool_colors) { - if (m_print == nullptr) + const Print *print = this->fff_print(); + if (print == nullptr) return; _load_print_toolpaths(); _load_wipe_tower_toolpaths(str_tool_colors); - for (const PrintObject* object : m_print->objects()) + for (const PrintObject* object : print->objects()) { if (object != nullptr) _load_print_object_toolpaths(*object, str_tool_colors); @@ -2661,186 +4592,6 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors) reset_legend_texture(); } -void GLCanvas3D::register_on_viewport_changed_callback(void* callback) -{ - if (callback != nullptr) - m_on_viewport_changed_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_double_click_callback(void* callback) -{ - if (callback != nullptr) - m_on_double_click_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_right_click_callback(void* callback) -{ - if (callback != nullptr) - m_on_right_click_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_select_object_callback(void* callback) -{ - if (callback != nullptr) - m_on_select_object_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_model_update_callback(void* callback) -{ - if (callback != nullptr) - m_on_model_update_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_remove_object_callback(void* callback) -{ - if (callback != nullptr) - m_on_remove_object_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_arrange_callback(void* callback) -{ - if (callback != nullptr) - m_on_arrange_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_rotate_object_left_callback(void* callback) -{ - if (callback != nullptr) - m_on_rotate_object_left_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_rotate_object_right_callback(void* callback) -{ - if (callback != nullptr) - m_on_rotate_object_right_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_scale_object_uniformly_callback(void* callback) -{ - if (callback != nullptr) - m_on_scale_object_uniformly_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_increase_objects_callback(void* callback) -{ - if (callback != nullptr) - m_on_increase_objects_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_decrease_objects_callback(void* callback) -{ - if (callback != nullptr) - m_on_decrease_objects_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_instance_moved_callback(void* callback) -{ - if (callback != nullptr) - m_on_instance_moved_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_wipe_tower_moved_callback(void* callback) -{ - if (callback != nullptr) - m_on_wipe_tower_moved_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_enable_action_buttons_callback(void* callback) -{ - if (callback != nullptr) - m_on_enable_action_buttons_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback) -{ - if (callback != nullptr) - m_on_gizmo_scale_uniformly_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_gizmo_rotate_callback(void* callback) -{ - if (callback != nullptr) - m_on_gizmo_rotate_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_gizmo_flatten_callback(void* callback) -{ - if (callback != nullptr) - m_on_gizmo_flatten_callback.register_callback(callback); -} - -void GLCanvas3D::register_on_update_geometry_info_callback(void* callback) -{ - if (callback != nullptr) - m_on_update_geometry_info_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_add_callback(void* callback) -{ - if (callback != nullptr) - m_action_add_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_delete_callback(void* callback) -{ - if (callback != nullptr) - m_action_delete_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_deleteall_callback(void* callback) -{ - if (callback != nullptr) - m_action_deleteall_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_arrange_callback(void* callback) -{ - if (callback != nullptr) - m_action_arrange_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_more_callback(void* callback) -{ - if (callback != nullptr) - m_action_more_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_fewer_callback(void* callback) -{ - if (callback != nullptr) - m_action_fewer_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_split_callback(void* callback) -{ - if (callback != nullptr) - m_action_split_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_cut_callback(void* callback) -{ - if (callback != nullptr) - m_action_cut_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_settings_callback(void* callback) -{ - if (callback != nullptr) - m_action_settings_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_layersediting_callback(void* callback) -{ - if (callback != nullptr) - m_action_layersediting_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_selectbyparts_callback(void* callback) -{ - if (callback != nullptr) - m_action_selectbyparts_callback.register_callback(callback); -} - void GLCanvas3D::bind_event_handlers() { if (m_canvas != nullptr) @@ -2928,31 +4679,51 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) // text input switch (keyCode) { + // key ESC + case 27: { m_gizmos.reset_all_states(); m_dirty = true; break; } // key + - case 43: { m_on_increase_objects_callback.call(); break; } + case 43: { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } // key - - case 45: { m_on_decrease_objects_callback.call(); break; } + case 45: { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } // key A/a case 65: - case 97: { m_on_arrange_callback.call(); break; } + case 97: { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } // key B/b case 66: case 98: { zoom_to_bed(); break; } - // key L/l - case 76: - case 108: { m_on_rotate_object_left_callback.call(); break; } - // key R/r - case 82: - case 114: { m_on_rotate_object_right_callback.call(); break; } - // key S/s - case 83: - case 115: { m_on_scale_object_uniformly_callback.call(); break; } + // key I/i + case 73: + case 105: { set_camera_zoom(1.0f); break; } + // key O/o + case 79: + case 111: { set_camera_zoom(-1.0f); break; } +#if ENABLE_MODIFIED_CAMERA_TARGET + // key Z/z + case 90: + case 122: + { + if (m_selection.is_empty()) + zoom_to_volumes(); + else + zoom_to_selection(); + + break; + } +#else // key Z/z case 90: case 122: { zoom_to_volumes(); break; } +#endif // ENABLE_MODIFIED_CAMERA_TARGET default: { - evt.Skip(); + if (m_gizmos.handle_shortcut(keyCode, m_selection)) + { + _update_gizmos_data(); + m_dirty = true; + } + else + evt.Skip(); + break; } } @@ -2970,7 +4741,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) // Performs layers editing updates, if enabled if (is_layers_editing_enabled()) { - int object_idx_selected = _get_first_selected_object_id(); + int object_idx_selected = m_selection.get_object_idx(); if (object_idx_selected != -1) { // A volume is selected. Test, whether hovering over a layer thickness bar. @@ -2988,18 +4759,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) // Calculate the zoom delta and apply it to the current zoom factor float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); - zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; - zoom = get_camera_zoom() / (1.0f - zoom); - - // Don't allow to zoom too far outside the scene. - float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); - if (zoom_min > 0.0f) - zoom = std::max(zoom, zoom_min * 0.8f); - - m_camera.zoom = zoom; - m_on_viewport_changed_callback.call(); - - _refresh_if_shown_on_screen(); + set_camera_zoom(zoom); } void GLCanvas3D::on_timer(wxTimerEvent& evt) @@ -3012,13 +4772,30 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) void GLCanvas3D::on_mouse(wxMouseEvent& evt) { +#if ENABLE_IMGUI + auto imgui = wxGetApp().imgui(); + if (imgui->update_mouse_data(evt)) { + render(); + if (imgui->want_any_input()) { + return; + } + } +#endif // ENABLE_IMGUI + Point pos(evt.GetX(), evt.GetY()); - int selected_object_idx = _get_first_selected_object_id(); + int selected_object_idx = m_selection.get_object_idx(); int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position, *this); +#else int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_REMOVE_TABS_FROM_PLATER + int view_toolbar_contains_mouse = (m_view_toolbar != nullptr) ? m_view_toolbar->contains_mouse(m_mouse.position, *this) : -1; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER if (evt.Entering()) { @@ -3036,18 +4813,25 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.position = Vec2d(-1.0, -1.0); m_dirty = true; } - else if (evt.LeftDClick() && (m_hover_volume_id != -1) && !gizmos_overlay_contains_mouse && (toolbar_contains_mouse == -1)) - m_on_double_click_callback.call(); else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) { m_toolbar_action_running = true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); +#else m_toolbar.do_action((unsigned int)toolbar_contains_mouse); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + } + else if (evt.LeftDClick() && (m_gizmos.get_current_type() != Gizmos::Undefined)) + { + m_mouse.ignore_up_event = true; } else if (evt.LeftDown() || evt.RightDown()) { + m_mouse.left_down = evt.LeftDown(); + // If user pressed left or right button we first check whether this happened // on a volume or not. - int volume_idx = m_hover_volume_id; m_layers_editing.state = LayersEditing::Unknown; if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) { @@ -3063,41 +4847,64 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // A volume is selected and the mouse is inside the reset button. // The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself, // therefore it is safe to call it while the background processing is running. - const_cast(m_print->get_object(layer_editing_object_idx))->reset_layer_height_profile(); + const_cast(this->fff_print()->get_object(layer_editing_object_idx))->reset_layer_height_profile(); // Index 2 means no editing, just wait for mouse up event. m_layers_editing.state = LayersEditing::Completed; m_dirty = true; } } - else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse) +#if !ENABLE_IMGUI + else if ((m_gizmos.get_current_type() == Gizmos::SlaSupports) && gizmo_reset_rect_contains(*this, pos(0), pos(1))) { - update_gizmos_data(); - m_gizmos.update_on_off_state(*this, m_mouse.position); + if (evt.LeftDown()) + { + m_gizmos.delete_current_grabber(true); + m_dirty = true; + } + } +#endif // not ENABLE_IMGUI + else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse) + { + m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection); + _update_gizmos_data(); m_dirty = true; } - else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse()) + else if (evt.LeftDown() && !m_selection.is_empty() && m_gizmos.grabber_contains_mouse()) { - update_gizmos_data(); - m_gizmos.start_dragging(_selected_volumes_bounding_box()); - m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx); + _update_gizmos_data(); + m_selection.start_dragging(); + m_gizmos.start_dragging(m_selection); if (m_gizmos.get_current_type() == Gizmos::Flatten) { // Rotate the object so the normal points downward: - Vec3d normal = m_gizmos.get_flattening_normal(); - if (normal(0) != 0.0 || normal(1) != 0.0 || normal(2) != 0.0) { - Vec3d axis = normal(2) > 0.999 ? Vec3d::UnitX() : normal.cross(-Vec3d::UnitZ()).normalized(); - float angle = acos(clamp(-1.0, 1.0, -normal(2))); - m_on_gizmo_flatten_callback.call(angle, (float)axis(0), (float)axis(1), (float)axis(2)); - } + m_selection.flattening_rotate(m_gizmos.get_flattening_normal()); + do_flatten(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); } m_dirty = true; } + else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse() && evt.RightDown()) { + if (m_gizmos.get_current_type() == Gizmos::SlaSupports) + m_gizmos.delete_current_grabber(); + } +#if ENABLE_REMOVE_TABS_FROM_PLATER + else if (view_toolbar_contains_mouse != -1) + { + if (m_view_toolbar != nullptr) + m_view_toolbar->do_action((unsigned int)view_toolbar_contains_mouse, *this); + } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER else if (toolbar_contains_mouse != -1) { m_toolbar_action_running = true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); +#else m_toolbar.do_action((unsigned int)toolbar_contains_mouse); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_mouse.left_down = false; } else { @@ -3105,51 +4912,45 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Don't deselect a volume if layer editing is enabled. We want the object to stay selected // during the scene manipulation. - if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled())) + if (m_picking_enabled && ((m_hover_volume_id != -1) || !is_layers_editing_enabled())) { - if (volume_idx != -1) + if (evt.LeftDown() && (m_hover_volume_id != -1)) { - deselect_volumes(); - select_volume(volume_idx); - int group_id = m_volumes.volumes[volume_idx]->select_group_id; - if (group_id != -1) + bool already_selected = m_selection.contains_volume(m_hover_volume_id); + bool shift_down = evt.ShiftDown(); + + if (already_selected && shift_down) + m_selection.remove(m_hover_volume_id); + else { - for (GLVolume* vol : m_volumes.volumes) - { - if ((vol != nullptr) && (vol->select_group_id == group_id)) - vol->selected = true; - } + bool add_as_single = !already_selected && !shift_down; + m_selection.add(m_hover_volume_id, add_as_single); } - update_gizmos_data(); + m_gizmos.update_on_off_state(m_selection); + _update_gizmos_data(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } } // propagate event through callback - if (m_picking_enabled && (volume_idx != -1)) - _on_select(volume_idx, selected_object_idx); - if (volume_idx != -1) + if (m_hover_volume_id != -1) { - if (evt.LeftDown() && m_moving_enabled) + if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1)) { - // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y, - // an converts the screen space coordinate to unscaled object space. - Vec3d pos3d = (volume_idx == -1) ? Vec3d(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos); - // Only accept the initial position, if it is inside the volume bounding box. - BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); + BoundingBoxf3 volume_bbox = m_volumes.volumes[m_hover_volume_id]->transformed_bounding_box(); volume_bbox.offset(1.0); - if (volume_bbox.contains(pos3d)) + if (volume_bbox.contains(m_mouse.scene_position)) { // The dragging operation is initiated. - m_mouse.drag.move_with_shift = evt.ShiftDown(); - m_mouse.drag.move_volume_idx = volume_idx; - m_mouse.drag.start_position_3D = pos3d; - // Remember the shift to to the object center.The object center will later be used - // to limit the object placement close to the bed. - m_mouse.drag.volume_center_offset = volume_bbox.center() - pos3d; + m_mouse.drag.move_volume_idx = m_hover_volume_id; + m_selection.start_dragging(); + m_mouse.drag.start_position_3D = m_mouse.scene_position; + m_moving = true; } } else if (evt.RightDown()) @@ -3161,8 +4962,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_hover_volume_id != -1) { // if right clicking on volume, propagate event through callback (shows context menu) - if (m_volumes.volumes[volume_idx]->hover) - m_on_right_click_callback.call(pos(0), pos(1)); + if (m_volumes.volumes[m_hover_volume_id]->hover && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower) + { + // forces the selection of the volume + m_selection.add(m_hover_volume_id); + m_gizmos.update_on_off_state(m_selection); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + _update_gizmos_data(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + // forces a frame render to update the view before the context menu is shown + render(); + post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, pos.cast())); + } } } } @@ -3175,48 +4986,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Get new position at the same Z of the initial click point. float z0 = 0.0f; float z1 = 1.0f; - Vec3d cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2)); + // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag + Vec3d cur_pos = m_selection.contains_volume(m_hover_volume_id) ? Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2)) : m_mouse.drag.start_position_3D; - // Clip the new position, so the object center remains close to the bed. - cur_pos += m_mouse.drag.volume_center_offset; - Point cur_pos2(scale_(cur_pos(0)), scale_(cur_pos(1))); - if (!m_bed.contains(cur_pos2)) - { - Point ip = m_bed.point_projection(cur_pos2); - cur_pos(0) = unscale(ip(0)); - cur_pos(1) = unscale(ip(1)); - } - cur_pos -= m_mouse.drag.volume_center_offset; - - // Calculate the translation vector. - Vec3d vector = cur_pos - m_mouse.drag.start_position_3D; - // Get the volume being dragged. - GLVolume* volume = m_volumes.volumes[m_mouse.drag.move_volume_idx]; - // Get all volumes belonging to the same group, if any. - std::vector volumes; - int group_id = m_mouse.drag.move_with_shift ? volume->select_group_id : volume->drag_group_id; - if (group_id == -1) - volumes.push_back(volume); - else - { - for (GLVolume* v : m_volumes.volumes) - { - if (v != nullptr) - { - if ((m_mouse.drag.move_with_shift && (v->select_group_id == group_id)) || (!m_mouse.drag.move_with_shift && (v->drag_group_id == group_id))) - volumes.push_back(v); - } - } - } - - // Apply new temporary volume origin and ignore Z. - for (GLVolume* v : volumes) - { - v->set_offset(v->get_offset() + Vec3d(vector(0), vector(1), 0.0)); - } - - update_position_values(volume->get_offset()); - m_mouse.drag.start_position_3D = cur_pos; + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); + wxGetApp().obj_manipul()->update_settings_value(m_selection); m_dirty = true; } @@ -3226,76 +5000,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_canvas->CaptureMouse(); m_mouse.dragging = true; - m_gizmos.update(mouse_ray(pos)); - - std::vector volumes; - if (m_mouse.drag.gizmo_volume_idx != -1) - { - GLVolume* volume = m_volumes.volumes[m_mouse.drag.gizmo_volume_idx]; - // Get all volumes belonging to the same group, if any. - if (volume->select_group_id == -1) - volumes.push_back(volume); - else - { - for (GLVolume* v : m_volumes.volumes) - { - if ((v != nullptr) && (v->select_group_id == volume->select_group_id)) - volumes.push_back(v); - } - } - } + m_gizmos.update(mouse_ray(pos), evt.ShiftDown(), &pos); switch (m_gizmos.get_current_type()) { case Gizmos::Move: { // Apply new temporary offset - GLVolume* volume = m_volumes.volumes[m_mouse.drag.gizmo_volume_idx]; - Vec3d offset = m_gizmos.get_position() - volume->get_offset(); - for (GLVolume* v : volumes) - { - v->set_offset(v->get_offset() + offset); - } - update_position_values(volume->get_offset()); + m_selection.translate(m_gizmos.get_displacement()); + wxGetApp().obj_manipul()->update_settings_value(m_selection); break; } case Gizmos::Scale: { - // Apply new temporary scale factor - float scale_factor = m_gizmos.get_scale(); - for (GLVolume* v : volumes) - { - v->set_scaling_factor((double)scale_factor); - } - update_scale_values((double)scale_factor); + // Apply new temporary scale factors + m_selection.scale(m_gizmos.get_scale(), evt.AltDown()); + wxGetApp().obj_manipul()->update_settings_value(m_selection); break; } case Gizmos::Rotate: { - // Apply new temporary angle_z - float angle_z = m_gizmos.get_angle_z(); - for (GLVolume* v : volumes) - { - v->set_rotation((double)angle_z); - } - update_rotation_value((double)angle_z, Z); + // Apply new temporary rotations + m_selection.rotate(m_gizmos.get_rotation(), evt.AltDown()); + wxGetApp().obj_manipul()->update_settings_value(m_selection); break; } default: break; } - if (!volumes.empty()) - { - BoundingBoxf3 bb; - for (const GLVolume* volume : volumes) - { - bb.merge(volume->transformed_bounding_box()); - } - const Vec3d& size = bb.size(); - m_on_update_geometry_info_callback.call(size(0), size(1), size(2), m_gizmos.get_scale()); - } - m_dirty = true; } else if (evt.Dragging() && !gizmos_overlay_contains_mouse) @@ -3316,7 +5049,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE); m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE); - m_on_viewport_changed_callback.call(); + viewport_changed(); m_dirty = true; } @@ -3331,9 +5064,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) float z = 0.0f; const Vec3d& cur_pos = _mouse_to_3d(pos, &z); Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); +#if ENABLE_CONSTRAINED_CAMERA_TARGET + m_camera.set_target(m_camera.get_target() + orig - cur_pos, *this); +#else m_camera.target += orig - cur_pos; +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET - m_on_viewport_changed_callback.call(); + viewport_changed(); m_dirty = true; } @@ -3349,40 +5086,37 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _stop_timer(); if (layer_editing_object_idx != -1) - m_on_model_update_callback.call(); + post_event(SimpleEvent(EVT_GLCANVAS_MODEL_UPDATE)); } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { - // get all volumes belonging to the same group, if any - std::vector volume_idxs; - int vol_id = m_mouse.drag.move_volume_idx; - int group_id = m_mouse.drag.move_with_shift ? m_volumes.volumes[vol_id]->select_group_id : m_volumes.volumes[vol_id]->drag_group_id; - if (group_id == -1) - volume_idxs.push_back(vol_id); - else - { - for (int i = 0; i < (int)m_volumes.volumes.size(); ++i) - { - if ((m_mouse.drag.move_with_shift && (m_volumes.volumes[i]->select_group_id == group_id)) || (m_volumes.volumes[i]->drag_group_id == group_id)) - volume_idxs.push_back(i); - } - } - - _on_move(volume_idxs); + m_regenerate_volumes = false; + do_move(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + // Let the platter know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + } + else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_hover_volume_id != -1) + { + int id = m_selection.get_object_idx(); - // force re-selection of the wipe tower, if needed - if ((volume_idxs.size() == 1) && m_volumes.volumes[volume_idxs[0]]->is_wipe_tower) - select_volume(volume_idxs[0]); + if ((id != -1) && (m_model != nullptr)) { + m_gizmos.clicked_on_object(Vec2d(pos(0), pos(1))); + } } else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) { // deselect and propagate event through callback - if (m_picking_enabled && !m_toolbar_action_running) + if (m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) { - deselect_volumes(); - _on_select(-1, -1); - update_gizmos_data(); + m_selection.clear(); + m_selection.set_mode(Selection::Instance); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + _update_gizmos_data(); } + m_mouse.ignore_up_event = false; } else if (evt.LeftUp() && m_gizmos.is_dragging()) { @@ -3390,47 +5124,48 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { case Gizmos::Move: { - // get all volumes belonging to the same group, if any - std::vector volume_idxs; - int vol_id = m_mouse.drag.gizmo_volume_idx; - int group_id = m_volumes.volumes[vol_id]->select_group_id; - if (group_id == -1) - volume_idxs.push_back(vol_id); - else - { - for (int i = 0; i < (int)m_volumes.volumes.size(); ++i) - { - if (m_volumes.volumes[i]->select_group_id == group_id) - volume_idxs.push_back(i); - } - } - - _on_move(volume_idxs); - + m_regenerate_volumes = false; + do_move(); break; } case Gizmos::Scale: { - m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale()); + do_scale(); break; } case Gizmos::Rotate: { - m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z()); + do_rotate(); break; } + case Gizmos::SlaSupports: + // End of mouse dragging, update the SLAPrint/SLAPrintObjects with the new support points. + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + break; default: break; } m_gizmos.stop_dragging(); - Slic3r::GUI::update_settings_value(); +#if ENABLE_WORLD_ROTATIONS + _update_gizmos_data(); +#endif // ENABLE_WORLD_ROTATIONS + + wxGetApp().obj_manipul()->update_settings_value(m_selection); + // Let the platter know that the dragging finished, so a delayed refresh + // of the scene with the background processing data should be performed. + post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); +#if ENABLE_CONSTRAINED_CAMERA_TARGET + m_camera.set_scene_box(scene_bounding_box(), *this); + set_camera_zoom(0.0f); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET } + m_moving = false; m_mouse.drag.move_volume_idx = -1; - m_mouse.drag.gizmo_volume_idx = -1; m_mouse.set_start_position_3D_as_invalid(); m_mouse.set_start_position_2D_as_invalid(); m_mouse.dragging = false; + m_mouse.left_down = false; m_toolbar_action_running = false; m_dirty = true; @@ -3439,7 +5174,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Moving()) { - m_mouse.position = Vec2d((double)pos(0), (double)pos(1)); + m_mouse.position = pos.cast(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + std::string tooltip = ""; + + // updates gizmos overlay + if (!m_selection.is_empty()) + tooltip = m_gizmos.update_hover_state(*this, m_mouse.position, m_selection); + else + m_gizmos.reset_all_states(); + + // updates toolbar overlay + if (tooltip.empty()) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + tooltip = m_toolbar.update_hover_state(m_mouse.position, *this); +#else + tooltip = m_toolbar.update_hover_state(m_mouse.position); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + + // updates view toolbar overlay + if (tooltip.empty() && (m_view_toolbar != nullptr)) + { + tooltip = m_view_toolbar->update_hover_state(m_mouse.position, *this); + if (!tooltip.empty()) + m_dirty = true; + } + + set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. if (m_picking_enabled) m_dirty = true; @@ -3460,16 +5223,14 @@ void GLCanvas3D::on_key_down(wxKeyEvent& evt) else { int key = evt.GetKeyCode(); - if (key == WXK_DELETE) - m_on_remove_object_callback.call(); - else - { #ifdef __WXOSX__ - if (key == WXK_BACK) - m_on_remove_object_callback.call(); -#endif - evt.Skip(); - } + if (key == WXK_BACK) +#else + if (key == WXK_DELETE) +#endif // __WXOSX__ + post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT)); + else + evt.Skip(); } } @@ -3495,16 +5256,290 @@ Point GLCanvas3D::get_local_mouse_position() const void GLCanvas3D::reset_legend_texture() { +#if !ENABLE_USE_UNIQUE_GLCONTEXT if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT m_legend_texture.reset(); } -void GLCanvas3D::set_tooltip(const std::string& tooltip) +void GLCanvas3D::set_tooltip(const std::string& tooltip) const { if (m_canvas != nullptr) - m_canvas->SetToolTip(tooltip); + { + wxToolTip* t = m_canvas->GetToolTip(); + if (t != nullptr) + { + if (t->GetTip() != tooltip) + t->SetTip(tooltip); + } + else + m_canvas->SetToolTip(tooltip); + } +} + +#if !ENABLE_IMGUI +void GLCanvas3D::set_external_gizmo_widgets_parent(wxWindow *parent) +{ + m_external_gizmo_widgets_parent = parent; +} +#endif // not ENABLE_IMGUI + +void GLCanvas3D::do_move() +{ + if (m_model == nullptr) + return; + + std::set> done; // keeps track of modified instances + bool object_moved = false; + Vec3d wipe_tower_origin = Vec3d::Zero(); + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) + { + int object_idx = v->object_idx(); + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + std::pair done_id(object_idx, instance_idx); + + if ((0 <= object_idx) && (object_idx < (int)m_model->objects.size())) + { + done.insert(done_id); + + // Move instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if (selection_mode == Selection::Instance) + { + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + object_moved = true; + } + else if (selection_mode == Selection::Volume) + { + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + object_moved = true; + } + if (object_moved) +#else + model_object->instances[instance_idx]->set_offset(v->get_offset()); + object_moved = true; +#endif // ENABLE_MODELVOLUME_TRANSFORM + model_object->invalidate_bounding_box(); + } + } + else if (object_idx == 1000) + // Move a wipe tower proxy. +#if ENABLE_MODELVOLUME_TRANSFORM + wipe_tower_origin = v->get_volume_offset(); +#else + wipe_tower_origin = v->get_offset(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) + { + ModelObject* m = m_model->objects[i.first]; + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + if (object_moved) + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); + + if (wipe_tower_origin != Vec3d::Zero()) + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); +} + +void GLCanvas3D::do_rotate() +{ + if (m_model == nullptr) + return; + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) + { + int object_idx = v->object_idx(); + if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes. + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if (selection_mode == Selection::Instance) + { + model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } + else if (selection_mode == Selection::Volume) + { + model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + } +#else + model_object->instances[instance_idx]->set_rotation(v->get_rotation()); + model_object->instances[instance_idx]->set_offset(v->get_offset()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) + { + ModelObject* m = m_model->objects[i.first]; + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLCanvas3D::do_scale() +{ + if (m_model == nullptr) + return; + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) + { + int object_idx = v->object_idx(); + if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Rotate instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if (selection_mode == Selection::Instance) + { + model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + } + else if (selection_mode == Selection::Volume) + { + model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); + model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); + model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); + } +#else + model_object->instances[instance_idx]->set_scaling_factor(v->get_scaling_factor()); + model_object->instances[instance_idx]->set_offset(v->get_offset()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) + { + ModelObject* m = m_model->objects[i.first]; + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLCanvas3D::do_flatten() +{ + do_rotate(); +} + +void GLCanvas3D::do_mirror() +{ + if (m_model == nullptr) + return; + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + + for (const GLVolume* v : m_volumes.volumes) + { + int object_idx = v->object_idx(); + if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + // Mirror instances/volumes + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) + { +#if ENABLE_MODELVOLUME_TRANSFORM + if (selection_mode == Selection::Instance) + model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); + else if (selection_mode == Selection::Volume) + model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); +#else + model_object->instances[instance_idx]->set_mirror(v->get_mirror()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + model_object->invalidate_bounding_box(); + } + } + + // Fixes sinking/flying instances + for (const std::pair& i : done) + { + ModelObject* m = m_model->objects[i.first]; + Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); + } + + post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLCanvas3D::set_camera_zoom(float zoom) +{ + zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; + zoom = get_camera_zoom() / (1.0f - zoom); + + // Don't allow to zoom too far outside the scene. + float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); + if (zoom_min > 0.0f) + zoom = std::max(zoom, zoom_min * 0.8f); + + m_camera.zoom = zoom; + viewport_changed(); + _refresh_if_shown_on_screen(); +} + +void GLCanvas3D::update_gizmos_on_off_state() +{ + set_as_dirty(); + m_gizmos.update_on_off_state(get_selection()); } bool GLCanvas3D::_is_shown_on_screen() const @@ -3523,7 +5558,24 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.is_enabled()) return true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + ItemsIconsTexture::Metadata icons_data; + icons_data.filename = "toolbar.png"; + icons_data.icon_size = 36; + icons_data.icon_border_size = 1; + icons_data.icon_gap_size = 1; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_toolbar.init(icons_data, background_data)) +#else if (!m_toolbar.init("toolbar.png", 36, 1, 1)) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { // unable to init the toolbar texture, disable it m_toolbar.set_enabled(false); @@ -3532,6 +5584,10 @@ bool GLCanvas3D::_init_toolbar() // m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_toolbar.set_border(5.0f); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_separator_size(5); m_toolbar.set_gap_size(2); @@ -3541,7 +5597,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Add..."); item.sprite_id = 0; item.is_toggable = false; - item.action_callback = &m_action_add_callback; + item.action_event = EVT_GLTOOLBAR_ADD; if (!m_toolbar.add_item(item)) return false; @@ -3549,7 +5605,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Delete"); item.sprite_id = 1; item.is_toggable = false; - item.action_callback = &m_action_delete_callback; + item.action_event = EVT_GLTOOLBAR_DELETE; if (!m_toolbar.add_item(item)) return false; @@ -3557,7 +5613,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Delete all"); item.sprite_id = 2; item.is_toggable = false; - item.action_callback = &m_action_deleteall_callback; + item.action_event = EVT_GLTOOLBAR_DELETE_ALL; if (!m_toolbar.add_item(item)) return false; @@ -3565,7 +5621,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Arrange"); item.sprite_id = 3; item.is_toggable = false; - item.action_callback = &m_action_arrange_callback; + item.action_event = EVT_GLTOOLBAR_ARRANGE; if (!m_toolbar.add_item(item)) return false; @@ -3576,7 +5632,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Add instance"); item.sprite_id = 4; item.is_toggable = false; - item.action_callback = &m_action_more_callback; + item.action_event = EVT_GLTOOLBAR_MORE; if (!m_toolbar.add_item(item)) return false; @@ -3584,56 +5640,37 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = GUI::L_str("Remove instance"); item.sprite_id = 5; item.is_toggable = false; - item.action_callback = &m_action_fewer_callback; + item.action_event = EVT_GLTOOLBAR_FEWER; if (!m_toolbar.add_item(item)) return false; if (!m_toolbar.add_separator()) return false; - item.name = "split"; - item.tooltip = GUI::L_str("Split"); + item.name = "splitobjects"; + item.tooltip = GUI::L_str("Split to objects"); item.sprite_id = 6; item.is_toggable = false; - item.action_callback = &m_action_split_callback; + item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS; if (!m_toolbar.add_item(item)) return false; - item.name = "cut"; - item.tooltip = GUI::L_str("Cut..."); - item.sprite_id = 7; + item.name = "splitvolumes"; + item.tooltip = GUI::L_str("Split to parts"); + item.sprite_id = 8; item.is_toggable = false; - item.action_callback = &m_action_cut_callback; + item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES; if (!m_toolbar.add_item(item)) return false; if (!m_toolbar.add_separator()) return false; - item.name = "settings"; - item.tooltip = GUI::L_str("Settings..."); - item.sprite_id = 8; - item.is_toggable = false; - item.action_callback = &m_action_settings_callback; - if (!m_toolbar.add_item(item)) - return false; - item.name = "layersediting"; item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 9; + item.sprite_id = 7; item.is_toggable = true; - item.action_callback = &m_action_layersediting_callback; - if (!m_toolbar.add_item(item)) - return false; - - if (!m_toolbar.add_separator()) - return false; - - item.name = "selectbyparts"; - item.tooltip = GUI::L_str("Select by parts"); - item.sprite_id = 10; - item.is_toggable = true; - item.action_callback = &m_action_selectbyparts_callback; + item.action_event = EVT_GLTOOLBAR_LAYERSEDITING; if (!m_toolbar.add_item(item)) return false; @@ -3642,13 +5679,31 @@ bool GLCanvas3D::_init_toolbar() return true; } +#if ENABLE_USE_UNIQUE_GLCONTEXT +bool GLCanvas3D::_set_current() +{ + if ((m_canvas != nullptr) && (m_context != nullptr)) + return m_canvas->SetCurrent(*m_context); + + return false; +} +#endif ENABLE_USE_UNIQUE_GLCONTEXT + void GLCanvas3D::_resize(unsigned int w, unsigned int h) { if ((m_canvas == nullptr) && (m_context == nullptr)) return; +#if ENABLE_IMGUI + wxGetApp().imgui()->set_display_size((float)w, (float)h); +#endif // ENABLE_IMGUI + // ensures that this canvas is current +#if ENABLE_USE_UNIQUE_GLCONTEXT + _set_current(); +#else set_current(); +#endif // ENABLE_USE_UNIQUE_GLCONTEXT ::glViewport(0, 0, w, h); ::glMatrixMode(GL_PROJECTION); @@ -3717,51 +5772,6 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box() const return bb; } -BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const -{ - BoundingBoxf3 bb; - - std::vector selected_volumes; - for (const GLVolume* volume : m_volumes.volumes) - { - if ((volume != nullptr) && !volume->is_wipe_tower && volume->selected) - selected_volumes.push_back(volume); - } - - bool use_drag_group_id = selected_volumes.size() > 1; - if (use_drag_group_id) - { - int drag_group_id = selected_volumes[0]->drag_group_id; - for (const GLVolume* volume : selected_volumes) - { - if (drag_group_id != volume->drag_group_id) - { - use_drag_group_id = false; - break; - } - } - } - - if (use_drag_group_id) - { - for (const GLVolume* volume : selected_volumes) - { - bb.merge(volume->bounding_box); - } - - bb = bb.transformed(selected_volumes[0]->world_matrix().cast()); - } - else - { - for (const GLVolume* volume : selected_volumes) - { - bb.merge(volume->transformed_bounding_box()); - } - } - - return bb; -} - void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) { // Calculate the zoom factor needed to adjust viewport to bounding box. @@ -3770,9 +5780,13 @@ void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) { m_camera.zoom = zoom; // center view around bounding box center +#if ENABLE_CONSTRAINED_CAMERA_TARGET + m_camera.set_target(bbox.center(), *this); +#else m_camera.target = bbox.center(); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET - m_on_viewport_changed_callback.call(); + viewport_changed(); _refresh_if_shown_on_screen(); } @@ -3845,56 +5859,22 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y); } -void GLCanvas3D::_deregister_callbacks() -{ - m_on_viewport_changed_callback.deregister_callback(); - m_on_double_click_callback.deregister_callback(); - m_on_right_click_callback.deregister_callback(); - m_on_select_object_callback.deregister_callback(); - m_on_model_update_callback.deregister_callback(); - m_on_remove_object_callback.deregister_callback(); - m_on_arrange_callback.deregister_callback(); - m_on_rotate_object_left_callback.deregister_callback(); - m_on_rotate_object_right_callback.deregister_callback(); - m_on_scale_object_uniformly_callback.deregister_callback(); - m_on_increase_objects_callback.deregister_callback(); - m_on_decrease_objects_callback.deregister_callback(); - m_on_instance_moved_callback.deregister_callback(); - m_on_wipe_tower_moved_callback.deregister_callback(); - m_on_enable_action_buttons_callback.deregister_callback(); - m_on_gizmo_scale_uniformly_callback.deregister_callback(); - m_on_gizmo_rotate_callback.deregister_callback(); - m_on_gizmo_flatten_callback.deregister_callback(); - m_on_update_geometry_info_callback.deregister_callback(); - - m_action_add_callback.deregister_callback(); - m_action_delete_callback.deregister_callback(); - m_action_deleteall_callback.deregister_callback(); - m_action_arrange_callback.deregister_callback(); - m_action_more_callback.deregister_callback(); - m_action_fewer_callback.deregister_callback(); - m_action_split_callback.deregister_callback(); - m_action_cut_callback.deregister_callback(); - m_action_settings_callback.deregister_callback(); - m_action_layersediting_callback.deregister_callback(); - m_action_selectbyparts_callback.deregister_callback(); -} - void GLCanvas3D::_mark_volumes_for_layer_height() const { - if (m_print == nullptr) + const Print *print = (m_process == nullptr) ? nullptr : m_process->fff_print(); + if (print == nullptr) return; for (GLVolume* vol : m_volumes.volumes) { - int object_id = int(vol->select_group_id / 1000000); + int object_id = vol->object_idx(); int shader_id = m_layers_editing.get_shader_program_id(); if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected && - vol->has_layer_height_texture() && (object_id < (int)m_print->objects().size())) + vol->has_layer_height_texture() && (object_id < (int)print->objects().size())) { vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id, - m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width); + print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width); } else vol->reset_layer_height_texture_data(); @@ -3926,15 +5906,19 @@ void GLCanvas3D::_camera_tranform() const ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw - Vec3d neg_target = - m_camera.target; - ::glTranslatef((GLfloat)neg_target(0), (GLfloat)neg_target(1), (GLfloat)neg_target(2)); +#if ENABLE_CONSTRAINED_CAMERA_TARGET + Vec3d target = -m_camera.get_target(); + ::glTranslated(target(0), target(1), target(2)); +#else + ::glTranslated(-m_camera.target(0), -m_camera.target(1), -m_camera.target(2)); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET } void GLCanvas3D::_picking_pass() const { const Vec2d& pos = m_mouse.position; - if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX))) + if (m_picking_enabled && !m_mouse.dragging && !m_mouse.left_down && (pos != Vec2d(DBL_MAX, DBL_MAX))) { // Render the object for picking. // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. @@ -3949,16 +5933,12 @@ void GLCanvas3D::_picking_pass() const ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _render_volumes(true); - m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box()); + m_gizmos.render_current_gizmo_for_picking_pass(m_selection); if (m_multisample_allowed) ::glEnable(GL_MULTISAMPLE); int volume_id = -1; - for (GLVolume* vol : m_volumes.volumes) - { - vol->hover = false; - } GLubyte color[4] = { 0, 0, 0, 0 }; const Size& cnv_size = get_canvas_size(); @@ -3972,16 +5952,6 @@ void GLCanvas3D::_picking_pass() const if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { m_hover_volume_id = volume_id; - m_volumes.volumes[volume_id]->hover = true; - int group_id = m_volumes.volumes[volume_id]->select_group_id; - if (group_id != -1) - { - for (GLVolume* vol : m_volumes.volumes) - { - if (vol->select_group_id == group_id) - vol->hover = true; - } - } m_gizmos.set_hover_id(-1); } else @@ -3990,38 +5960,48 @@ void GLCanvas3D::_picking_pass() const m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1); } + _update_volumes_hover_state(); + +#if !ENABLE_REMOVE_TABS_FROM_PLATER // updates gizmos overlay - if (_get_first_selected_object_id() != -1) - m_gizmos.update_hover_state(*this, pos); + if (!m_selection.is_empty()) + { + std::string name = m_gizmos.update_hover_state(*this, pos, m_selection); + if (!name.empty()) + set_tooltip(name); + } else m_gizmos.reset_all_states(); m_toolbar.update_hover_state(pos); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER } } void GLCanvas3D::_render_background() const { - ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - ::glPushMatrix(); ::glLoadIdentity(); ::glMatrixMode(GL_PROJECTION); ::glPushMatrix(); ::glLoadIdentity(); - // Draws a bluish bottom to top gradient over the complete screen. + // Draws a bottom to top gradient over the complete screen. ::glDisable(GL_DEPTH_TEST); ::glBegin(GL_QUADS); - ::glColor3f(0.0f, 0.0f, 0.0f); + if (m_dynamic_background_enabled && _is_any_volume_outside()) + ::glColor3fv(ERROR_BG_DARK_COLOR); + else + ::glColor3fv(DEFAULT_BG_DARK_COLOR); + ::glVertex2f(-1.0f, -1.0f); ::glVertex2f(1.0f, -1.0f); if (m_dynamic_background_enabled && _is_any_volume_outside()) - ::glColor3f(ERROR_BG_COLOR[0], ERROR_BG_COLOR[1], ERROR_BG_COLOR[2]); + ::glColor3fv(ERROR_BG_LIGHT_COLOR); else - ::glColor3f(DEFAULT_BG_COLOR[0], DEFAULT_BG_COLOR[1], DEFAULT_BG_COLOR[2]); + ::glColor3fv(DEFAULT_BG_LIGHT_COLOR); ::glVertex2f(1.0f, 1.0f); ::glVertex2f(-1.0f, 1.0f); @@ -4070,6 +6050,11 @@ void GLCanvas3D::_render_objects() const ::glDisable(GL_CULL_FACE); } + if (m_use_clipping_planes) + m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); + else + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + m_shader.start_using(); m_volumes.render_VBOs(); m_shader.stop_using(); @@ -4079,6 +6064,14 @@ void GLCanvas3D::_render_objects() const } else { + if (m_use_clipping_planes) + { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data()); + ::glEnable(GL_CLIP_PLANE0); + ::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data()); + ::glEnable(GL_CLIP_PLANE1); + } + // do not cull backfaces to show broken geometry, if any if (m_picking_enabled) ::glDisable(GL_CULL_FACE); @@ -4087,14 +6080,21 @@ void GLCanvas3D::_render_objects() const if (m_picking_enabled) ::glEnable(GL_CULL_FACE); + + if (m_use_clipping_planes) + { + ::glDisable(GL_CLIP_PLANE0); + ::glDisable(GL_CLIP_PLANE1); + } } ::glDisable(GL_LIGHTING); } -void GLCanvas3D::_render_cutting_plane() const +void GLCanvas3D::_render_selection() const { - m_cutting_plane.render(volumes_bounding_box()); + if (!m_gizmos.is_running()) + m_selection.render(); } void GLCanvas3D::_render_warning_texture() const @@ -4115,7 +6115,8 @@ void GLCanvas3D::_render_legend_texture() const void GLCanvas3D::_render_layer_editing_overlay() const { - if (m_print == nullptr) + const Print *print = this->fff_print(); + if ((print == nullptr) || print->objects().empty()) return; GLVolume* volume = nullptr; @@ -4134,11 +6135,11 @@ void GLCanvas3D::_render_layer_editing_overlay() const // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion // and an update by Platter::async_apply_config. - int object_idx = int(volume->select_group_id / 1000000); - if ((int)m_print->objects().size() < object_idx) + int object_idx = volume->object_idx(); + if ((int)print->objects().size() <= object_idx) return; - const PrintObject* print_object = m_print->get_object(object_idx); + const PrintObject* print_object = print->get_object(object_idx); if (print_object == nullptr) return; @@ -4175,10 +6176,12 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const else { vol->set_render_color(); - ::glColor4f(vol->render_color[0], vol->render_color[1], vol->render_color[2], vol->render_color[3]); + ::glColor4fv(vol->render_color); } - vol->render(); + if (!fake_colors || !vol->disabled) + vol->render(); + ++volume_id; } @@ -4194,18 +6197,386 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const void GLCanvas3D::_render_current_gizmo() const { - m_gizmos.render_current_gizmo(_selected_volumes_bounding_box()); + m_gizmos.render_current_gizmo(m_selection); } void GLCanvas3D::_render_gizmos_overlay() const { - m_gizmos.render_overlay(*this); + m_gizmos.render_overlay(*this, m_selection); } void GLCanvas3D::_render_toolbar() const { +#if !ENABLE_REMOVE_TABS_FROM_PLATER _resize_toolbar(); +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.render(*this); +#else m_toolbar.render(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +void GLCanvas3D::_render_view_toolbar() const +{ + if (m_view_toolbar != nullptr) + m_view_toolbar->render(*this); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +#if ENABLE_SHOW_CAMERA_TARGET +void GLCanvas3D::_render_camera_target() const +{ + double half_length = 5.0; + + ::glDisable(GL_DEPTH_TEST); + + ::glLineWidth(2.0f); + ::glBegin(GL_LINES); +#if ENABLE_CONSTRAINED_CAMERA_TARGET + const Vec3d& target = m_camera.get_target(); + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3d(target(0) - half_length, target(1), target(2)); + ::glVertex3d(target(0) + half_length, target(1), target(2)); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3d(target(0), target(1) - half_length, target(2)); + ::glVertex3d(target(0), target(1) + half_length, target(2)); + ::glEnd(); + + ::glBegin(GL_LINES); + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3d(target(0), target(1), target(2) - half_length); + ::glVertex3d(target(0), target(1), target(2) + half_length); +#else + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3d(m_camera.target(0) - half_length, m_camera.target(1), m_camera.target(2)); + ::glVertex3d(m_camera.target(0) + half_length, m_camera.target(1), m_camera.target(2)); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3d(m_camera.target(0), m_camera.target(1) - half_length, m_camera.target(2)); + ::glVertex3d(m_camera.target(0), m_camera.target(1) + half_length, m_camera.target(2)); + ::glEnd(); + + ::glBegin(GL_LINES); + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3d(m_camera.target(0), m_camera.target(1), m_camera.target(2) - half_length); + ::glVertex3d(m_camera.target(0), m_camera.target(1), m_camera.target(2) + half_length); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET + ::glEnd(); +} +#endif // ENABLE_SHOW_CAMERA_TARGET + +void GLCanvas3D::_render_sla_slices() const +{ + if (!m_use_clipping_planes || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + return; + + const SLAPrint* print = this->sla_print(); + const PrintObjects& print_objects = print->objects(); + if (print_objects.empty()) + // nothing to render, return + return; + + double clip_min_z = -m_clipping_planes[0].get_data()[3]; + double clip_max_z = m_clipping_planes[1].get_data()[3]; + for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) + { + const SLAPrintObject* obj = print_objects[i]; + + Pointf3s bottom_obj_triangles; + Pointf3s bottom_sup_triangles; + Pointf3s top_obj_triangles; + Pointf3s top_sup_triangles; + + double shift_z = obj->get_current_elevation(); + double min_z = clip_min_z - shift_z; + double max_z = clip_max_z - shift_z; + + if (m_sla_caps[0].matches(min_z)) + { + SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[0].triangles.find(i); + if (it != m_sla_caps[0].triangles.end()) + { + bottom_obj_triangles = it->second.object; + bottom_sup_triangles = it->second.suppports; + } + } + + if (m_sla_caps[1].matches(max_z)) + { + SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[1].triangles.find(i); + if (it != m_sla_caps[1].triangles.end()) + { + top_obj_triangles = it->second.object; + top_sup_triangles = it->second.suppports; + } + } + + const std::vector& instances = obj->instances(); + struct InstanceTransform + { + Vec3d offset; + float rotation; + }; + + std::vector instance_transforms; + for (const SLAPrintObject::Instance& inst : instances) + { + instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) }); + } + + if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && obj->is_step_done(slaposIndexSlices)) + { + const std::vector& model_slices = obj->get_model_slices(); + const std::vector& support_slices = obj->get_support_slices(); + + const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); + SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; }); + SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; }); + + if (it_min_z != index.end()) + { + if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) + { + // calculate model bottom cap + const ExPolygons& polys = model_slices[it_min_z->second.model_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 2; v >= 0; --v) + { + bottom_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); + } + } + } + } + + if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) + { + // calculate support bottom cap + const ExPolygons& polys = support_slices[it_min_z->second.support_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 2; v >= 0; --v) + { + bottom_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); + } + } + } + + m_sla_caps[0].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { bottom_obj_triangles, bottom_sup_triangles })); + m_sla_caps[0].z = min_z; + } + } + + if (it_max_z != index.end()) + { + if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) + { + // calculate model top cap + const ExPolygons& polys = model_slices[it_max_z->second.model_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 0; v < 3; ++v) + { + top_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); + } + } + } + } + + if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) + { + // calculate support top cap + const ExPolygons& polys = support_slices[it_max_z->second.support_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 0; v < 3; ++v) + { + top_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); + } + } + } + } + + m_sla_caps[1].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { top_obj_triangles, top_sup_triangles })); + m_sla_caps[1].z = max_z; + } + } + + if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) + { + for (const InstanceTransform& inst : instance_transforms) + { + ::glPushMatrix(); + ::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2)); + ::glRotatef(inst.rotation, 0.0, 0.0, 1.0); + + ::glBegin(GL_TRIANGLES); + + ::glColor3f(1.0f, 0.37f, 0.0f); + + for (const Vec3d& v : bottom_obj_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + for (const Vec3d& v : top_obj_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + ::glColor3f(1.0f, 0.0f, 0.37f); + + for (const Vec3d& v : bottom_sup_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + for (const Vec3d& v : top_sup_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + ::glEnd(); + + ::glPopMatrix(); + } + } + } +} + +void GLCanvas3D::_update_volumes_hover_state() const +{ + for (GLVolume* v : m_volumes.volumes) + { + v->hover = false; + } + + if (m_hover_volume_id == -1) + return; + + GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; + + switch (m_selection.get_mode()) + { + case Selection::Volume: + { + volume->hover = true; + break; + } + case Selection::Instance: + { + int object_idx = volume->object_idx(); + int instance_idx = volume->instance_idx(); + + for (GLVolume* v : m_volumes.volumes) + { + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + v->hover = true; + } + + break; + } + } +} + +void GLCanvas3D::_update_gizmos_data() +{ + if (!m_gizmos.is_enabled()) + return; + + bool enable_move_z = !m_selection.is_wipe_tower(); + m_gizmos.enable_grabber(Gizmos::Move, 2, enable_move_z); + bool enable_scale_xyz = m_selection.is_single_full_instance() || m_selection.is_single_volume() || m_selection.is_single_modifier(); + for (int i = 0; i < 6; ++i) + { + m_gizmos.enable_grabber(Gizmos::Scale, i, enable_scale_xyz); + } + + if (m_selection.is_single_full_instance()) + { +#if ENABLE_MODELVOLUME_TRANSFORM + // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first + const GLVolume* volume = m_volumes.volumes[*m_selection.get_volume_idxs().begin()]; + m_gizmos.set_scale(volume->get_instance_scaling_factor()); +#if ENABLE_WORLD_ROTATIONS + m_gizmos.set_rotation(Vec3d::Zero()); +#else + m_gizmos.set_rotation(volume->get_instance_rotation()); +#endif // ENABLE_WORLD_ROTATIONS + ModelObject* model_object = m_model->objects[m_selection.get_object_idx()]; + m_gizmos.set_flattening_data(model_object); +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + m_gizmos.set_sla_support_data(model_object, m_selection); +#else + m_gizmos.set_model_object_ptr(model_object); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD +#else + ModelObject* model_object = m_model->objects[m_selection.get_object_idx()]; + ModelInstance* model_instance = model_object->instances[m_selection.get_instance_idx()]; + m_gizmos.set_scale(model_instance->get_scaling_factor()); +#if ENABLE_WORLD_ROTATIONS + m_gizmos.set_rotation(Vec3d::Zero()); +#else + m_gizmos.set_rotation(model_instance->get_rotation()); +#endif // ENABLE_WORLD_ROTATIONS + m_gizmos.set_flattening_data(model_object); +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + m_gizmos.set_sla_support_data(model_object, m_selection); +#else + m_gizmos.set_model_object_ptr(model_object); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD +#endif // ENABLE_MODELVOLUME_TRANSFORM + } +#if ENABLE_MODELVOLUME_TRANSFORM + else if (m_selection.is_single_volume() || m_selection.is_single_modifier()) + { + const GLVolume* volume = m_volumes.volumes[*m_selection.get_volume_idxs().begin()]; + m_gizmos.set_scale(volume->get_volume_scaling_factor()); +#if ENABLE_WORLD_ROTATIONS + m_gizmos.set_rotation(Vec3d::Zero()); +#else + m_gizmos.set_rotation(volume->get_volume_rotation()); +#endif // ENABLE_WORLD_ROTATIONS + m_gizmos.set_flattening_data(nullptr); +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + m_gizmos.set_sla_support_data(nullptr, m_selection); +#else + m_gizmos.set_model_object_ptr(nullptr); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + } +#endif // ENABLE_MODELVOLUME_TRANSFORM + else + { + m_gizmos.set_scale(Vec3d::Ones()); + m_gizmos.set_rotation(Vec3d::Zero()); + m_gizmos.set_flattening_data(m_selection.is_from_single_object() ? m_model->objects[m_selection.get_object_idx()] : nullptr); +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + m_gizmos.set_sla_support_data(nullptr, m_selection); +#else + m_gizmos.set_model_object_ptr(nullptr); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + } } float GLCanvas3D::_get_layers_editing_cursor_z_relative() const @@ -4219,10 +6590,11 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) if (object_idx_selected == -1) return; - if (m_print == nullptr) + const Print *print = this->fff_print(); + if (print == nullptr) return; - const PrintObject* selected_obj = m_print->get_object(object_idx_selected); + const PrintObject* selected_obj = print->get_object(object_idx_selected); if (selected_obj == nullptr) return; @@ -4245,7 +6617,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) int volume_idx = 0; for (int i = 0; i < object_idx_selected; ++i) { - const PrintObject* obj = m_print->get_object(i); + const PrintObject* obj = print->get_object(i); if (obj != nullptr) { for (int j = 0; j < (int)obj->region_volumes.size(); ++j) @@ -4302,79 +6674,46 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) void GLCanvas3D::_start_timer() { - if (m_timer != nullptr) - m_timer->Start(100, wxTIMER_CONTINUOUS); + m_timer.Start(100, wxTIMER_CONTINUOUS); } void GLCanvas3D::_stop_timer() { - if (m_timer != nullptr) - m_timer->Stop(); -} - -int GLCanvas3D::_get_first_selected_object_id() const -{ - if (m_print != nullptr) - { - int objects_count = (int)m_print->objects().size(); - - for (const GLVolume* vol : m_volumes.volumes) - { - if ((vol != nullptr) && vol->selected) - { - int object_id = vol->select_group_id / 1000000; - // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. - if (object_id < 10000) - return (object_id >= objects_count) ? -1 : object_id; - } - } - } - return -1; -} - -int GLCanvas3D::_get_first_selected_volume_id(int object_id) const -{ - int volume_id = -1; - - for (const GLVolume* vol : m_volumes.volumes) - { - ++volume_id; - if ((vol != nullptr) && vol->selected && (object_id == vol->select_group_id / 1000000)) - return volume_id; - } - - return -1; + m_timer.Stop(); } void GLCanvas3D::_load_print_toolpaths() { +#if !ENABLE_USE_UNIQUE_GLCONTEXT // ensures this canvas is current if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT - if (m_print == nullptr) + const Print *print = this->fff_print(); + if (print == nullptr) return; - if (!m_print->is_step_done(psSkirt) || !m_print->is_step_done(psBrim)) + if (!print->is_step_done(psSkirt) || !print->is_step_done(psBrim)) return; - if (!m_print->has_skirt() && (m_print->config().brim_width.value == 0)) + if (!print->has_skirt() && (print->config().brim_width.value == 0)) return; const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish // number of skirt layers size_t total_layer_count = 0; - for (const PrintObject* print_object : m_print->objects()) + for (const PrintObject* print_object : print->objects()) { total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); } - size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min(m_print->config().skirt_height.value, total_layer_count); - if ((skirt_height == 0) && (m_print->config().brim_width.value > 0)) + size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); + if ((skirt_height == 0) && (print->config().brim_width.value > 0)) skirt_height = 1; // get first skirt_height layers (maybe this should be moved to a PrintObject method?) - const PrintObject* object0 = m_print->objects().front(); + const PrintObject* object0 = print->objects().front(); std::vector print_zs; print_zs.reserve(skirt_height * 2); for (size_t i = 0; i < std::min(skirt_height, object0->layers().size()); ++i) @@ -4397,9 +6736,9 @@ void GLCanvas3D::_load_print_toolpaths() volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); if (i == 0) - _3DScene::extrusionentity_to_verts(m_print->brim(), print_zs[i], Point(0, 0), volume); + _3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), volume); - _3DScene::extrusionentity_to_verts(m_print->skirt(), print_zs[i], Point(0, 0), volume); + _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } volume.bounding_box = volume.indexed_vertex_array.bounding_box(); volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); @@ -4471,7 +6810,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { - std::vector vols; + GLVolumePtrs vols; if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++i) vols.emplace_back(new_volume(ctxt.color_tool(i))); @@ -4561,10 +6900,11 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) { - if ((m_print == nullptr) || m_print->wipe_tower_data().tool_changes.empty()) + const Print *print = this->fff_print(); + if ((print == nullptr) || print->wipe_tower_data().tool_changes.empty()) return; - if (!m_print->is_step_done(psWipeTower)) + if (!print->is_step_done(psWipeTower)) return; std::vector tool_colors = _parse_colors(str_tool_colors); @@ -4601,12 +6941,12 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::vector final; } ctxt; - ctxt.print = m_print; + ctxt.print = print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (m_print->wipe_tower_data().priming && m_print->config().single_extruder_multi_material_priming) - ctxt.priming.emplace_back(*m_print->wipe_tower_data().priming.get()); - if (m_print->wipe_tower_data().final_purge) - ctxt.final.emplace_back(*m_print->wipe_tower_data().final_purge.get()); + if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) + ctxt.priming.emplace_back(*print->wipe_tower_data().priming.get()); + if (print->wipe_tower_data().final_purge) + ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); @@ -4614,7 +6954,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. - size_t n_items = m_print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); size_t grain_size = std::max(n_items / 128, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { @@ -4630,7 +6970,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ tbb::blocked_range(0, n_items, grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { // Bounding box of this slab of a wipe tower. - std::vector vols; + GLVolumePtrs vols; if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++i) vols.emplace_back(new_volume(ctxt.color_tool(i))); @@ -4759,6 +7099,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat case GCodePreviewData::Extrusion::Tool: case GCodePreviewData::Extrusion::Filament: return (float)path.extruder_id; + case GCodePreviewData::Extrusion::ColorPrint: + return (float)path.cp_color_id; default: return 0.0f; } @@ -4787,6 +7129,15 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); return color; } + case GCodePreviewData::Extrusion::ColorPrint: + { + int val = int(value); + while (val >= GCodePreviewData::Range::Colors_Count) + val -= GCodePreviewData::Range::Colors_Count; + + GCodePreviewData::Color color = GCodePreviewData::Range::Default_Colors[val]; + return color; + } default: return GCodePreviewData::Color::Dummy; } @@ -4858,9 +7209,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat m_gcode_preview_volume_index.first_volumes.pop_back(); if (initial_volumes_count != m_volumes.volumes.size()) { - std::vector::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; - std::vector::iterator end = m_volumes.volumes.end(); - for (std::vector::iterator it = begin; it < end; ++it) + GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + GLVolumePtrs::iterator end = m_volumes.volumes.end(); + for (GLVolumePtrs::iterator it = begin; it < end; ++it) { GLVolume* volume = *it; delete volume; @@ -4932,9 +7283,9 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, // an error occourred - restore to previous state and return if (initial_volumes_count != m_volumes.volumes.size()) { - std::vector::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; - std::vector::iterator end = m_volumes.volumes.end(); - for (std::vector::iterator it = begin; it < end; ++it) + GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + GLVolumePtrs::iterator end = m_volumes.volumes.end(); + for (GLVolumePtrs::iterator it = begin; it < end; ++it) { GLVolume* volume = *it; delete volume; @@ -5217,18 +7568,19 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) } } -void GLCanvas3D::_load_shells() +void GLCanvas3D::_load_shells_fff() { size_t initial_volumes_count = m_volumes.volumes.size(); m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count); - if (m_print->objects().empty()) + const Print *print = this->fff_print(); + if (print->objects().empty()) // nothing to render, return return; // adds objects' volumes - unsigned int object_id = 0; - for (const PrintObject* obj : m_print->objects()) + int object_id = 0; + for (const PrintObject* obj : print->objects()) { const ModelObject* model_obj = obj->model_object(); @@ -5238,33 +7590,127 @@ void GLCanvas3D::_load_shells() instance_ids[i] = i; } - m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized); + m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized); ++object_id; } - // adds wipe tower's volume - double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2); - const PrintConfig& config = m_print->config(); - unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - float depth = m_print->get_wipe_tower_depth(); - if (!m_print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; - m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { + // adds wipe tower's volume + double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2); + const PrintConfig& config = print->config(); + unsigned int extruders_count = config.nozzle_diameter.size(); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + float depth = print->get_wipe_tower_depth(); + if (!print->is_step_done(psWipeTower)) + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + } } } +void GLCanvas3D::_load_shells_sla() +{ + const SLAPrint* print = this->sla_print(); + if (print->objects().empty()) + // nothing to render, return + return; + + // adds objects' volumes + int obj_idx = 0; + for (const SLAPrintObject* obj : print->objects()) + { + if (!obj->is_step_done(slaposIndexSlices)) + continue; + + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + + const ModelObject* model_obj = obj->model_object(); + std::vector instance_idxs(model_obj->instances.size()); + for (int i = 0; i < (int)model_obj->instances.size(); ++i) + { + instance_idxs[i] = i; + } + + m_volumes.load_object(model_obj, obj_idx, instance_idxs, "object", m_use_VBOs && m_initialized); + + const std::vector& instances = obj->instances(); + + for (const SLAPrintObject::Instance& instance : instances) + { + Vec3d offset = unscale(instance.shift(0), instance.shift(1), 0); + Vec3d rotation(0.0, 0.0, (double)instance.rotation); + + unsigned int partial_volumes_count = (unsigned int)m_volumes.volumes.size(); + + // add supports + if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) + { + const TriangleMesh& mesh = obj->support_mesh(); + m_volumes.volumes.emplace_back(new GLVolume(GLVolume::SLA_SUPPORT_COLOR)); + GLVolume& v = *m_volumes.volumes.back(); + + if (m_use_VBOs) + v.indexed_vertex_array.load_mesh_full_shading(mesh); + else + v.indexed_vertex_array.load_mesh_flat_shading(mesh); + + v.shader_outside_printer_detection_enabled = true; + v.composite_id.volume_id = -1; + v.set_instance_offset(offset); + v.set_instance_rotation(rotation); + } + + // add pad + if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) + { + const TriangleMesh& mesh = obj->pad_mesh(); + m_volumes.volumes.emplace_back(new GLVolume(GLVolume::SLA_PAD_COLOR)); + GLVolume& v = *m_volumes.volumes.back(); + + if (m_use_VBOs) + v.indexed_vertex_array.load_mesh_full_shading(mesh); + else + v.indexed_vertex_array.load_mesh_flat_shading(mesh); + + v.shader_outside_printer_detection_enabled = false; + v.composite_id.volume_id = -1; + v.set_instance_offset(offset); + v.set_instance_rotation(rotation); + } + + // finalize volumes and sends geometry to gpu + for (unsigned int i = partial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + GLVolume& v = *m_volumes.volumes[i]; + v.bounding_box = v.indexed_vertex_array.bounding_box(); + v.indexed_vertex_array.finalize_geometry(m_use_VBOs); + } + + ++obj_idx; + } + + // apply shift z + double shift_z = obj->get_current_elevation(); + for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + m_volumes.volumes[i]->set_sla_shift_z(shift_z); + } + } + + update_volumes_colors_by_extruder(); +} + void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) { unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); for (unsigned int i = 0; i < size; ++i) { - std::vector::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id; - std::vector::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end(); + GLVolumePtrs::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id; + GLVolumePtrs::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end(); - for (std::vector::iterator it = begin; it != end; ++it) + for (GLVolumePtrs::iterator it = begin; it != end; ++it) { GLVolume* volume = *it; @@ -5353,89 +7799,6 @@ void GLCanvas3D::_show_warning_texture_if_needed() } } -void GLCanvas3D::_on_move(const std::vector& volume_idxs) -{ - if (m_model == nullptr) - return; - - std::set done; // prevent moving instances twice - bool object_moved = false; - Vec3d wipe_tower_origin = Vec3d::Zero(); - for (int volume_idx : volume_idxs) - { - GLVolume* volume = m_volumes.volumes[volume_idx]; - int obj_idx = volume->object_idx(); - int instance_idx = volume->instance_idx(); - - // prevent moving instances twice - char done_id[64]; - ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); - if (done.find(done_id) != done.end()) - continue; - - done.insert(done_id); - - if (obj_idx < 1000) - { - // Move a regular object. - ModelObject* model_object = m_model->objects[obj_idx]; - if (model_object != nullptr) - { -#if ENABLE_MODELINSTANCE_3D_OFFSET - model_object->instances[instance_idx]->set_offset(volume->get_offset()); -#else - const Vec3d& offset = volume->get_offset(); - model_object->instances[instance_idx]->offset = Vec2d(offset(0), offset(1)); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - model_object->invalidate_bounding_box(); - update_position_values(); - object_moved = true; - } - } - else if (obj_idx == 1000) - // Move a wipe tower proxy. - wipe_tower_origin = volume->get_offset(); - } - - if (object_moved) - m_on_instance_moved_callback.call(); - - if (wipe_tower_origin != Vec3d::Zero()) - m_on_wipe_tower_moved_callback.call(wipe_tower_origin(0), wipe_tower_origin(1)); -} - -void GLCanvas3D::_on_select(int volume_idx, int object_idx) -{ - int vol_id = -1; - int obj_id = -1; - - if ((volume_idx != -1) && (volume_idx < (int)m_volumes.volumes.size())) - { - if (m_select_by == "volume") - { - if (m_volumes.volumes[volume_idx]->object_idx() != object_idx) - { - set_select_by("object"); - obj_id = m_volumes.volumes[volume_idx]->object_idx(); - vol_id = -1; - } - else - { - obj_id = object_idx; - vol_id = m_volumes.volumes[volume_idx]->volume_idx(); - } - } - else if (m_select_by == "object") - { - obj_id = m_volumes.volumes[volume_idx]->object_idx(); - vol_id = -1; - } - } - - m_on_select_object_callback.call(obj_id, vol_id); - Slic3r::GUI::select_current_volume(obj_id, vol_id); -} - std::vector GLCanvas3D::_parse_colors(const std::vector& colors) { static const float INV_255 = 1.0f / 255.0f; @@ -5463,24 +7826,30 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { +#if !ENABLE_USE_UNIQUE_GLCONTEXT if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT - m_legend_texture.generate(preview_data, tool_colors); + m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside()); } void GLCanvas3D::_generate_warning_texture(const std::string& msg) { +#if !ENABLE_USE_UNIQUE_GLCONTEXT if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT m_warning_texture.generate(msg); } void GLCanvas3D::_reset_warning_texture() { +#if !ENABLE_USE_UNIQUE_GLCONTEXT if (!set_current()) return; +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT m_warning_texture.reset(); } @@ -5496,35 +7865,88 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +void GLCanvas3D::_resize_toolbars() const +#else void GLCanvas3D::_resize_toolbar() const +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { Size cnv_size = get_canvas_size(); float zoom = get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + switch (m_toolbar.get_layout_type()) { default: case GLToolbar::Layout::Horizontal: { // centers the toolbar on the top edge of the 3d scene - unsigned int toolbar_width = m_toolbar.get_width(); - float top = (0.5f * (float)cnv_size.get_height() - 2.0f) * inv_zoom; - float left = -0.5f * (float)toolbar_width * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float top, left; + if (orientation == GLToolbar::Layout::Top) + { + top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } + else + { + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } +#else + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = -0.5f * m_toolbar.get_width() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_position(top, left); break; } case GLToolbar::Layout::Vertical: { // centers the toolbar on the right edge of the 3d scene - unsigned int toolbar_width = m_toolbar.get_width(); - unsigned int toolbar_height = m_toolbar.get_height(); - float top = 0.5f * (float)toolbar_height * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - toolbar_width - 2.0f) * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float top, left; + if (orientation == GLToolbar::Layout::Left) + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + } + else + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; + } +#else + float top = 0.5f * m_toolbar.get_height() * inv_zoom; + float left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_position(top, left); break; } } + +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (m_view_toolbar != nullptr) + { + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + m_view_toolbar->set_position(top, left); + } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +const Print* GLCanvas3D::fff_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->fff_print(); +} + +const SLAPrint* GLCanvas3D::sla_print() const +{ + return (m_process == nullptr) ? nullptr : m_process->sla_print(); } } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 528f73fc1..fd732ddf1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1,21 +1,31 @@ #ifndef slic3r_GLCanvas3D_hpp_ #define slic3r_GLCanvas3D_hpp_ -#include "../../slic3r/GUI/3DScene.hpp" -#include "../../slic3r/GUI/GLToolbar.hpp" +#include -class wxTimer; +#include "3DScene.hpp" +#include "GLToolbar.hpp" +#include "Event.hpp" + +#include + +#include + +class wxWindow; class wxSizeEvent; class wxIdleEvent; class wxKeyEvent; class wxMouseEvent; class wxTimerEvent; class wxPaintEvent; +class wxGLCanvas; + namespace Slic3r { class GLShader; class ExPolygon; +class BackgroundSlicingProcess; namespace GUI { @@ -76,6 +86,30 @@ public: void set_bottom(float bottom); }; +wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); + +using Vec2dEvent = Event; +template using Vec2dsEvent = ArrayEvent; + +using Vec3dEvent = Event; +template using Vec3dsEvent = ArrayEvent; + +#if ENABLE_REMOVE_TABS_FROM_PLATER +wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_MODEL_UPDATE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); // data: +1 => increase, -1 => decrease +wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); +wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); +wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); + class GLCanvas3D { struct GCodePreviewVolumeIndex @@ -119,9 +153,15 @@ class GLCanvas3D float zoom; float phi; // float distance; +#if !ENABLE_CONSTRAINED_CAMERA_TARGET Vec3d target; +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET private: +#if ENABLE_CONSTRAINED_CAMERA_TARGET + Vec3d m_target; + BoundingBoxf3 m_scene_box; +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET float m_theta; public: @@ -129,8 +169,16 @@ class GLCanvas3D std::string get_type_as_string() const; - float get_theta() const; + float get_theta() const { return m_theta; } void set_theta(float theta); + +#if ENABLE_CONSTRAINED_CAMERA_TARGET + const Vec3d& get_target() const { return m_target; } + void set_target(const Vec3d& target, GLCanvas3D& canvas); + + const BoundingBoxf3& get_scene_box() const { return m_scene_box; } + void set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET }; class Bed @@ -140,6 +188,7 @@ class GLCanvas3D { MK2, MK3, + SL1, Custom, Num_Types }; @@ -175,9 +224,7 @@ class GLCanvas3D void _calc_triangles(const ExPolygon& poly); void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType _detect_type() const; - void _render_mk2(float theta) const; - void _render_mk3(float theta) const; - void _render_prusa(float theta) const; + void _render_prusa(const std::string &key, float theta) const; void _render_custom() const; static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2); }; @@ -192,23 +239,6 @@ class GLCanvas3D void render(bool depth_test) const; }; - class CuttingPlane - { - float m_z; - GeometryBuffer m_lines; - - public: - CuttingPlane(); - - bool set(float z, const ExPolygons& polygons); - - void render(const BoundingBoxf3& bb) const; - - private: - void _render_plane(const BoundingBoxf3& bb) const; - void _render_contour() const; - }; - class Shader { GLShader* m_shader; @@ -302,19 +332,18 @@ class GLCanvas3D Point start_position_2D; Vec3d start_position_3D; - Vec3d volume_center_offset; - - bool move_with_shift; int move_volume_idx; - int gizmo_volume_idx; public: Drag(); }; bool dragging; + bool left_down; Vec2d position; + Vec3d scene_position; Drag drag; + bool ignore_up_event; Mouse(); @@ -325,10 +354,261 @@ class GLCanvas3D bool is_start_position_3D_defined() const; }; +public: + class Selection + { + public: + typedef std::set IndicesList; + + enum EMode : unsigned char + { +#if ENABLE_MODELVOLUME_TRANSFORM + Volume, + Instance +#else + Volume, + Instance, + Object +#endif // ENABLE_MODELVOLUME_TRANSFORM + }; + + enum EType : unsigned char + { + Invalid, + Empty, + WipeTower, + SingleModifier, + MultipleModifier, + SingleVolume, + MultipleVolume, + SingleFullObject, + MultipleFullObject, + SingleFullInstance, + MultipleFullInstance, + Mixed + }; + + private: + struct VolumeCache + { + private: +#if ENABLE_MODELVOLUME_TRANSFORM + struct TransformCache + { + Vec3d position; + Vec3d rotation; + Vec3d scaling_factor; + Vec3d mirror; + Transform3d rotation_matrix; + Transform3d scale_matrix; + Transform3d mirror_matrix; + + TransformCache(); + explicit TransformCache(const Geometry::Transformation& transform); + }; + + TransformCache m_volume; + TransformCache m_instance; +#else + Vec3d m_position; + Vec3d m_rotation; + Vec3d m_scaling_factor; + Transform3d m_rotation_matrix; + Transform3d m_scale_matrix; +#endif // ENABLE_MODELVOLUME_TRANSFORM + + public: +#if ENABLE_MODELVOLUME_TRANSFORM + VolumeCache() {} + VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); +#else + VolumeCache(); + VolumeCache(const Vec3d& position, const Vec3d& rotation, const Vec3d& scaling_factor); +#endif // ENABLE_MODELVOLUME_TRANSFORM + +#if ENABLE_MODELVOLUME_TRANSFORM + const Vec3d& get_volume_position() const { return m_volume.position; } + const Vec3d& get_volume_rotation() const { return m_volume.rotation; } + const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } + const Vec3d& get_volume_mirror() const { return m_volume.mirror; } + const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } + const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } + const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } + + const Vec3d& get_instance_position() const { return m_instance.position; } + const Vec3d& get_instance_rotation() const { return m_instance.rotation; } + const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; } + const Vec3d& get_instance_mirror() const { return m_instance.mirror; } + const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; } + const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } + const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } +#else + const Vec3d& get_position() const { return m_position; } + const Vec3d& get_rotation() const { return m_rotation; } + const Vec3d& get_scaling_factor() const { return m_scaling_factor; } + const Transform3d& get_rotation_matrix() const { return m_rotation_matrix; } + const Transform3d& get_scale_matrix() const { return m_scale_matrix; } +#endif // ENABLE_MODELVOLUME_TRANSFORM + }; + + typedef std::map VolumesCache; + typedef std::set InstanceIdxsList; + typedef std::map ObjectIdxsToInstanceIdxsMap; + + struct Cache + { + // Cache of GLVolume derived transformation matrices, valid during mouse dragging. + VolumesCache volumes_data; + // Center of the dragged selection, valid during mouse dragging. + Vec3d dragging_center; + // Map from indices of ModelObject instances in Model::objects + // to a set of indices of ModelVolume instances in ModelObject::instances + // Here the index means a position inside the respective std::vector, not ModelID. + ObjectIdxsToInstanceIdxsMap content; + }; + + // Volumes owned by GLCanvas3D. + GLVolumePtrs* m_volumes; + // Model, not owned. + Model* m_model; + + bool m_valid; + EMode m_mode; + EType m_type; + // set of indices to m_volumes + IndicesList m_list; + Cache m_cache; + mutable BoundingBoxf3 m_bounding_box; + mutable bool m_bounding_box_dirty; + + public: + Selection(); + + void set_volumes(GLVolumePtrs* volumes); + + Model* get_model() const { return m_model; } + void set_model(Model* model); + + EMode get_mode() const { return m_mode; } + void set_mode(EMode mode) { m_mode = mode; } + + void add(unsigned int volume_idx, bool as_single_selection = true); + void remove(unsigned int volume_idx); + + void add_object(unsigned int object_idx, bool as_single_selection = true); + void remove_object(unsigned int object_idx); + + void add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection = true); + void remove_instance(unsigned int object_idx, unsigned int instance_idx); + + void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); + void remove_volume(unsigned int object_idx, unsigned int volume_idx); + + void add_all(); + + // Update the selection based on the map from old indices to new indices after m_volumes changed. + // If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. + void volumes_changed(const std::vector &map_volume_old_to_new); + void clear(); + + bool is_empty() const { return m_type == Empty; } + bool is_wipe_tower() const { return m_type == WipeTower; } + bool is_modifier() const { return (m_type == SingleModifier) || (m_type == MultipleModifier); } + bool is_single_modifier() const { return m_type == SingleModifier; } + bool is_single_full_instance() const; + bool is_multiple_full_instance() const { return m_type == MultipleFullInstance; } + bool is_single_full_object() const { return m_type == SingleFullObject; } + bool is_multiple_full_object() const { return m_type == MultipleFullObject; } + bool is_single_volume() const { return m_type == SingleVolume; } + bool is_multiple_volume() const { return m_type == MultipleVolume; } + bool is_mixed() const { return m_type == Mixed; } + bool is_from_single_instance() const { return get_instance_idx() != -1; } + bool is_from_single_object() const; + + bool contains_volume(unsigned int volume_idx) const { return std::find(m_list.begin(), m_list.end(), volume_idx) != m_list.end(); } + + // Returns the the object id if the selection is from a single object, otherwise is -1 + int get_object_idx() const; + // Returns the instance id if the selection is from a single object and from a single instance, otherwise is -1 + int get_instance_idx() const; + // Returns the indices of selected instances. + // Can only be called if selection is from a single object. + const InstanceIdxsList& get_instance_idxs() const; + + const IndicesList& get_volume_idxs() const { return m_list; } + const GLVolume* get_volume(unsigned int volume_idx) const; + + const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } + + unsigned int volumes_count() const { return (unsigned int)m_list.size(); } + const BoundingBoxf3& get_bounding_box() const; + + void start_dragging(); + + void translate(const Vec3d& displacement); + void rotate(const Vec3d& rotation, bool local); + void flattening_rotate(const Vec3d& normal); + void scale(const Vec3d& scale, bool local); + void mirror(Axis axis); + + void translate(unsigned int object_idx, const Vec3d& displacement); + void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); + + void erase(); + + void render() const; + + private: + void _update_valid(); + void _update_type(); + void _set_caches(); + void _add_volume(unsigned int volume_idx); + void _add_instance(unsigned int object_idx, unsigned int instance_idx); + void _add_object(unsigned int object_idx); + void _remove_volume(unsigned int volume_idx); + void _remove_instance(unsigned int object_idx, unsigned int instance_idx); + void _remove_object(unsigned int object_idx); + void _calc_bounding_box() const; + void _render_selected_volumes() const; + void _render_synchronized_volumes() const; + void _render_bounding_box(const BoundingBoxf3& box, float* color) const; + void _synchronize_unselected_instances(); + void _synchronize_unselected_volumes(); +#if ENABLE_ENSURE_ON_BED_WHILE_SCALING + void _ensure_on_bed(); +#endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING + }; + + class ClippingPlane + { + double m_data[4]; + + public: + ClippingPlane() + { + m_data[0] = 0.0; + m_data[1] = 0.0; + m_data[2] = 1.0; + m_data[3] = 0.0; + } + + ClippingPlane(const Vec3d& direction, double offset) + { + Vec3d norm_dir = direction.normalized(); + m_data[0] = norm_dir(0); + m_data[1] = norm_dir(1); + m_data[2] = norm_dir(2); + m_data[3] = offset; + } + + const double* get_data() const { return m_data; } + }; + +private: class Gizmos { - static const float OverlayTexturesScale; - static const float OverlayOffsetX; + static const float OverlayIconsScale; + static const float OverlayBorder; static const float OverlayGapY; public: @@ -339,6 +619,8 @@ class GLCanvas3D Scale, Rotate, Flatten, + Cut, + SlaSupports, Num_Types }; @@ -346,6 +628,9 @@ class GLCanvas3D bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + BackgroundTexture m_background_texture; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE EType m_current; public: @@ -357,51 +642,85 @@ class GLCanvas3D bool is_enabled() const; void set_enabled(bool enable); - void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); - void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); + std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); + void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); + void update_on_off_state(const Selection& selection); void reset_all_states(); void set_hover_id(int id); + void enable_grabber(EType type, unsigned int id, bool enable); bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const; bool grabber_contains_mouse() const; - void update(const Linef3& mouse_ray); - + void update(const Linef3& mouse_ray, bool shift_down, const Point* mouse_pos = nullptr); + Rect get_reset_rect_viewport(const GLCanvas3D& canvas) const; EType get_current_type() const; bool is_running() const; + bool handle_shortcut(int key, const Selection& selection); bool is_dragging() const; - void start_dragging(const BoundingBoxf3& box); + void start_dragging(const Selection& selection); void stop_dragging(); - Vec3d get_position() const; - void set_position(const Vec3d& position); + Vec3d get_displacement() const; - float get_scale() const; - void set_scale(float scale); + Vec3d get_scale() const; + void set_scale(const Vec3d& scale); - float get_angle_z() const; - void set_angle_z(float angle_z); + Vec3d get_rotation() const; + void set_rotation(const Vec3d& rotation); - void set_flattening_data(const ModelObject* model_object); Vec3d get_flattening_normal() const; - void render_current_gizmo(const BoundingBoxf3& box) const; + void set_flattening_data(const ModelObject* model_object); - void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const; - void render_overlay(const GLCanvas3D& canvas) const; +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); +#else + void set_model_object_ptr(ModelObject* model_object); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + void clicked_on_object(const Vec2d& mouse_position); + void delete_current_grabber(bool delete_all = false); + + void render_current_gizmo(const Selection& selection) const; + void render_current_gizmo_for_picking_pass(const Selection& selection) const; + + void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + +#if !ENABLE_IMGUI + void create_external_gizmo_widgets(wxWindow *parent); +#endif // not ENABLE_IMGUI private: void _reset(); - void _render_overlay(const GLCanvas3D& canvas) const; - void _render_current_gizmo(const BoundingBoxf3& box) const; + void _render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void _render_current_gizmo(const Selection& selection) const; float _get_total_overlay_height() const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float _get_total_overlay_width() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLGizmoBase* _get_current() const; }; + struct SlaCap + { + struct Triangles + { + Pointf3s object; + Pointf3s suppports; + }; + typedef std::map ObjectIdToTrianglesMap; + double z; + ObjectIdToTrianglesMap triangles; + + SlaCap() { reset(); } + void reset() { z = DBL_MAX; triangles.clear(); } + bool matches(double z) const { return this->z == z; } + }; + class WarningTexture : public GUI::GLTexture { static const unsigned char Background_Color[3]; @@ -426,7 +745,8 @@ class GLCanvas3D static const int Px_Square_Contour = 1; static const int Px_Border = Px_Square / 2; static const unsigned char Squares_Border_Color[3]; - static const unsigned char Background_Color[3]; + static const unsigned char Default_Background_Color[3]; + static const unsigned char Error_Background_Color[3]; static const unsigned char Opacity; int m_original_width; @@ -435,31 +755,43 @@ class GLCanvas3D public: LegendTexture(); - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors); + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors); void render(const GLCanvas3D& canvas) const; }; wxGLCanvas* m_canvas; wxGLContext* m_context; + bool m_in_render; LegendTexture m_legend_texture; WarningTexture m_warning_texture; - wxTimer* m_timer; + wxTimer m_timer; Camera m_camera; Bed m_bed; Axes m_axes; - CuttingPlane m_cutting_plane; LayersEditing m_layers_editing; Shader m_shader; Mouse m_mouse; mutable Gizmos m_gizmos; mutable GLToolbar m_toolbar; +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar* m_view_toolbar; +#else + GLRadioToolbar* m_view_toolbar; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + ClippingPlane m_clipping_planes[2]; + bool m_use_clipping_planes; + mutable SlaCap m_sla_caps[2]; mutable GLVolumeCollection m_volumes; + Selection m_selection; DynamicPrintConfig* m_config; - Print* m_print; Model* m_model; + BackgroundSlicingProcess *m_process; + // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; bool m_use_VBOs; @@ -474,100 +806,94 @@ class GLCanvas3D bool m_shader_enabled; bool m_dynamic_background_enabled; bool m_multisample_allowed; + bool m_regenerate_volumes; + bool m_moving; std::string m_color_by; - std::string m_select_by; - std::string m_drag_by; bool m_reload_delayed; - std::vector> m_objects_volumes_idxs; - std::vector m_objects_selections; GCodePreviewVolumeIndex m_gcode_preview_volume_index; - PerlCallback m_on_viewport_changed_callback; - PerlCallback m_on_double_click_callback; - PerlCallback m_on_right_click_callback; - PerlCallback m_on_select_object_callback; - PerlCallback m_on_model_update_callback; - PerlCallback m_on_remove_object_callback; - PerlCallback m_on_arrange_callback; - PerlCallback m_on_rotate_object_left_callback; - PerlCallback m_on_rotate_object_right_callback; - PerlCallback m_on_scale_object_uniformly_callback; - PerlCallback m_on_increase_objects_callback; - PerlCallback m_on_decrease_objects_callback; - PerlCallback m_on_instance_moved_callback; - PerlCallback m_on_wipe_tower_moved_callback; - PerlCallback m_on_enable_action_buttons_callback; - PerlCallback m_on_gizmo_scale_uniformly_callback; - PerlCallback m_on_gizmo_rotate_callback; - PerlCallback m_on_gizmo_flatten_callback; - PerlCallback m_on_update_geometry_info_callback; +#if !ENABLE_IMGUI + wxWindow *m_external_gizmo_widgets_parent; +#endif // not ENABLE_IMGUI - PerlCallback m_action_add_callback; - PerlCallback m_action_delete_callback; - PerlCallback m_action_deleteall_callback; - PerlCallback m_action_arrange_callback; - PerlCallback m_action_more_callback; - PerlCallback m_action_fewer_callback; - PerlCallback m_action_split_callback; - PerlCallback m_action_cut_callback; - PerlCallback m_action_settings_callback; - PerlCallback m_action_layersediting_callback; - PerlCallback m_action_selectbyparts_callback; +#if !ENABLE_CONSTRAINED_CAMERA_TARGET + void viewport_changed(); +#endif // !ENABLE_CONSTRAINED_CAMERA_TARGET public: GLCanvas3D(wxGLCanvas* canvas); ~GLCanvas3D(); - bool init(bool useVBOs, bool use_legacy_opengl); +#if ENABLE_USE_UNIQUE_GLCONTEXT + void set_context(wxGLContext* context) { m_context = context; } +#endif // ENABLE_USE_UNIQUE_GLCONTEXT + wxGLCanvas* get_wxglcanvas() { return m_canvas; } + +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar) { m_view_toolbar = toolbar; } +#else + void set_view_toolbar(GLRadioToolbar* toolbar) { m_view_toolbar = toolbar; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + bool init(bool useVBOs, bool use_legacy_opengl); + void post_event(wxEvent &&event); + +#if !ENABLE_USE_UNIQUE_GLCONTEXT bool set_current(); +#endif // !ENABLE_USE_UNIQUE_GLCONTEXT void set_as_dirty(); unsigned int get_volumes_count() const; void reset_volumes(); - void deselect_volumes(); - void select_volume(unsigned int id); - void update_volumes_selection(const std::vector& selections); +#if ENABLE_REMOVE_TABS_FROM_PLATER + int check_volumes_outside_state() const; +#else int check_volumes_outside_state(const DynamicPrintConfig* config) const; - bool move_volume_up(unsigned int id); - bool move_volume_down(unsigned int id); - - void set_objects_selections(const std::vector& selections); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER void set_config(DynamicPrintConfig* config); - void set_print(Print* print); + void set_process(BackgroundSlicingProcess* process); void set_model(Model* model); + const Selection& get_selection() const { return m_selection; } + Selection& get_selection() { return m_selection; } + // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. void set_bed_shape(const Pointfs& shape); - // Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects. - void set_auto_bed_shape(); void set_axes_length(float length); - void set_cutting_plane(float z, const ExPolygons& polygons); + void set_clipping_plane(unsigned int id, const ClippingPlane& plane) + { + if (id < 2) + { + m_clipping_planes[id] = plane; + m_sla_caps[id].reset(); + } + } + void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } void set_color_by(const std::string& value); - void set_select_by(const std::string& value); - void set_drag_by(const std::string& value); - - const std::string& get_select_by() const; - const std::string& get_drag_by() const; float get_camera_zoom() const; BoundingBoxf3 volumes_bounding_box() const; +#if ENABLE_CONSTRAINED_CAMERA_TARGET + BoundingBoxf3 scene_bounding_box() const; +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET bool is_layers_editing_enabled() const; bool is_layers_editing_allowed() const; - bool is_shader_enabled() const; bool is_reload_delayed() const; @@ -588,60 +914,41 @@ public: void zoom_to_bed(); void zoom_to_volumes(); +#if ENABLE_MODIFIED_CAMERA_TARGET + void zoom_to_selection(); +#endif // ENABLE_MODIFIED_CAMERA_TARGET void select_view(const std::string& direction); void set_viewport_from_scene(const GLCanvas3D& other); void update_volumes_colors_by_extruder(); - void update_gizmos_data(); + +#if !ENABLE_IMGUI + Rect get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const; + bool gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const; +#endif // not ENABLE_IMGUI + + bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } void render(); + void select_all(); + void delete_selected(); + void ensure_on_bed(unsigned int object_idx); + std::vector get_current_print_zs(bool active_only) const; void set_toolpaths_range(double low, double high); std::vector load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs); std::vector load_object(const Model& model, int obj_idx); - int get_first_volume_id(int obj_idx) const; - int get_in_object_volume_id(int scene_vol_idx) const; + void mirror_selection(Axis axis); - void reload_scene(bool force); + void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); + void load_sla_preview(); void load_preview(const std::vector& str_tool_colors); - void register_on_viewport_changed_callback(void* callback); - void register_on_double_click_callback(void* callback); - void register_on_right_click_callback(void* callback); - void register_on_select_object_callback(void* callback); - void register_on_model_update_callback(void* callback); - void register_on_remove_object_callback(void* callback); - void register_on_arrange_callback(void* callback); - void register_on_rotate_object_left_callback(void* callback); - void register_on_rotate_object_right_callback(void* callback); - void register_on_scale_object_uniformly_callback(void* callback); - void register_on_increase_objects_callback(void* callback); - void register_on_decrease_objects_callback(void* callback); - void register_on_instance_moved_callback(void* callback); - void register_on_wipe_tower_moved_callback(void* callback); - void register_on_enable_action_buttons_callback(void* callback); - void register_on_gizmo_scale_uniformly_callback(void* callback); - void register_on_gizmo_rotate_callback(void* callback); - void register_on_gizmo_flatten_callback(void* callback); - void register_on_update_geometry_info_callback(void* callback); - - void register_action_add_callback(void* callback); - void register_action_delete_callback(void* callback); - void register_action_deleteall_callback(void* callback); - void register_action_arrange_callback(void* callback); - void register_action_more_callback(void* callback); - void register_action_fewer_callback(void* callback); - void register_action_split_callback(void* callback); - void register_action_cut_callback(void* callback); - void register_action_settings_callback(void* callback); - void register_action_layersediting_callback(void* callback); - void register_action_selectbyparts_callback(void* callback); - void bind_event_handlers(); void unbind_event_handlers(); @@ -659,7 +966,27 @@ public: void reset_legend_texture(); - void set_tooltip(const std::string& tooltip); + void set_tooltip(const std::string& tooltip) const; + +#if !ENABLE_IMGUI + void set_external_gizmo_widgets_parent(wxWindow *parent); +#endif // not ENABLE_IMGUI + + void do_move(); + void do_rotate(); + void do_scale(); + void do_flatten(); + void do_mirror(); + + void set_camera_zoom(float zoom); + + void update_gizmos_on_off_state(); + +#if ENABLE_CONSTRAINED_CAMERA_TARGET + void viewport_changed(); +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET + + void handle_sidebar_focus_event(const std::string& opt_key) {} private: bool _is_shown_on_screen() const; @@ -667,16 +994,16 @@ private: bool _init_toolbar(); +#if ENABLE_USE_UNIQUE_GLCONTEXT + bool _set_current(); +#endif // ENABLE_USE_UNIQUE_GLCONTEXT void _resize(unsigned int w, unsigned int h); BoundingBoxf3 _max_bounding_box() const; - BoundingBoxf3 _selected_volumes_bounding_box() const; void _zoom_to_bounding_box(const BoundingBoxf3& bbox); float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; - void _deregister_callbacks(); - void _mark_volumes_for_layer_height() const; void _refresh_if_shown_on_screen(); @@ -686,7 +1013,7 @@ private: void _render_bed(float theta) const; void _render_axes(bool depth_test) const; void _render_objects() const; - void _render_cutting_plane() const; + void _render_selection() const; void _render_warning_texture() const; void _render_legend_texture() const; void _render_layer_editing_overlay() const; @@ -694,6 +1021,16 @@ private: void _render_current_gizmo() const; void _render_gizmos_overlay() const; void _render_toolbar() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + void _render_view_toolbar() const; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_SHOW_CAMERA_TARGET + void _render_camera_target() const; +#endif // ENABLE_SHOW_CAMERA_TARGET + void _render_sla_slices() const; + + void _update_volumes_hover_state() const; + void _update_gizmos_data(); float _get_layers_editing_cursor_z_relative() const; void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); @@ -711,9 +1048,6 @@ private: void _start_timer(); void _stop_timer(); - int _get_first_selected_object_id() const; - int _get_first_selected_volume_id(int object_id) const; - // Create 3D thick extrusion lines for a skirt and brim. // Adds a new Slic3r::GUI::3DScene::Volume to volumes. void _load_print_toolpaths(); @@ -736,15 +1070,14 @@ private: // generates gcode unretractions geometry void _load_gcode_unretractions(const GCodePreviewData& preview_data); // generates objects and wipe tower geometry - void _load_shells(); + void _load_shells_fff(); + // generates objects geometry for sla + void _load_shells_sla(); // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); void _update_toolpath_volumes_outside_state(); void _show_warning_texture_if_needed(); - void _on_move(const std::vector& volume_idxs); - void _on_select(int volume_idx, int object_idx); - // generates the legend texture in dependence of the current shown view type void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors); @@ -754,9 +1087,16 @@ private: bool _is_any_volume_outside() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER + void _resize_toolbars() const; +#else void _resize_toolbar() const; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER static std::vector _parse_colors(const std::vector& colors); + + const Print* fff_print() const; + const SLAPrint* sla_print() const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 495f49425..28c00ffa5 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -110,14 +110,29 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten return out.str(); } +GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; + GLCanvas3DManager::GLCanvas3DManager() - : m_current(nullptr) +#if ENABLE_USE_UNIQUE_GLCONTEXT + : m_context(nullptr) +#endif // ENABLE_USE_UNIQUE_GLCONTEXT , m_gl_initialized(false) , m_use_legacy_opengl(false) , m_use_VBOs(false) { } +#if ENABLE_USE_UNIQUE_GLCONTEXT +GLCanvas3DManager::~GLCanvas3DManager() +{ + if (m_context != nullptr) + { + delete m_context; + m_context = nullptr; + } +} +#endif // ENABLE_USE_UNIQUE_GLCONTEXT + bool GLCanvas3DManager::add(wxGLCanvas* canvas) { if (canvas == nullptr) @@ -131,6 +146,18 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas) return false; canvas3D->bind_event_handlers(); + +#if ENABLE_USE_UNIQUE_GLCONTEXT + if (m_context == nullptr) + { + m_context = new wxGLContext(canvas); + if (m_context == nullptr) + return false; + } + + canvas3D->set_context(m_context); +#endif // ENABLE_USE_UNIQUE_GLCONTEXT + m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D)); return true; @@ -182,11 +209,6 @@ std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) return m_gl_info.to_string(format_as_html, extensions); } -bool GLCanvas3DManager::use_VBOs() const -{ - return m_use_VBOs; -} - bool GLCanvas3DManager::init(wxGLCanvas* canvas) { CanvasesMap::const_iterator it = _get_canvas(canvas); @@ -196,605 +218,28 @@ bool GLCanvas3DManager::init(wxGLCanvas* canvas) return false; } -void GLCanvas3DManager::set_as_dirty(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_as_dirty(); -} - -unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const +GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas) { CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0; + return (it != m_canvases.end()) ? it->second : nullptr; } -void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas) +wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) { - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->reset_volumes(); -} + int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0 }; -void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->deselect_volumes(); -} - -void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->select_volume(id); -} - -void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->update_volumes_selection(selections); -} - -int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false; -} - -bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id) -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false; -} - -bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id) -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false; -} - -void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector& selections) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_objects_selections(selections); -} - -void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_config(config); -} - -void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_print(print); -} - -void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_model(model); -} - -void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_bed_shape(shape); -} - -void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_auto_bed_shape(); -} - -BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas) -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3(); -} - -void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_axes_length(length); -} - -void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_cutting_plane(z, polygons); -} - -void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_color_by(value); -} - -void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_select_by(value); -} - -void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_drag_by(value); -} - -std::string GLCanvas3DManager::get_select_by(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->get_select_by() : ""; -} - -bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false; -} - -bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false; -} - -bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false; -} - -bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false; -} - -void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_layers_editing(enable); -} - -void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_warning_texture(enable); -} - -void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_legend_texture(enable); -} - -void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_picking(enable); -} - -void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_moving(enable); -} - -void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_gizmos(enable); -} - -void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_toolbar(enable); -} - -void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_shader(enable); -} - -void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_force_zoom_to_bed(enable); -} - -void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_dynamic_background(enable); -} - -void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->allow_multisample(allow); -} - -void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->enable_toolbar_item(name, enable); -} - -bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false; -} - -void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->zoom_to_bed(); -} - -void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->zoom_to_volumes(); -} - -void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->select_view(direction); -} - -void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) + if (s_multisample == MS_Unknown) { - CanvasesMap::iterator other_it = _get_canvas(other); - if (other_it != m_canvases.end()) - it->second->set_viewport_from_scene(*other_it->second); + _detect_multisample(attribList); + // debug output + std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; } -} -void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas) -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->update_volumes_colors_by_extruder(); -} - -void GLCanvas3DManager::update_gizmos_data(wxGLCanvas* canvas) -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->update_gizmos_data(); -} - -void GLCanvas3DManager::render(wxGLCanvas* canvas) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->render(); -} - -std::vector GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector(); -} - -void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->set_toolpaths_range(low, high); -} - -std::vector GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs) -{ - if (model_object == nullptr) - return std::vector(); - - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector(); -} - -std::vector GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) -{ - if (model == nullptr) - return std::vector(); - - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector(); -} - -int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1; -} - -int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const -{ - CanvasesMap::const_iterator it = _get_canvas(canvas); - return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1; -} - -void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->reload_scene(force); -} - -void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors) -{ - if (preview_data == nullptr) - return; - - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_gcode_preview(*preview_data, str_tool_colors); -} - -void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_preview(str_tool_colors); -} - -void GLCanvas3DManager::reset_legend_texture() -{ - for (CanvasesMap::value_type& canvas : m_canvases) - { - if (canvas.second != nullptr) - canvas.second->reset_legend_texture(); + if (! can_multisample()) { + attribList[4] = 0; } -} -void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_viewport_changed_callback(callback); -} - -void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_double_click_callback(callback); -} - -void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_right_click_callback(callback); -} - -void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_select_object_callback(callback); -} - -void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_model_update_callback(callback); -} - -void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_remove_object_callback(callback); -} - -void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_arrange_callback(callback); -} - -void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_rotate_object_left_callback(callback); -} - -void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_rotate_object_right_callback(callback); -} - -void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_scale_object_uniformly_callback(callback); -} - -void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_increase_objects_callback(callback); -} - -void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_decrease_objects_callback(callback); -} - -void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_instance_moved_callback(callback); -} - -void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_wipe_tower_moved_callback(callback); -} - -void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_enable_action_buttons_callback(callback); -} - -void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_gizmo_scale_uniformly_callback(callback); -} - -void GLCanvas3DManager::register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_gizmo_rotate_callback(callback); -} - -void GLCanvas3DManager::register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_gizmo_flatten_callback(callback); -} - -void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_on_update_geometry_info_callback(callback); -} - -void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_add_callback(callback); -} - -void GLCanvas3DManager::register_action_delete_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_delete_callback(callback); -} - -void GLCanvas3DManager::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_deleteall_callback(callback); -} - -void GLCanvas3DManager::register_action_arrange_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_arrange_callback(callback); -} - -void GLCanvas3DManager::register_action_more_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_more_callback(callback); -} - -void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_fewer_callback(callback); -} - -void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_split_callback(callback); -} - -void GLCanvas3DManager::register_action_cut_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_cut_callback(callback); -} - -void GLCanvas3DManager::register_action_settings_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_settings_callback(callback); -} - -void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_layersediting_callback(callback); -} - -void GLCanvas3DManager::register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_selectbyparts_callback(callback); + return new wxGLCanvas(parent, wxID_ANY, attribList); } GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) @@ -815,5 +260,18 @@ bool GLCanvas3DManager::_init(GLCanvas3D& canvas) return canvas.init(m_use_VBOs, m_use_legacy_opengl); } +void GLCanvas3DManager::_detect_multisample(int* attribList) +{ + int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; + const AppConfig* app_config = GUI::get_app_config(); + bool enable_multisample = app_config != nullptr + && app_config->get("use_legacy_opengl") != "1" + && wxVersion >= 30003; + + s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled; + // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows + // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 4922b6171..1ed4a8251 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -1,25 +1,26 @@ #ifndef slic3r_GLCanvas3DManager_hpp_ #define slic3r_GLCanvas3DManager_hpp_ -#include "../../libslic3r/BoundingBox.hpp" +#include "libslic3r/BoundingBox.hpp" #include #include +class wxWindow; class wxGLCanvas; class wxGLContext; namespace Slic3r { +class BackgroundSlicingProcess; class DynamicPrintConfig; -class Print; class Model; class ExPolygon; typedef std::vector ExPolygons; class ModelObject; class PrintObject; class GCodePreviewData; - + namespace GUI { class GLCanvas3D; @@ -41,21 +42,33 @@ class GLCanvas3DManager std::string to_string(bool format_as_html, bool extensions) const; }; + enum EMultisampleState : unsigned char + { + MS_Unknown, + MS_Enabled, + MS_Disabled + }; + typedef std::map CanvasesMap; CanvasesMap m_canvases; - wxGLCanvas* m_current; +#if ENABLE_USE_UNIQUE_GLCONTEXT + wxGLContext* m_context; +#endif // ENABLE_USE_UNIQUE_GLCONTEXT GLInfo m_gl_info; bool m_gl_initialized; bool m_use_legacy_opengl; bool m_use_VBOs; + static EMultisampleState s_multisample; public: GLCanvas3DManager(); +#if ENABLE_USE_UNIQUE_GLCONTEXT + ~GLCanvas3DManager(); +#endif // ENABLE_USE_UNIQUE_GLCONTEXT bool add(wxGLCanvas* canvas); bool remove(wxGLCanvas* canvas); - void remove_all(); unsigned int count() const; @@ -63,127 +76,19 @@ public: void init_gl(); std::string get_gl_info(bool format_as_html, bool extensions) const; - bool use_VBOs() const; - bool layer_editing_allowed() const; - bool init(wxGLCanvas* canvas); - void set_as_dirty(wxGLCanvas* canvas); + GLCanvas3D* get_canvas(wxGLCanvas* canvas); - unsigned int get_volumes_count(wxGLCanvas* canvas) const; - void reset_volumes(wxGLCanvas* canvas); - void deselect_volumes(wxGLCanvas* canvas); - void select_volume(wxGLCanvas* canvas, unsigned int id); - void update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections); - int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const; - bool move_volume_up(wxGLCanvas* canvas, unsigned int id); - bool move_volume_down(wxGLCanvas* canvas, unsigned int id); - - void set_objects_selections(wxGLCanvas* canvas, const std::vector& selections); - - void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); - void set_print(wxGLCanvas* canvas, Print* print); - void set_model(wxGLCanvas* canvas, Model* model); - - void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); - void set_auto_bed_shape(wxGLCanvas* canvas); - - BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); - - void set_axes_length(wxGLCanvas* canvas, float length); - - void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); - - void set_color_by(wxGLCanvas* canvas, const std::string& value); - void set_select_by(wxGLCanvas* canvas, const std::string& value); - void set_drag_by(wxGLCanvas* canvas, const std::string& value); - - std::string get_select_by(wxGLCanvas* canvas) const; - - bool is_layers_editing_enabled(wxGLCanvas* canvas) const; - bool is_layers_editing_allowed(wxGLCanvas* canvas) const; - bool is_shader_enabled(wxGLCanvas* canvas) const; - - bool is_reload_delayed(wxGLCanvas* canvas) const; - - void enable_layers_editing(wxGLCanvas* canvas, bool enable); - void enable_warning_texture(wxGLCanvas* canvas, bool enable); - void enable_legend_texture(wxGLCanvas* canvas, bool enable); - void enable_picking(wxGLCanvas* canvas, bool enable); - void enable_moving(wxGLCanvas* canvas, bool enable); - void enable_gizmos(wxGLCanvas* canvas, bool enable); - void enable_toolbar(wxGLCanvas* canvas, bool enable); - void enable_shader(wxGLCanvas* canvas, bool enable); - void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); - void enable_dynamic_background(wxGLCanvas* canvas, bool enable); - void allow_multisample(wxGLCanvas* canvas, bool allow); - - void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); - bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const; - - void zoom_to_bed(wxGLCanvas* canvas); - void zoom_to_volumes(wxGLCanvas* canvas); - void select_view(wxGLCanvas* canvas, const std::string& direction); - void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); - - void update_volumes_colors_by_extruder(wxGLCanvas* canvas); - void update_gizmos_data(wxGLCanvas* canvas); - - void render(wxGLCanvas* canvas) const; - - std::vector get_current_print_zs(wxGLCanvas* canvas, bool active_only) const; - void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); - - std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); - std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); - - int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const; - int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const; - - void reload_scene(wxGLCanvas* canvas, bool force); - - void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors); - void load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors); - - void reset_legend_texture(); - - void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); - void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); - void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); - void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); - void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); - void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); - void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); - void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); - void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); - void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); - void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); - void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); - void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); - void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); - void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); - void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); - void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); - void register_on_gizmo_flatten_callback(wxGLCanvas* canvas, void* callback); - void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); - - void register_action_add_callback(wxGLCanvas* canvas, void* callback); - void register_action_delete_callback(wxGLCanvas* canvas, void* callback); - void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); - void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); - void register_action_more_callback(wxGLCanvas* canvas, void* callback); - void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); - void register_action_split_callback(wxGLCanvas* canvas, void* callback); - void register_action_cut_callback(wxGLCanvas* canvas, void* callback); - void register_action_settings_callback(wxGLCanvas* canvas, void* callback); - void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); - void register_action_selectbyparts_callback(wxGLCanvas* canvas, void* callback); + static bool can_multisample() { return s_multisample == MS_Enabled; } + static wxGLCanvas* create_wxglcanvas(wxWindow *parent); private: CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; bool _init(GLCanvas3D& canvas); + static void _detect_multisample(int* attribList); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index e23958c1d..7f62d0901 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1,15 +1,35 @@ +// Include GLGizmo.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmo.hpp" -#include "../../libslic3r/Utils.hpp" -#include "../../slic3r/GUI/GLCanvas3D.hpp" - -#include -#include "../../libslic3r/Geometry.hpp" - #include +#include -#include +#include "libslic3r/libslic3r.h" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/SLA/SLASupportTree.hpp" + +#include #include +#include + +#include +#include +#include +#include +#include +#include + +#include "Tab.hpp" +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "PresetBundle.hpp" + +#include + +// TODO: Display tooltips quicker on Linux static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; @@ -20,89 +40,6 @@ static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f namespace Slic3r { namespace GUI { -// returns the intersection of the given ray with the plane parallel to plane XY and passing through the given center -// coordinates are local to the plane -Vec3d intersection_on_plane_xy(const Linef3& ray, const Vec3d& center) -{ - Transform3d m = Transform3d::Identity(); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - return Vec3d(mouse_pos_2d(0), mouse_pos_2d(1), 0.0); -} - -// returns the intersection of the given ray with the plane parallel to plane XZ and passing through the given center -// coordinates are local to the plane -Vec3d intersection_on_plane_xz(const Linef3& ray, const Vec3d& center) -{ - Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd(-0.5 * (double)PI, Vec3d::UnitX())); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - return Vec3d(mouse_pos_2d(0), 0.0, mouse_pos_2d(1)); -} - -// returns the intersection of the given ray with the plane parallel to plane YZ and passing through the given center -// coordinates are local to the plane -Vec3d intersection_on_plane_yz(const Linef3& ray, const Vec3d& center) -{ - Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd(-0.5f * (double)PI, Vec3d::UnitY())); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - - return Vec3d(0.0, mouse_pos_2d(1), -mouse_pos_2d(0)); -} - -// return an index: -// 0 for plane XY -// 1 for plane XZ -// 2 for plane YZ -// which indicates which plane is best suited for intersecting the given unit vector -// giving precedence to the plane with the given index -unsigned int select_best_plane(const Vec3d& unit_vector, unsigned int preferred_plane) -{ - unsigned int ret = preferred_plane; - - // 1st checks if the given vector is not parallel to the given preferred plane - double dot_to_normal = 0.0; - switch (ret) - { - case 0: // plane xy - { - dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitZ())); - break; - } - case 1: // plane xz - { - dot_to_normal = std::abs(unit_vector.dot(-Vec3d::UnitY())); - break; - } - case 2: // plane yz - { - dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitX())); - break; - } - default: - { - break; - } - } - - // if almost parallel, select the plane whose normal direction is closest to the given vector direction, - // otherwise return the given preferred plane index - if (dot_to_normal < 0.1) - { - typedef std::map ProjsMap; - ProjsMap projs_map; - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitZ())), 0)); // plane xy - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(-Vec3d::UnitY())), 1)); // plane xz - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitX())), 2)); // plane yz - ret = projs_map.rbegin()->second; - } - - return ret; -} - const float GLGizmoBase::Grabber::SizeFactor = 0.025f; const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; @@ -118,7 +55,7 @@ GLGizmoBase::Grabber::Grabber() color[2] = 1.0f; } -void GLGizmoBase::Grabber::render(bool hover, const BoundingBoxf3& box) const +void GLGizmoBase::Grabber::render(bool hover, float size) const { float render_color[3]; if (hover) @@ -130,27 +67,34 @@ void GLGizmoBase::Grabber::render(bool hover, const BoundingBoxf3& box) const else ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); - render(box, render_color, true); + render(size, render_color, true); } -void GLGizmoBase::Grabber::render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const +float GLGizmoBase::Grabber::get_half_size(float size) const { - float max_size = (float)box.max_size(); - float half_size = dragging ? max_size * SizeFactor * DraggingScaleFactor : max_size * SizeFactor; - half_size = std::max(half_size, MinHalfSize); + return std::max(size * SizeFactor, MinHalfSize); +} + +float GLGizmoBase::Grabber::get_dragging_half_size(float size) const +{ + return std::max(size * SizeFactor * DraggingScaleFactor, MinHalfSize); +} + +void GLGizmoBase::Grabber::render(float size, const float* render_color, bool use_lighting) const +{ + float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); if (use_lighting) ::glEnable(GL_LIGHTING); - ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]); + ::glColor3fv(render_color); ::glPushMatrix(); - ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2)); + ::glTranslated(center(0), center(1), center(2)); - float rad_to_deg = 180.0f / (GLfloat)PI; - ::glRotatef((GLfloat)angles(0) * rad_to_deg, 1.0f, 0.0f, 0.0f); - ::glRotatef((GLfloat)angles(1) * rad_to_deg, 0.0f, 1.0f, 0.0f); - ::glRotatef((GLfloat)angles(2) * rad_to_deg, 0.0f, 0.0f, 1.0f); + ::glRotated(Geometry::rad2deg(angles(2)), 0.0, 0.0, 1.0); + ::glRotated(Geometry::rad2deg(angles(1)), 0.0, 1.0, 0.0); + ::glRotated(Geometry::rad2deg(angles(0)), 1.0, 0.0, 0.0); // face min x ::glPushMatrix(); @@ -216,8 +160,12 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) : m_parent(parent) , m_group_id(-1) , m_state(Off) + , m_shortcut_key(0) , m_hover_id(-1) , m_dragging(false) +#if ENABLE_IMGUI + , m_imgui(wxGetApp().imgui()) +#endif // ENABLE_IMGUI { ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); @@ -255,7 +203,7 @@ void GLGizmoBase::disable_grabber(unsigned int id) on_disable_grabber(id); } -void GLGizmoBase::start_dragging(const BoundingBoxf3& box) +void GLGizmoBase::start_dragging(const GLCanvas3D::Selection& selection) { m_dragging = true; @@ -264,13 +212,12 @@ void GLGizmoBase::start_dragging(const BoundingBoxf3& box) m_grabbers[i].dragging = (m_hover_id == i); } - on_start_dragging(box); + on_start_dragging(selection); } void GLGizmoBase::stop_dragging() { m_dragging = false; - set_tooltip(""); for (int i = 0; i < (int)m_grabbers.size(); ++i) { @@ -280,10 +227,10 @@ void GLGizmoBase::stop_dragging() on_stop_dragging(); } -void GLGizmoBase::update(const Linef3& mouse_ray) +void GLGizmoBase::update(const UpdateData& data) { if (m_hover_id != -1) - on_update(mouse_ray); + on_update(data); } float GLGizmoBase::picking_color_component(unsigned int id) const @@ -297,15 +244,28 @@ float GLGizmoBase::picking_color_component(unsigned int id) const void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const { + float size = (float)box.max_size(); + for (int i = 0; i < (int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) - m_grabbers[i].render((m_hover_id == i), box); + m_grabbers[i].render((m_hover_id == i), size); + } +} + +void GLGizmoBase::render_grabbers(float size) const +{ + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + m_grabbers[i].render((m_hover_id == i), size); } } void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { + float size = (float)box.max_size(); + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) @@ -313,11 +273,15 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const m_grabbers[i].color[0] = 1.0f; m_grabbers[i].color[1] = 1.0f; m_grabbers[i].color[2] = picking_color_component(i); - m_grabbers[i].render_for_picking(box); + m_grabbers[i].render_for_picking(size); } } } +#if !ENABLE_IMGUI +void GLGizmoBase::create_external_gizmo_widgets(wxWindow *parent) {} +#endif // not ENABLE_IMGUI + void GLGizmoBase::set_tooltip(const std::string& tooltip) const { m_parent.set_tooltip(tooltip); @@ -325,9 +289,7 @@ void GLGizmoBase::set_tooltip(const std::string& tooltip) const std::string GLGizmoBase::format(float value, unsigned int decimals) const { - char buf[1024]; - ::sprintf(buf, "%.*f", decimals, value); - return buf; + return Slic3r::string_printf("%.*f", decimals, value); } const float GLGizmoRotate::Offset = 5.0f; @@ -336,18 +298,48 @@ const unsigned int GLGizmoRotate::AngleResolution = 64; const unsigned int GLGizmoRotate::ScaleStepsCount = 72; const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; const unsigned int GLGizmoRotate::ScaleLongEvery = 2; -const float GLGizmoRotate::ScaleLongTooth = 2.0f; -const float GLGizmoRotate::ScaleShortTooth = 1.0f; +const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius const unsigned int GLGizmoRotate::SnapRegionsCount = 8; -const float GLGizmoRotate::GrabberOffset = 5.0f; +const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) : GLGizmoBase(parent) , m_axis(axis) , m_angle(0.0) + , m_quadric(nullptr) , m_center(0.0, 0.0, 0.0) , m_radius(0.0f) + , m_snap_coarse_in_radius(0.0f) + , m_snap_coarse_out_radius(0.0f) + , m_snap_fine_in_radius(0.0f) + , m_snap_fine_out_radius(0.0f) { + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) + : GLGizmoBase(other.m_parent) + , m_axis(other.m_axis) + , m_angle(other.m_angle) + , m_quadric(nullptr) + , m_center(other.m_center) + , m_radius(other.m_radius) + , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) + , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) + , m_snap_fine_in_radius(other.m_snap_fine_in_radius) + , m_snap_fine_out_radius(other.m_snap_fine_out_radius) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::~GLGizmoRotate() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); } void GLGizmoRotate::set_angle(double angle) @@ -364,15 +356,20 @@ bool GLGizmoRotate::on_init() return true; } -void GLGizmoRotate::on_start_dragging(const BoundingBoxf3& box) +void GLGizmoRotate::on_start_dragging(const GLCanvas3D::Selection& selection) { + const BoundingBoxf3& box = selection.get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; } -void GLGizmoRotate::on_update(const Linef3& mouse_ray) -{ - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(mouse_ray)); +void GLGizmoRotate::on_update(const UpdateData& data) +{ + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray)); Vec2d orig_dir = Vec2d::UnitX(); Vec2d new_dir = mouse_pos.normalized(); @@ -383,20 +380,16 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray) double len = mouse_pos.norm(); - // snap to snap region - double in_radius = (double)m_radius / 3.0; - double out_radius = 2.0 * (double)in_radius; - if ((in_radius <= len) && (len <= out_radius)) + // snap to coarse snap region + if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) { double step = 2.0 * (double)PI / (double)SnapRegionsCount; theta = step * (double)std::round(theta / step); } else { - // snap to scale - in_radius = (double)m_radius; - out_radius = in_radius + (double)ScaleLongTooth; - if ((in_radius <= len) && (len <= out_radius)) + // snap to fine snap region (scale) + if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) { double step = 2.0 * (double)PI / (double)ScaleStepsCount; theta = step * (double)std::round(theta / step); @@ -409,17 +402,40 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray) m_angle = theta; } -void GLGizmoRotate::on_render(const BoundingBoxf3& box) const +void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const { if (!m_grabbers[0].enabled) return; - if (m_dragging) - set_tooltip(format(m_angle * 180.0f / (float)PI, 4)); + const BoundingBoxf3& box = selection.get_bounding_box(); +#if !ENABLE_WORLD_ROTATIONS + bool single_selection = selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume(); +#endif // !ENABLE_WORLD_ROTATIONS + + std::string axis; + switch (m_axis) + { + case X: { axis = "X"; break; } + case Y: { axis = "Y"; break; } + case Z: { axis = "Z"; break; } + } + +#if ENABLE_WORLD_ROTATIONS + if (!m_dragging && (m_hover_id == 0)) + set_tooltip(axis); + else if (m_dragging) +#else + if ((single_selection && (m_hover_id == 0)) || m_dragging) +#endif // ENABLE_WORLD_ROTATIONS + set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); else { m_center = box.center(); m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); } ::glEnable(GL_DEPTH_TEST); @@ -445,18 +461,22 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const render_angle(); render_grabber(box); + render_grabber_extension(box, false); ::glPopMatrix(); } -void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const +void GLGizmoRotate::on_render_for_picking(const GLCanvas3D::Selection& selection) const { ::glDisable(GL_DEPTH_TEST); ::glPushMatrix(); transform_to_local(); + + const BoundingBoxf3& box = selection.get_bounding_box(); render_grabbers_for_picking(box); + render_grabber_extension(box, true); ::glPopMatrix(); } @@ -477,8 +497,8 @@ void GLGizmoRotate::render_circle() const void GLGizmoRotate::render_scale() const { - float out_radius_long = m_radius + ScaleLongTooth; - float out_radius_short = m_radius + ScaleShortTooth; + float out_radius_long = m_snap_fine_out_radius; + float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); ::glBegin(GL_LINES); for (unsigned int i = 0; i < ScaleStepsCount; ++i) @@ -527,14 +547,14 @@ void GLGizmoRotate::render_reference_radius() const { ::glBegin(GL_LINES); ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)(m_radius + GrabberOffset), 0.0f, 0.0f); + ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); ::glEnd(); } void GLGizmoRotate::render_angle() const { float step_angle = (float)m_angle / AngleResolution; - float ex_radius = m_radius + GrabberOffset; + float ex_radius = m_radius * (1.0f + GrabberOffset); ::glBegin(GL_LINE_STRIP); for (unsigned int i = 0; i <= AngleResolution; ++i) @@ -550,7 +570,11 @@ void GLGizmoRotate::render_angle() const void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const { - double grabber_radius = (double)(m_radius + GrabberOffset); +#if ENABLE_WORLD_ROTATIONS + double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); +#else + double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset) + 2.0 * (double)m_axis * (double)m_grabbers[0].get_half_size((float)box.max_size()); +#endif // ENABLE_WORLD_ROTATIONS m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); m_grabbers[0].angles(2) = m_angle; @@ -558,29 +582,74 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const ::glBegin(GL_LINES); ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)m_grabbers[0].center(0), (GLfloat)m_grabbers[0].center(1), (GLfloat)m_grabbers[0].center(2)); + ::glVertex3dv(m_grabbers[0].center.data()); ::glEnd(); ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); render_grabbers(box); } +void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[0].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(-90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + void GLGizmoRotate::transform_to_local() const { - ::glTranslatef((GLfloat)m_center(0), (GLfloat)m_center(1), (GLfloat)m_center(2)); + ::glTranslated(m_center(0), m_center(1), m_center(2)); switch (m_axis) { case X: { ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - ::glRotatef(90.0f, 0.0f, 0.0f, 1.0f); + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); break; } case Y: { - ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); - ::glRotatef(180.0f, 0.0f, 0.0f, 1.0f); + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); + ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); break; } default: @@ -602,14 +671,14 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) cons { case X: { - m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitZ())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); break; } case Y: { - m.rotate(Eigen::AngleAxisd(-(double)PI, Vec3d::UnitZ())); - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitX())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); break; } default: @@ -653,25 +722,29 @@ bool GLGizmoRotate3D::on_init() std::string path = resources_dir() + "/icons/overlay/"; - std::string filename = path + "rotate_off.png"; - if (!m_textures[Off].load_from_file(filename, false)) + if (!m_textures[Off].load_from_file(path + "rotate_off.png", false)) return false; - filename = path + "rotate_hover.png"; - if (!m_textures[Hover].load_from_file(filename, false)) + if (!m_textures[Hover].load_from_file(path + "rotate_hover.png", false)) return false; - filename = path + "rotate_on.png"; - if (!m_textures[On].load_from_file(filename, false)) + if (!m_textures[On].load_from_file(path + "rotate_on.png", false)) return false; + m_shortcut_key = WXK_CONTROL_R; + return true; } -void GLGizmoRotate3D::on_start_dragging(const BoundingBoxf3& box) +std::string GLGizmoRotate3D::on_get_name() const +{ + return L("Rotate"); +} + +void GLGizmoRotate3D::on_start_dragging(const GLCanvas3D::Selection& selection) { if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(box); + m_gizmos[m_hover_id].start_dragging(selection); } void GLGizmoRotate3D::on_stop_dragging() @@ -680,26 +753,43 @@ void GLGizmoRotate3D::on_stop_dragging() m_gizmos[m_hover_id].stop_dragging(); } -void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const +void GLGizmoRotate3D::on_render(const GLCanvas3D::Selection& selection) const { + ::glClear(GL_DEPTH_BUFFER_BIT); + if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(box); + m_gizmos[X].render(selection); if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(box); + m_gizmos[Y].render(selection); if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(box); + m_gizmos[Z].render(selection); } +#if ENABLE_IMGUI +void GLGizmoRotate3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); + wxString label = _(L("Rotation (deg)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} +#endif // ENABLE_IMGUI + const float GLGizmoScale3D::Offset = 5.0f; -const Vec3d GLGizmoScale3D::OffsetVec = (double)GLGizmoScale3D::Offset * Vec3d::Ones(); GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent) : GLGizmoBase(parent) , m_scale(Vec3d::Ones()) + , m_snap_step(0.05) , m_starting_scale(Vec3d::Ones()) - , m_show_starting_box(false) { } @@ -707,16 +797,13 @@ bool GLGizmoScale3D::on_init() { std::string path = resources_dir() + "/icons/overlay/"; - std::string filename = path + "scale_off.png"; - if (!m_textures[Off].load_from_file(filename, false)) + if (!m_textures[Off].load_from_file(path + "scale_off.png", false)) return false; - filename = path + "scale_hover.png"; - if (!m_textures[Hover].load_from_file(filename, false)) + if (!m_textures[Hover].load_from_file(path + "scale_hover.png", false)) return false; - filename = path + "scale_on.png"; - if (!m_textures[On].load_from_file(filename, false)) + if (!m_textures[On].load_from_file(path + "scale_on.png", false)) return false; for (int i = 0; i < 10; ++i) @@ -734,87 +821,184 @@ bool GLGizmoScale3D::on_init() m_grabbers[2].angles(0) = half_pi; m_grabbers[3].angles(0) = half_pi; + m_shortcut_key = WXK_CONTROL_S; + return true; } -void GLGizmoScale3D::on_start_dragging(const BoundingBoxf3& box) +std::string GLGizmoScale3D::on_get_name() const +{ + return L("Scale"); +} + +void GLGizmoScale3D::on_start_dragging(const GLCanvas3D::Selection& selection) { if (m_hover_id != -1) { m_starting_drag_position = m_grabbers[m_hover_id].center; - m_show_starting_box = true; - m_starting_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec); + m_starting_box = selection.get_bounding_box(); } } -void GLGizmoScale3D::on_update(const Linef3& mouse_ray) +void GLGizmoScale3D::on_update(const UpdateData& data) { if ((m_hover_id == 0) || (m_hover_id == 1)) - do_scale_x(mouse_ray); + do_scale_x(data); else if ((m_hover_id == 2) || (m_hover_id == 3)) - do_scale_y(mouse_ray); + do_scale_y(data); else if ((m_hover_id == 4) || (m_hover_id == 5)) - do_scale_z(mouse_ray); + do_scale_z(data); else if (m_hover_id >= 6) - do_scale_uniform(mouse_ray); + do_scale_uniform(data); } -void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const +void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const { - if (m_grabbers[0].dragging || m_grabbers[1].dragging) - set_tooltip("X: " + format(100.0f * m_scale(0), 4) + "%"); - else if (m_grabbers[2].dragging || m_grabbers[3].dragging) - set_tooltip("Y: " + format(100.0f * m_scale(1), 4) + "%"); - else if (m_grabbers[4].dragging || m_grabbers[5].dragging) - set_tooltip("Z: " + format(100.0f * m_scale(2), 4) + "%"); - else if (m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) + bool single_instance = selection.is_single_full_instance(); + bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + bool single_selection = single_instance || single_volume; + + Vec3f scale = 100.0f * Vec3f::Ones(); +#if ENABLE_MODELVOLUME_TRANSFORM + if (single_instance) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); + else if (single_volume) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); +#else + Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_scaling_factor().cast() : 100.0f * m_scale.cast(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) + set_tooltip("X: " + format(scale(0), 4) + "%"); + else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1))) + set_tooltip("X"); + else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) + set_tooltip("Y: " + format(scale(1), 4) + "%"); + else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3))) + set_tooltip("Y"); + else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) + set_tooltip("Z: " + format(scale(2), 4) + "%"); + else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5))) + set_tooltip("Z"); + else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) { - std::string tooltip = "X: " + format(100.0f * m_scale(0), 4) + "%\n"; - tooltip += "Y: " + format(100.0f * m_scale(1), 4) + "%\n"; - tooltip += "Z: " + format(100.0f * m_scale(2), 4) + "%"; + std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; + tooltip += "Y: " + format(scale(1), 4) + "%\n"; + tooltip += "Z: " + format(scale(2), 4) + "%"; set_tooltip(tooltip); } + else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging && + ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + set_tooltip("X/Y/Z"); + ::glClear(GL_DEPTH_BUFFER_BIT); ::glEnable(GL_DEPTH_TEST); - m_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec); + BoundingBoxf3 box; + Transform3d transform = Transform3d::Identity(); + Vec3d angles = Vec3d::Zero(); + Transform3d offsets_transform = Transform3d::Identity(); + + Vec3d grabber_size = Vec3d::Zero(); + + if (single_instance) + { + // calculate bounding box in instance local reference system + const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) + { + const GLVolume* vol = selection.get_volume(idx); + box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + } + + // gets transform from first selected volume + const GLVolume* v = selection.get_volume(*idxs.begin()); +#if ENABLE_MODELVOLUME_TRANSFORM + transform = v->get_instance_transformation().get_matrix(); + // gets angles from first selected volume + angles = v->get_instance_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_instance_transformation().get_matrix(true, true, false, true) * box.size(); +#else + transform = v->world_matrix().cast(); + // gets angles from first selected volume + angles = v->get_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + else if (single_volume) + { + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + box = v->bounding_box; +#if ENABLE_MODELVOLUME_TRANSFORM + transform = v->world_matrix(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_volume_transformation().get_matrix(true, true, false, true) * box.size(); +#else + transform = v->world_matrix().cast(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + else + { + box = selection.get_bounding_box(); + grabber_size = box.size(); + } + + m_box = box; + const Vec3d& center = m_box.center(); + Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); // x axis - m_grabbers[0].center = Vec3d(m_box.min(0), center(1), center(2)); - m_grabbers[1].center = Vec3d(m_box.max(0), center(1), center(2)); + m_grabbers[0].center = transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; + m_grabbers[1].center = transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); // y axis - m_grabbers[2].center = Vec3d(center(0), m_box.min(1), center(2)); - m_grabbers[3].center = Vec3d(center(0), m_box.max(1), center(2)); + m_grabbers[2].center = transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; + m_grabbers[3].center = transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); ::memcpy((void*)m_grabbers[3].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); // z axis - m_grabbers[4].center = Vec3d(center(0), center(1), m_box.min(2)); - m_grabbers[5].center = Vec3d(center(0), center(1), m_box.max(2)); + m_grabbers[4].center = transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; + m_grabbers[5].center = transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; ::memcpy((void*)m_grabbers[4].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); ::memcpy((void*)m_grabbers[5].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); // uniform - m_grabbers[6].center = Vec3d(m_box.min(0), m_box.min(1), m_box.min(2)); - m_grabbers[7].center = Vec3d(m_box.max(0), m_box.min(1), m_box.min(2)); - m_grabbers[8].center = Vec3d(m_box.max(0), m_box.max(1), m_box.min(2)); - m_grabbers[9].center = Vec3d(m_box.min(0), m_box.max(1), m_box.min(2)); + m_grabbers[6].center = transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; + m_grabbers[7].center = transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; + m_grabbers[8].center = transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; + m_grabbers[9].center = transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); } + // sets grabbers orientation + for (int i = 0; i < 10; ++i) + { + m_grabbers[i].angles = angles; + } + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + float grabber_max_size = (float)std::max(grabber_size(0), std::max(grabber_size(1), grabber_size(2))); + if (m_hover_id == -1) { - // draw box - ::glColor3fv(m_base_color); - render_box(m_box); // draw connections if (m_grabbers[0].enabled && m_grabbers[1].enabled) { @@ -831,115 +1015,79 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const ::glColor3fv(m_grabbers[4].color); render_grabbers_connection(4, 5); } + ::glColor3fv(m_base_color); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); // draw grabbers - render_grabbers(m_box); + render_grabbers(grabber_max_size); } else if ((m_hover_id == 0) || (m_hover_id == 1)) { - // draw starting box - if (m_show_starting_box) - { - ::glColor3fv(m_base_color); - render_box(m_starting_box); - } - // draw current box - ::glColor3fv(m_drag_color); - render_box(m_box); // draw connection ::glColor3fv(m_grabbers[0].color); render_grabbers_connection(0, 1); // draw grabbers - m_grabbers[0].render(true, m_box); - m_grabbers[1].render(true, m_box); + m_grabbers[0].render(true, grabber_max_size); + m_grabbers[1].render(true, grabber_max_size); } else if ((m_hover_id == 2) || (m_hover_id == 3)) { - // draw starting box - if (m_show_starting_box) - { - ::glColor3fv(m_base_color); - render_box(m_starting_box); - } - // draw current box - ::glColor3fv(m_drag_color); - render_box(m_box); // draw connection ::glColor3fv(m_grabbers[2].color); render_grabbers_connection(2, 3); // draw grabbers - m_grabbers[2].render(true, m_box); - m_grabbers[3].render(true, m_box); + m_grabbers[2].render(true, grabber_max_size); + m_grabbers[3].render(true, grabber_max_size); } else if ((m_hover_id == 4) || (m_hover_id == 5)) { - // draw starting box - if (m_show_starting_box) - { - ::glColor3fv(m_base_color); - render_box(m_starting_box); - } - // draw current box - ::glColor3fv(m_drag_color); - render_box(m_box); // draw connection ::glColor3fv(m_grabbers[4].color); render_grabbers_connection(4, 5); // draw grabbers - m_grabbers[4].render(true, m_box); - m_grabbers[5].render(true, m_box); + m_grabbers[4].render(true, grabber_max_size); + m_grabbers[5].render(true, grabber_max_size); } else if (m_hover_id >= 6) { - // draw starting box - if (m_show_starting_box) - { - ::glColor3fv(m_base_color); - render_box(m_starting_box); - } - // draw current box + // draw connection ::glColor3fv(m_drag_color); - render_box(m_box); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); // draw grabbers for (int i = 6; i < 10; ++i) { - m_grabbers[i].render(true, m_box); + m_grabbers[i].render(true, grabber_max_size); } } } -void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const +void GLGizmoScale3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const { ::glDisable(GL_DEPTH_TEST); - render_grabbers_for_picking(box); + render_grabbers_for_picking(selection.get_bounding_box()); } -void GLGizmoScale3D::render_box(const BoundingBoxf3& box) const +#if ENABLE_IMGUI +void GLGizmoScale3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) { - // bottom face - ::glBegin(GL_LINE_LOOP); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.min(1), (GLfloat)box.min(2)); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.max(1), (GLfloat)box.min(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.max(1), (GLfloat)box.min(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.min(1), (GLfloat)box.min(2)); - ::glEnd(); +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool single_instance = selection.is_single_full_instance(); + wxString label = _(L("Scale (%)")); - // top face - ::glBegin(GL_LINE_LOOP); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.min(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.max(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.max(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.min(1), (GLfloat)box.max(2)); - ::glEnd(); - - // vertical edges - ::glBegin(GL_LINES); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.min(1), (GLfloat)box.min(2)); ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.min(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.max(1), (GLfloat)box.min(2)); ::glVertex3f((GLfloat)box.min(0), (GLfloat)box.max(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.max(1), (GLfloat)box.min(2)); ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.max(1), (GLfloat)box.max(2)); - ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.min(1), (GLfloat)box.min(2)); ::glVertex3f((GLfloat)box.max(0), (GLfloat)box.min(1), (GLfloat)box.max(2)); - ::glEnd(); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // ENABLE_IMGUI void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { @@ -947,81 +1095,67 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) { ::glBegin(GL_LINES); - ::glVertex3f((GLfloat)m_grabbers[id_1].center(0), (GLfloat)m_grabbers[id_1].center(1), (GLfloat)m_grabbers[id_1].center(2)); - ::glVertex3f((GLfloat)m_grabbers[id_2].center(0), (GLfloat)m_grabbers[id_2].center(1), (GLfloat)m_grabbers[id_2].center(2)); + ::glVertex3dv(m_grabbers[id_1].center.data()); + ::glVertex3dv(m_grabbers[id_2].center.data()); ::glEnd(); } } -void GLGizmoScale3D::do_scale_x(const Linef3& mouse_ray) +void GLGizmoScale3D::do_scale_x(const UpdateData& data) { - double ratio = calc_ratio(1, mouse_ray, m_starting_box.center()); - + double ratio = calc_ratio(data); if (ratio > 0.0) m_scale(0) = m_starting_scale(0) * ratio; } -void GLGizmoScale3D::do_scale_y(const Linef3& mouse_ray) +void GLGizmoScale3D::do_scale_y(const UpdateData& data) { - double ratio = calc_ratio(2, mouse_ray, m_starting_box.center()); - + double ratio = calc_ratio(data); if (ratio > 0.0) - m_scale(0) = m_starting_scale(1) * ratio; // << this is temporary -// m_scale(1) = m_starting_scale(1) * ratio; + m_scale(1) = m_starting_scale(1) * ratio; } -void GLGizmoScale3D::do_scale_z(const Linef3& mouse_ray) +void GLGizmoScale3D::do_scale_z(const UpdateData& data) { - double ratio = calc_ratio(1, mouse_ray, m_starting_box.center()); - + double ratio = calc_ratio(data); if (ratio > 0.0) - m_scale(0) = m_starting_scale(2) * ratio; // << this is temporary -// m_scale(2) = m_starting_scale(2) * ratio; + m_scale(2) = m_starting_scale(2) * ratio; } -void GLGizmoScale3D::do_scale_uniform(const Linef3& mouse_ray) +void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { - Vec3d center = m_starting_box.center(); - center(2) = m_box.min(2); - double ratio = calc_ratio(0, mouse_ray, center); - + double ratio = calc_ratio(data); if (ratio > 0.0) m_scale = m_starting_scale * ratio; } -double GLGizmoScale3D::calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const +double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d starting_vec = m_starting_drag_position - center; + // vector from the center to the starting position + Vec3d starting_vec = m_starting_drag_position - m_starting_box.center(); double len_starting_vec = starting_vec.norm(); - if (len_starting_vec == 0.0) - return ratio; + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; - Vec3d starting_vec_dir = starting_vec.normalized(); - Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds projection of the vector along the staring direction + double proj = inters_vec.dot(starting_vec.normalized()); - unsigned int plane_id = select_best_plane(mouse_dir, preferred_plane_id); - // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length - switch (plane_id) - { - case 0: - { - ratio = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, center)) / len_starting_vec; - break; - } - case 1: - { - ratio = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, center)) / len_starting_vec; - break; - } - case 2: - { - ratio = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, center)) / len_starting_vec; - break; - } + ratio = (len_starting_vec + proj) / len_starting_vec; } + if (data.shift_down) + ratio = m_snap_step * (double)std::round(ratio / m_snap_step); + return ratio; } @@ -1029,27 +1163,35 @@ const double GLGizmoMove3D::Offset = 10.0; GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent) : GLGizmoBase(parent) - , m_position(Vec3d::Zero()) + , m_displacement(Vec3d::Zero()) + , m_snap_step(1.0) , m_starting_drag_position(Vec3d::Zero()) , m_starting_box_center(Vec3d::Zero()) , m_starting_box_bottom_center(Vec3d::Zero()) + , m_quadric(nullptr) { + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoMove3D::~GLGizmoMove3D() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); } bool GLGizmoMove3D::on_init() { std::string path = resources_dir() + "/icons/overlay/"; - std::string filename = path + "move_off.png"; - if (!m_textures[Off].load_from_file(filename, false)) + if (!m_textures[Off].load_from_file(path + "move_off.png", false)) return false; - filename = path + "move_hover.png"; - if (!m_textures[Hover].load_from_file(filename, false)) + if (!m_textures[Hover].load_from_file(path + "move_hover.png", false)) return false; - filename = path + "move_on.png"; - if (!m_textures[On].load_from_file(filename, false)) + if (!m_textures[On].load_from_file(path + "move_on.png", false)) return false; for (int i = 0; i < 3; ++i) @@ -1057,13 +1199,22 @@ bool GLGizmoMove3D::on_init() m_grabbers.push_back(Grabber()); } + m_shortcut_key = WXK_CONTROL_M; + return true; } -void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box) +std::string GLGizmoMove3D::on_get_name() const +{ + return L("Move"); +} + +void GLGizmoMove3D::on_start_dragging(const GLCanvas3D::Selection& selection) { if (m_hover_id != -1) { + m_displacement = Vec3d::Zero(); + const BoundingBoxf3& box = selection.get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); @@ -1071,27 +1222,43 @@ void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box) } } -void GLGizmoMove3D::on_update(const Linef3& mouse_ray) +void GLGizmoMove3D::on_stop_dragging() { - if (m_hover_id == 0) - m_position(0) = 2.0 * m_starting_box_center(0) + calc_projection(X, 1, mouse_ray) - m_starting_drag_position(0); - else if (m_hover_id == 1) - m_position(1) = 2.0 * m_starting_box_center(1) + calc_projection(Y, 2, mouse_ray) - m_starting_drag_position(1); - else if (m_hover_id == 2) - m_position(2) = 2.0 * m_starting_box_bottom_center(2) + calc_projection(Z, 1, mouse_ray) - m_starting_drag_position(2); + m_displacement = Vec3d::Zero(); } -void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const +void GLGizmoMove3D::on_update(const UpdateData& data) { - if (m_grabbers[0].dragging) - set_tooltip("X: " + format(m_position(0), 2)); - else if (m_grabbers[1].dragging) - set_tooltip("Y: " + format(m_position(1), 2)); - else if (m_grabbers[2].dragging) - set_tooltip("Z: " + format(m_position(2), 2)); + if (m_hover_id == 0) + m_displacement(0) = calc_projection(data); + else if (m_hover_id == 1) + m_displacement(1) = calc_projection(data); + else if (m_hover_id == 2) + m_displacement(2) = calc_projection(data); +} +void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const +{ + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging) + set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2)); + else if (!m_grabbers[0].dragging && (m_hover_id == 0)) + set_tooltip("X"); + else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging) + set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2)); + else if (!m_grabbers[1].dragging && (m_hover_id == 1)) + set_tooltip("Y"); + else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging) + set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2)); + else if (!m_grabbers[2].dragging && (m_hover_id == 2)) + set_tooltip("Z"); + + ::glClear(GL_DEPTH_BUFFER_BIT); ::glEnable(GL_DEPTH_TEST); + const BoundingBoxf3& box = selection.get_bounding_box(); const Vec3d& center = box.center(); // x axis @@ -1117,72 +1284,131 @@ void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const { ::glColor3fv(AXES_COLOR[i]); ::glBegin(GL_LINES); - ::glVertex3f(center(0), center(1), center(2)); - ::glVertex3f((GLfloat)m_grabbers[i].center(0), (GLfloat)m_grabbers[i].center(1), (GLfloat)m_grabbers[i].center(2)); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[i].center.data()); ::glEnd(); } } // draw grabbers render_grabbers(box); + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, box, false); + } } else { // draw axis ::glColor3fv(AXES_COLOR[m_hover_id]); ::glBegin(GL_LINES); - ::glVertex3f(center(0), center(1), center(2)); - ::glVertex3f((GLfloat)m_grabbers[m_hover_id].center(0), (GLfloat)m_grabbers[m_hover_id].center(1), (GLfloat)m_grabbers[m_hover_id].center(2)); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[m_hover_id].center.data()); ::glEnd(); // draw grabber - m_grabbers[m_hover_id].render(true, box); + m_grabbers[m_hover_id].render(true, box.max_size()); + render_grabber_extension((Axis)m_hover_id, box, false); } } -void GLGizmoMove3D::on_render_for_picking(const BoundingBoxf3& box) const +void GLGizmoMove3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const { ::glDisable(GL_DEPTH_TEST); + const BoundingBoxf3& box = selection.get_bounding_box(); render_grabbers_for_picking(box); + render_grabber_extension(X, box, true); + render_grabber_extension(Y, box, true); + render_grabber_extension(Z, box, true); } -double GLGizmoMove3D::calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const +#if ENABLE_IMGUI +void GLGizmoMove3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + Vec3d displacement = show_position ? position : m_displacement; + wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); + + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} +#endif // ENABLE_IMGUI + +double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - Vec3d starting_vec = (axis == Z) ? m_starting_drag_position - m_starting_box_bottom_center : m_starting_drag_position - m_starting_box_center; + Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; double len_starting_vec = starting_vec.norm(); - if (len_starting_vec == 0.0) - return projection; + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; - Vec3d starting_vec_dir = starting_vec.normalized(); - Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } - unsigned int plane_id = select_best_plane(mouse_dir, preferred_plane_id); - - switch (plane_id) - { - case 0: - { - projection = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); - break; - } - case 1: - { - projection = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); - break; - } - case 2: - { - projection = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); - break; - } - } + if (data.shift_down) + projection = m_snap_step * (double)std::round(projection / m_snap_step); return projection; } +void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[axis].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2)); + if (axis == X) + ::glRotated(90.0, 0.0, 1.0, 0.0); + else if (axis == Y) + ::glRotated(-90.0, 1.0, 0.0, 0.0); + + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent) : GLGizmoBase(parent) , m_normal(Vec3d::Zero()) @@ -1194,115 +1420,105 @@ bool GLGizmoFlatten::on_init() { std::string path = resources_dir() + "/icons/overlay/"; - std::string filename = path + "layflat_off.png"; - if (!m_textures[Off].load_from_file(filename, false)) + if (!m_textures[Off].load_from_file(path + "layflat_off.png", false)) return false; - filename = path + "layflat_hover.png"; - if (!m_textures[Hover].load_from_file(filename, false)) + if (!m_textures[Hover].load_from_file(path + "layflat_hover.png", false)) return false; - filename = path + "layflat_on.png"; - if (!m_textures[On].load_from_file(filename, false)) + if (!m_textures[On].load_from_file(path + "layflat_on.png", false)) return false; + m_shortcut_key = WXK_CONTROL_F; + return true; } -void GLGizmoFlatten::on_start_dragging(const BoundingBoxf3& box) +std::string GLGizmoFlatten::on_get_name() const +{ + return L("Place on face"); +} + +bool GLGizmoFlatten::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + return selection.is_single_full_instance(); +} + +void GLGizmoFlatten::on_start_dragging(const GLCanvas3D::Selection& selection) { if (m_hover_id != -1) { m_normal = m_planes[m_hover_id].normal; - m_starting_center = box.center(); + m_starting_center = selection.get_bounding_box().center(); } } -void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const +void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const { - // the dragged_offset is a vector measuring where was the object moved - // with the gizmo being on. This is reset in set_flattening_data and - // does not work correctly when there are multiple copies. - Vec3d dragged_offset(Vec3d::Zero()); - if (m_dragging) - dragged_offset = box.center() - m_starting_center; + ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_BLEND); ::glEnable(GL_DEPTH_TEST); + ::glEnable(GL_BLEND); - for (int i=0; i<(int)m_planes.size(); ++i) { - if (i == m_hover_id) - ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); - else - ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); + if (selection.is_single_full_instance()) + { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glMultMatrixd(m.data()); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + if (i == m_hover_id) + ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); + else + ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); -#if ENABLE_MODELINSTANCE_3D_OFFSET - for (Vec3d offset : m_instances_positions) { - offset += dragged_offset; -#else - for (Vec2d offset : m_instances_positions) { - offset += to_2d(dragged_offset); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - ::glPushMatrix(); -#if ENABLE_MODELINSTANCE_3D_OFFSET - ::glTranslated(offset(0), offset(1), offset(2)); -#else - ::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) - ::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2)); + { + ::glVertex3dv(vertex.data()); + } ::glEnd(); - ::glPopMatrix(); } + ::glPopMatrix(); } + ::glEnable(GL_CULL_FACE); ::glDisable(GL_BLEND); } -void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const +void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selection) const { - ::glEnable(GL_DEPTH_TEST); + ::glDisable(GL_DEPTH_TEST); + ::glDisable(GL_BLEND); - for (unsigned int i = 0; i < m_planes.size(); ++i) + if (selection.is_single_full_instance()) { - ::glColor3f(1.0f, 1.0f, picking_color_component(i)); -#if ENABLE_MODELINSTANCE_3D_OFFSET - for (const Vec3d& offset : m_instances_positions) { -#else - for (const Vec2d& offset : m_instances_positions) { -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - ::glPushMatrix(); -#if ENABLE_MODELINSTANCE_3D_OFFSET - ::glTranslated(offset(0), offset(1), offset(2)); -#else - ::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glMultMatrixd(m.data()); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + ::glColor3f(1.0f, 1.0f, picking_color_component(i)); ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) - ::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2)); + { + ::glVertex3dv(vertex.data()); + } ::glEnd(); - ::glPopMatrix(); } + ::glPopMatrix(); } + + ::glEnable(GL_CULL_FACE); } void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) { + m_starting_center = Vec3d::Zero(); + bool object_changed = m_model_object != model_object; m_model_object = model_object; - // ...and save the updated positions of the object instances: - if (m_model_object && !m_model_object->instances.empty()) { - m_instances_positions.clear(); - for (const auto* instance : m_model_object->instances) -#if ENABLE_MODELINSTANCE_3D_OFFSET - m_instances_positions.emplace_back(instance->get_offset()); -#else - m_instances_positions.emplace_back(instance->offset); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - } - - if (is_plane_update_necessary()) + if (object_changed && is_plane_update_necessary()) update_planes(); } @@ -1310,10 +1526,22 @@ void GLGizmoFlatten::update_planes() { TriangleMesh ch; for (const ModelVolume* vol : m_model_object->volumes) +#if ENABLE_MODELVOLUME_TRANSFORM + { + if (vol->type() != ModelVolume::Type::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->get_convex_hull(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } +#else ch.merge(vol->get_convex_hull()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + ch = ch.convex_hull_3d(); - ch.scale(m_model_object->instances.front()->scaling_factor); - ch.rotate_z(m_model_object->instances.front()->rotation); + + const Vec3d& bb_size = ch.bounding_box().size(); + double min_bb_face_area = std::min(bb_size(0) * bb_size(1), std::min(bb_size(0) * bb_size(2), bb_size(1) * bb_size(2))); m_planes.clear(); @@ -1343,7 +1571,7 @@ void GLGizmoFlatten::update_planes() if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back(first_vertex[j](0), first_vertex[j](1), first_vertex[j](2)); + m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { @@ -1357,49 +1585,63 @@ void GLGizmoFlatten::update_planes() // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected anyway): if (m_planes.back().vertices.size() == 3 && - (m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < 1.f - || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < 1.f) + ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < 1.0 + || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < 1.0 + || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < 1.0)) m_planes.pop_back(); } + const float minimal_area = 0.01f * (float)min_bb_face_area; + // Now we'll go through all the polygons, transform the points into xy plane to process them: for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { Pointf3s& polygon = m_planes[polygon_id].vertices; const Vec3d& normal = m_planes[polygon_id].normal; // We are going to rotate about z and y to flatten the plane - float angle_z = 0.f; - float angle_y = 0.f; - if (std::abs(normal(1)) > 0.001) - angle_z = -atan2(normal(1), normal(0)); // angle to rotate so that normal ends up in xz-plane - if (std::abs(normal(0)*cos(angle_z) - normal(1)*sin(angle_z)) > 0.001) - angle_y = -atan2(normal(0)*cos(angle_z) - normal(1)*sin(angle_z), normal(2)); // angle to rotate to make normal point upwards - else { - // In case it already was in z-direction, we must ensure it is not the wrong way: - angle_y = normal(2) > 0.f ? 0.f : -PI; - } - - // Rotate all points to the xy plane: + Eigen::Quaterniond q; Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd((double)angle_y, Vec3d::UnitY())); - m.rotate(Eigen::AngleAxisd((double)angle_z, Vec3d::UnitZ())); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal, Vec3d::UnitZ()).toRotationMatrix(); polygon = transform(polygon, m); polygon = Slic3r::Geometry::convex_hull(polygon); // To remove the inner points - // We will calculate area of the polygon and discard ones that are too small + // We will calculate area of the polygons and discard ones that are too small // The limit is more forgiving in case the normal is in the direction of the coordinate axes - const float minimal_area = (std::abs(normal(0)) > 0.999f || std::abs(normal(1)) > 0.999f || std::abs(normal(2)) > 0.999f) ? 1.f : 20.f; + float area_threshold = (std::abs(normal(0)) > 0.999f || std::abs(normal(1)) > 0.999f || std::abs(normal(2)) > 0.999f) ? minimal_area : 10.0f * minimal_area; float& area = m_planes[polygon_id].area; area = 0.f; for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = std::abs(area / 2.f); - if (area < minimal_area) { + area = 0.5f * std::abs(area); + if (area < area_threshold) { m_planes.erase(m_planes.begin()+(polygon_id--)); continue; } + // We check the inner angles and discard polygons with angles smaller than the following threshold + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); + bool discard = false; + + for (unsigned int i = 0; i < polygon.size(); ++i) + { + const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; + const Vec3d& curr = polygon[i]; + const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; + + if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) + { + discard = true; + break; + } + } + + if (discard) + { + m_planes.erase(m_planes.begin() + (polygon_id--)); + continue; + } + // We will shrink the polygon a little bit so it does not touch the object edges: Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); centroid /= (double)polygon.size(); @@ -1459,13 +1701,9 @@ void GLGizmoFlatten::update_planes() m_planes.resize(std::min((int)m_planes.size(), 254)); // Planes are finished - let's save what we calculated it from: - m_source_data.bounding_boxes.clear(); - for (const auto& vol : m_model_object->volumes) - m_source_data.bounding_boxes.push_back(vol->get_convex_hull().bounding_box()); - m_source_data.scaling_factor = m_model_object->instances.front()->scaling_factor; - m_source_data.rotation = m_model_object->instances.front()->rotation; - const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); - m_source_data.mesh_first_point = Vec3d((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); + m_volumes_matrices.clear(); + for (const ModelVolume* vol : m_model_object->volumes) + m_volumes_matrices.push_back(vol->get_matrix()); } // Check if the bounding boxes of each volume's convex hull is the same as before @@ -1475,29 +1713,797 @@ bool GLGizmoFlatten::is_plane_update_necessary() const if (m_state != On || !m_model_object || m_model_object->instances.empty()) return false; - if (m_model_object->volumes.size() != m_source_data.bounding_boxes.size() - || m_model_object->instances.front()->scaling_factor != m_source_data.scaling_factor - || m_model_object->instances.front()->rotation != m_source_data.rotation) - return true; - - // now compare the bounding boxes: - for (unsigned int i=0; ivolumes.size(); ++i) - if (m_model_object->volumes[i]->get_convex_hull().bounding_box() != m_source_data.bounding_boxes[i]) - return true; - - const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); - Vec3d first_point((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); - if (first_point != m_source_data.mesh_first_point) + if (m_model_object->volumes.size() != m_volumes_matrices.size()) return true; + for (unsigned int i=0; i < m_model_object->volumes.size(); ++i) + if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i])) + return true; + return false; } -Vec3d GLGizmoFlatten::get_flattening_normal() const { - Vec3d normal = m_model_object->instances.front()->world_matrix(true).matrix().block(0, 0, 3, 3).inverse() * m_normal; +Vec3d GLGizmoFlatten::get_flattening_normal() const +{ + Vec3d out = m_normal; m_normal = Vec3d::Zero(); - return normal.normalized(); + m_starting_center = Vec3d::Zero(); + return out; } +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent) +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr) +#else + : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()) +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD +{ +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + // using GLU_FILL does not work when the instance's transformation + // contains mirroring (normals are reverted) + ::gluQuadricDrawStyle(m_quadric, GLU_SILHOUETTE); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD +} + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD +GLGizmoSlaSupports::~GLGizmoSlaSupports() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + +bool GLGizmoSlaSupports::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + if (!m_textures[Off].load_from_file(path + "sla_support_points_off.png", false)) + return false; + + if (!m_textures[Hover].load_from_file(path + "sla_support_points_hover.png", false)) + return false; + + if (!m_textures[On].load_from_file(path + "sla_support_points_on.png", false)) + return false; + + m_shortcut_key = WXK_CONTROL_L; + + return true; +} + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD +void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) +{ + m_starting_center = Vec3d::Zero(); + m_old_model_object = m_model_object; + m_model_object = model_object; + if (selection.is_empty()) + m_old_instance_id = -1; + + if ((model_object != nullptr) && selection.is_from_single_instance()) + { + if (is_mesh_update_necessary()) + update_mesh(); + } +} +#else +void GLGizmoSlaSupports::set_model_object_ptr(ModelObject* model_object) +{ + if (model_object != nullptr) { + m_starting_center = Vec3d::Zero(); + m_model_object = model_object; + + int selected_instance = m_parent.get_selection().get_instance_idx(); + assert(selected_instance < (int)model_object->instances.size()); + + m_instance_matrix = model_object->instances[selected_instance]->get_matrix(); + if (is_mesh_update_necessary()) + update_mesh(); + } +} +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + +void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const +{ + ::glEnable(GL_BLEND); + ::glEnable(GL_DEPTH_TEST); + +#if !ENABLE_SLA_SUPPORT_GIZMO_MOD + // the dragged_offset is a vector measuring where was the object moved + // with the gizmo being on. This is reset in set_model_object_ptr and + // does not work correctly when there are multiple copies. + + if (m_starting_center == Vec3d::Zero()) + m_starting_center = selection.get_bounding_box().center(); + Vec3d dragged_offset = selection.get_bounding_box().center() - m_starting_center; +#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD + + + for (auto& g : m_grabbers) { + g.color[0] = 1.f; + g.color[1] = 0.f; + g.color[2] = 0.f; + } + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + render_grabbers(selection, false); +#else + //::glTranslatef((GLfloat)dragged_offset(0), (GLfloat)dragged_offset(1), (GLfloat)dragged_offset(2)); + render_grabbers(false); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + +#if !ENABLE_IMGUI + render_tooltip_texture(); +#endif // not ENABLE_IMGUI + ::glDisable(GL_BLEND); +} + + +void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glEnable(GL_DEPTH_TEST); + for (unsigned int i=0; iget_sla_shift_z(); + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, z_shift); + + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glMultMatrixd(m.data()); + + if (!picking) + ::glEnable(GL_LIGHTING); + + float render_color[3]; + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + // first precalculate the grabber position in world coordinates, so that the grabber + // is not scaled with the object (as it would be if rendered with current gl matrix). + Eigen::Matrix glmatrix; + glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data()); + Eigen::Matrix grabber_pos; + for (int j=0; j<3; ++j) + grabber_pos(j) = m_grabbers[i].center(j); + grabber_pos[3] = 1.f; + Eigen::Matrix grabber_world_position = glmatrix * grabber_pos; + + if (!picking && (m_hover_id == i)) + { + render_color[0] = 1.0f - m_grabbers[i].color[0]; + render_color[1] = 1.0f - m_grabbers[i].color[1]; + render_color[2] = 1.0f - m_grabbers[i].color[2]; + } + else + ::memcpy((void*)render_color, (const void*)m_grabbers[i].color, 3 * sizeof(float)); + + ::glColor3fv(render_color); + ::glPushMatrix(); + ::glLoadIdentity(); + ::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift); + ::gluQuadricDrawStyle(m_quadric, GLU_SILHOUETTE); + ::gluSphere(m_quadric, 0.75, 64, 36); + ::glPopMatrix(); + } + + if (!picking) + ::glDisable(GL_LIGHTING); + + ::glPopMatrix(); +} +#else +void GLGizmoSlaSupports::render_grabbers(bool picking) const +{ + if (m_parent.get_selection().is_empty()) + return; + + float z_shift = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); + ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)z_shift); + + int selected_instance = m_parent.get_selection().get_instance_idx(); + assert(selected_instance < (int)m_model_object->instances.size()); + + float render_color_inactive[3] = { 0.5f, 0.5f, 0.5f }; + + for (const ModelInstance* inst : m_model_object->instances) { + bool active = inst == m_model_object->instances[selected_instance]; + if (picking && ! active) + continue; + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (!m_grabbers[i].enabled) + continue; + + float render_color[3]; + if (! picking && active && m_hover_id == i) { + render_color[0] = 1.0f - m_grabbers[i].color[0]; + render_color[1] = 1.0f - m_grabbers[i].color[1]; + render_color[2] = 1.0f - m_grabbers[i].color[2]; + } + else + ::memcpy((void*)render_color, active ? (const void*)m_grabbers[i].color : (const void*)render_color_inactive, 3 * sizeof(float)); + if (!picking) + ::glEnable(GL_LIGHTING); + ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]); + ::glPushMatrix(); + Vec3d center = inst->get_matrix() * m_grabbers[i].center; + ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2)); + GLUquadricObj *quadric; + quadric = ::gluNewQuadric(); + ::gluQuadricDrawStyle(quadric, GLU_FILL ); + ::gluSphere( quadric , 0.75f, 64 , 32 ); + ::gluDeleteQuadric(quadric); + ::glPopMatrix(); + if (!picking) + ::glDisable(GL_LIGHTING); + } + } + + ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)-z_shift); +} +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + +bool GLGizmoSlaSupports::is_mesh_update_necessary() const +{ +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + return (m_state == On) && (m_model_object != nullptr) && (m_model_object != m_old_model_object) && !m_model_object->instances.empty(); +#else + return m_state == On && m_model_object && !m_model_object->instances.empty() && !m_instance_matrix.isApprox(m_source_data.matrix); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + + //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) + // return false; + + // following should detect direct mesh changes (can be removed after the mesh is made completely immutable): + /*const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex(); + Vec3d first_point((double)first_vertex[0], (double)first_vertex[1], (double)first_vertex[2]); + if (first_point != m_source_data.mesh_first_point) + return true;*/ +} + +void GLGizmoSlaSupports::update_mesh() +{ + Eigen::MatrixXf& V = m_V; + Eigen::MatrixXi& F = m_F; + // Composite mesh of all instances in the world coordinate system. + // This mesh does not account for the possible Z up SLA offset. + TriangleMesh mesh = m_model_object->raw_mesh(); + const stl_file& stl = mesh.stl; + V.resize(3 * stl.stats.number_of_facets, 3); + F.resize(stl.stats.number_of_facets, 3); + for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); + V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); + V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + F(i, 0) = 3*i+0; + F(i, 1) = 3*i+1; + F(i, 2) = 3*i+2; + } + + m_AABB = igl::AABB(); + m_AABB.init(m_V, m_F); + +#if !ENABLE_SLA_SUPPORT_GIZMO_MOD + m_source_data.matrix = m_instance_matrix; +#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD + + // we'll now reload Grabbers (selection might have changed): + m_grabbers.clear(); + + for (const Vec3f& point : m_model_object->sla_support_points) { + m_grabbers.push_back(Grabber()); + m_grabbers.back().center = point.cast(); + } +} + +Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) +{ + // if the gizmo doesn't have the V, F structures for igl, calculate them first: +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + if (m_V.size() == 0) +#else + if (m_V.size() == 0 || is_mesh_update_necessary()) +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + update_mesh(); + + Eigen::Matrix viewport; + ::glGetIntegerv(GL_VIEWPORT, viewport.data()); + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Eigen::Matrix projection_matrix; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix.data()); + + Vec3d point1; + Vec3d point2; + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); + + igl::Hit hit; + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + const GLCanvas3D::Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); +#else + double z_offset = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1(2) -= z_offset; + point2(2) -= z_offset; +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); +#else + Transform3d inv = m_instance_matrix.inverse(); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1 = inv * point1; + point2 = inv * point2; + + if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) + throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + + int fid = hit.id; + Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); + return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); +} + +void GLGizmoSlaSupports::clicked_on_object(const Vec2d& mouse_position) +{ +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + int instance_id = m_parent.get_selection().get_instance_idx(); + if (m_old_instance_id != instance_id) + { + bool something_selected = (m_old_instance_id != -1); + m_old_instance_id = instance_id; + if (something_selected) + return; + } + if (instance_id == -1) + return; +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + + Vec3f new_pos; + try { + new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new grabber in that case + } + catch (...) { return; } + + m_grabbers.push_back(Grabber()); + m_grabbers.back().center = new_pos.cast(); + m_model_object->sla_support_points.push_back(new_pos); + + // This should trigger the support generation + // wxGetApp().plater()->reslice(); + + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLGizmoSlaSupports::delete_current_grabber(bool delete_all) +{ + if (delete_all) { + m_grabbers.clear(); + m_model_object->sla_support_points.clear(); + + // This should trigger the support generation + // wxGetApp().plater()->reslice(); + } + else + if (m_hover_id != -1) { + m_grabbers.erase(m_grabbers.begin() + m_hover_id); + m_model_object->sla_support_points.erase(m_model_object->sla_support_points.begin() + m_hover_id); + m_hover_id = -1; + + // This should trigger the support generation + // wxGetApp().plater()->reslice(); + } + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLGizmoSlaSupports::on_update(const UpdateData& data) +{ + if (m_hover_id != -1 && data.mouse_pos) { + Vec3f new_pos; + try { + new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + } + catch (...) { return; } + m_grabbers[m_hover_id].center = new_pos.cast(); + m_model_object->sla_support_points[m_hover_id] = new_pos; + // Do not update immediately, wait until the mouse is released. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + +#if !ENABLE_IMGUI +void GLGizmoSlaSupports::render_tooltip_texture() const { + if (m_tooltip_texture.get_id() == 0) + if (!m_tooltip_texture.load_from_file(resources_dir() + "/icons/sla_support_points_tooltip.png", false)) + return; + if (m_reset_texture.get_id() == 0) + if (!m_reset_texture.load_from_file(resources_dir() + "/icons/sla_support_points_reset.png", false)) + return; + + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float gap = 30.0f * inv_zoom; + + const Size& cnv_size = m_parent.get_canvas_size(); + float l = gap - cnv_size.get_width()/2.f * inv_zoom; + float r = l + (float)m_tooltip_texture.get_width() * inv_zoom; + float b = gap - cnv_size.get_height()/2.f * inv_zoom; + float t = b + (float)m_tooltip_texture.get_height() * inv_zoom; + + Rect reset_rect = m_parent.get_gizmo_reset_rect(m_parent, true); + + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); + GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); +} +#endif // not ENABLE_IMGUI + + +#if ENABLE_IMGUI +void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) +{ + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + m_imgui->text(_(L("Left mouse click - add point"))); + m_imgui->text(_(L("Right mouse click - remove point"))); + m_imgui->text(" "); + + bool generate = m_imgui->button(_(L("Generate points automatically"))); + bool remove_all_clicked = m_imgui->button(_(L("Remove all points")) + (m_model_object == nullptr ? "" : " (" + std::to_string(m_model_object->sla_support_points.size())+")")); + + m_imgui->end(); + + if (remove_all_clicked) + delete_current_grabber(true); + + if (generate) { + const DynamicPrintConfig& cfg = *wxGetApp().get_tab(Preset::TYPE_SLA_PRINT)->get_config(); + SLAAutoSupports::Config config; + config.density_at_horizontal = cfg.opt_int("support_density_at_horizontal") / 10000.f; + config.density_at_45 = cfg.opt_int("support_density_at_45") / 10000.f; + config.minimal_z = cfg.opt_float("support_minimal_z"); + + SLAAutoSupports sas(*m_model_object, config); + sas.generate(); + m_grabbers.clear(); + for (const Vec3f& point : m_model_object->sla_support_points) { + m_grabbers.push_back(Grabber()); + m_grabbers.back().center = point.cast(); + } + } + + if (remove_all_clicked || generate) { + m_parent.reload_scene(true); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} +#endif // ENABLE_IMGUI + +bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + && selection.is_from_single_instance(); +} + +bool GLGizmoSlaSupports::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoSlaSupports::on_get_name() const + +{ + return L("SLA Support Points"); +} + + + +// GLGizmoCut + +class GLGizmoCutPanel : public wxPanel +{ +public: + GLGizmoCutPanel(wxWindow *parent); + + void display(bool display); +private: + bool m_active; + wxCheckBox *m_cb_rotate; + wxButton *m_btn_cut; + wxButton *m_btn_cancel; +}; + +GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) + : wxPanel(parent) + , m_active(false) + , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) + , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) + , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) +{ + enum { MARGIN = 5 }; + + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + + auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); + sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->AddStretchSpacer(); + sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); + + SetSizer(sizer); +} + +void GLGizmoCutPanel::display(bool display) +{ + Show(display); + GetParent()->Layout(); +} + + +const double GLGizmoCut::Offset = 10.0; +const double GLGizmoCut::Margin = 20.0; +const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; + +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent) + : GLGizmoBase(parent) + , m_cut_z(0.0) + , m_max_z(0.0) +#if !ENABLE_IMGUI + , m_panel(nullptr) +#endif // not ENABLE_IMGUI + , m_keep_upper(true) + , m_keep_lower(true) + , m_rotate_lower(false) +{} + +#if !ENABLE_IMGUI +void GLGizmoCut::create_external_gizmo_widgets(wxWindow *parent) +{ + wxASSERT(m_panel == nullptr); + + m_panel = new GLGizmoCutPanel(parent); + parent->GetSizer()->Add(m_panel, 0, wxEXPAND); + + parent->Layout(); + parent->Fit(); + auto prev_heigh = parent->GetMinSize().GetHeight(); + parent->SetMinSize(wxSize(-1, std::max(prev_heigh, m_panel->GetSize().GetHeight()))); + + m_panel->Hide(); + m_panel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + perform_cut(m_parent.get_selection()); + }, wxID_OK); +} +#endif // not ENABLE_IMGUI + +bool GLGizmoCut::on_init() +{ + // TODO: icon + + std::string path = resources_dir() + "/icons/overlay/"; + + if (!m_textures[Off].load_from_file(path + "cut_off.png", false)) { + return false; + } + + if (!m_textures[Hover].load_from_file(path + "cut_hover.png", false)) { + return false; + } + + if (!m_textures[On].load_from_file(path + "cut_on.png", false)) { + return false; + } + + m_grabbers.emplace_back(); + + m_shortcut_key = WXK_CONTROL_C; + + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return L("Cut"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (get_state() == On) { + m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0; + } + +#if !ENABLE_IMGUI + // Display or hide the extra panel + if (m_panel != nullptr) { + m_panel->display(get_state() == On); + } +#endif // not ENABLE_IMGUI +} + +bool GLGizmoCut::on_is_activable(const GLCanvas3D::Selection& selection) const +{ + return selection.is_single_full_instance() && !selection.is_wipe_tower(); +} + +void GLGizmoCut::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id == -1) { return; } + + const BoundingBoxf3& box = selection.get_bounding_box(); + m_start_z = m_cut_z; + update_max_z(selection); + m_drag_pos = m_grabbers[m_hover_id].center; + m_drag_center = box.center(); + m_drag_center(2) = m_cut_z; +} + +void GLGizmoCut::on_update(const UpdateData& data) +{ + if (m_hover_id != -1) { + set_cut_z(m_start_z + calc_projection(data.mouse_ray)); + } +} + +void GLGizmoCut::on_render(const GLCanvas3D::Selection& selection) const +{ + if (m_grabbers[0].dragging) { + set_tooltip("Z: " + format(m_cut_z, 2)); + } + + update_max_z(selection); + + const BoundingBoxf3& box = selection.get_bounding_box(); + Vec3d plane_center = box.center(); + plane_center(2) = m_cut_z; + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + ::glEnable(GL_DEPTH_TEST); + ::glDisable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, plane_center(2)); + ::glVertex3f(max_x, min_y, plane_center(2)); + ::glVertex3f(max_x, max_y, plane_center(2)); + ::glVertex3f(min_x, max_y, plane_center(2)); + ::glEnd(); + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); + + // TODO: draw cut part contour? + + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center(2) = plane_center(2) + Offset; + + ::glDisable(GL_DEPTH_TEST); + ::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f); + ::glColor3f(1.0, 1.0, 0.0); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center.data()); + ::glVertex3dv(m_grabbers[0].center.data()); + ::glEnd(); + + std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color); + m_grabbers[0].render(m_hover_id == 0, box.max_size()); +} + +void GLGizmoCut::on_render_for_picking(const GLCanvas3D::Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + render_grabbers_for_picking(selection.get_bounding_box()); +} + +#if ENABLE_IMGUI +void GLGizmoCut::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) +{ + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + + m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); + m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); + m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower); + + const bool cut_clicked = m_imgui->button(_(L("Perform cut"))); + + m_imgui->end(); + + if (cut_clicked) { + perform_cut(selection); + } +} +#endif // ENABLE_IMGUI + +void GLGizmoCut::update_max_z(const GLCanvas3D::Selection& selection) const +{ + m_max_z = selection.get_bounding_box().size()(2); + set_cut_z(m_cut_z); +} + +void GLGizmoCut::set_cut_z(double cut_z) const +{ + // Clamp the plane to the object's bounding box + m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); +} + +void GLGizmoCut::perform_cut(const GLCanvas3D::Selection& selection) +{ + const auto instance_idx = selection.get_instance_idx(); + const auto object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); +} + +double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +{ + double projection = 0.0; + + const Vec3d starting_vec = m_drag_pos - m_drag_center; + const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_drag_pos; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + return projection; +} + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index 2430b5af5..bfec0965e 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -1,11 +1,24 @@ #ifndef slic3r_GLGizmo_hpp_ #define slic3r_GLGizmo_hpp_ -#include "../../slic3r/GUI/GLTexture.hpp" -#include "../../libslic3r/Point.hpp" -#include "../../libslic3r/BoundingBox.hpp" +#include +#include "../../slic3r/GUI/GLTexture.hpp" +#include "../../slic3r/GUI/GLCanvas3D.hpp" + +#include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/SLA/SLAAutoSupports.hpp" + +#include #include +#include + + +class wxWindow; +class GLUquadric; +typedef class GLUquadric GLUquadricObj; + namespace Slic3r { @@ -16,6 +29,9 @@ class ModelObject; namespace GUI { class GLCanvas3D; +#if ENABLE_IMGUI +class ImGuiWrapper; +#endif // ENABLE_IMGUI class GLGizmoBase { @@ -34,11 +50,14 @@ protected: Grabber(); - void render(bool hover, const BoundingBoxf3& box) const; - void render_for_picking(const BoundingBoxf3& box) const { render(box, color, false); } + void render(bool hover, float size) const; + void render_for_picking(float size) const { render(size, color, false); } + + float get_half_size(float size) const; + float get_dragging_half_size(float size) const; private: - void render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const; + void render(float size, const float* render_color, bool use_lighting) const; void render_face(float half_size) const; }; @@ -51,11 +70,23 @@ public: Num_States }; + struct UpdateData + { + const Linef3 mouse_ray; + const Point* mouse_pos; + bool shift_down; + + UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr, bool shift_down = false) + : mouse_ray(mouse_ray), mouse_pos(mouse_pos), shift_down(shift_down) + {} + }; + protected: GLCanvas3D& m_parent; int m_group_id; EState m_state; + int m_shortcut_key; // textures are assumed to be square and all with the same size in pixels, no internal check is done GLTexture m_textures[Num_States]; int m_hover_id; @@ -64,6 +95,9 @@ protected: float m_drag_color[3]; float m_highlight_color[3]; mutable std::vector m_grabbers; +#if ENABLE_IMGUI + ImGuiWrapper* m_imgui; +#endif // ENABLE_IMGUI public: explicit GLGizmoBase(GLCanvas3D& parent); @@ -71,12 +105,20 @@ public: bool init() { return on_init(); } + std::string get_name() const { return on_get_name(); } + int get_group_id() const { return m_group_id; } void set_group_id(int id) { m_group_id = id; } EState get_state() const { return m_state; } void set_state(EState state) { m_state = state; on_set_state(); } + int get_shortcut_key() const { return m_shortcut_key; } + void set_shortcut_key(int key) { m_shortcut_key = key; } + + bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); } + bool is_selectable() const { return on_is_selectable(); } + unsigned int get_texture_id() const { return m_textures[m_state].get_id(); } int get_textures_size() const { return m_textures[Off].get_width(); } @@ -88,29 +130,45 @@ public: void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); - void start_dragging(const BoundingBoxf3& box); + void start_dragging(const GLCanvas3D::Selection& selection); void stop_dragging(); bool is_dragging() const { return m_dragging; } - void update(const Linef3& mouse_ray); + void update(const UpdateData& data); - void render(const BoundingBoxf3& box) const { on_render(box); } - void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); } + void render(const GLCanvas3D::Selection& selection) const { on_render(selection); } + void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); } + +#if !ENABLE_IMGUI + virtual void create_external_gizmo_widgets(wxWindow *parent); +#endif // not ENABLE_IMGUI + +#if ENABLE_IMGUI + void render_input_window(float x, float y, const GLCanvas3D::Selection& selection) { on_render_input_window(x, y, selection); } +#endif // ENABLE_IMGUI protected: virtual bool on_init() = 0; + virtual std::string on_get_name() const = 0; virtual void on_set_state() {} virtual void on_set_hover_id() {} + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; } + virtual bool on_is_selectable() const { return true; } virtual void on_enable_grabber(unsigned int id) {} virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const BoundingBoxf3& box) {} + virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {} virtual void on_stop_dragging() {} - virtual void on_update(const Linef3& mouse_ray) = 0; - virtual void on_render(const BoundingBoxf3& box) const = 0; - virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0; + virtual void on_update(const UpdateData& data) = 0; + virtual void on_render(const GLCanvas3D::Selection& selection) const = 0; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0; + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) {} +#endif // ENABLE_IMGUI float picking_color_component(unsigned int id) const; void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers(float size) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; void set_tooltip(const std::string& tooltip) const; @@ -126,7 +184,6 @@ class GLGizmoRotate : public GLGizmoBase static const float ScaleStepRad; static const unsigned int ScaleLongEvery; static const float ScaleLongTooth; - static const float ScaleShortTooth; static const unsigned int SnapRegionsCount; static const float GrabberOffset; @@ -142,21 +199,31 @@ private: Axis m_axis; double m_angle; + GLUquadricObj* m_quadric; + mutable Vec3d m_center; mutable float m_radius; + mutable float m_snap_coarse_in_radius; + mutable float m_snap_coarse_out_radius; + mutable float m_snap_fine_in_radius; + mutable float m_snap_fine_out_radius; + public: GLGizmoRotate(GLCanvas3D& parent, Axis axis); + GLGizmoRotate(const GLGizmoRotate& other); + virtual ~GLGizmoRotate(); double get_angle() const { return m_angle; } void set_angle(double angle); protected: virtual bool on_init(); - virtual void on_start_dragging(const BoundingBoxf3& box); - virtual void on_update(const Linef3& mouse_ray); - virtual void on_render(const BoundingBoxf3& box) const; - virtual void on_render_for_picking(const BoundingBoxf3& box) const; + virtual std::string on_get_name() const { return ""; } + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; private: void render_circle() const; @@ -165,6 +232,7 @@ private: void render_reference_radius() const; void render_angle() const; void render_grabber(const BoundingBoxf3& box) const; + void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; void transform_to_local() const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate @@ -178,17 +246,12 @@ class GLGizmoRotate3D : public GLGizmoBase public: explicit GLGizmoRotate3D(GLCanvas3D& parent); - double get_angle_x() const { return m_gizmos[X].get_angle(); } - void set_angle_x(double angle) { m_gizmos[X].set_angle(angle); } - - double get_angle_y() const { return m_gizmos[Y].get_angle(); } - void set_angle_y(double angle) { m_gizmos[Y].set_angle(angle); } - - double get_angle_z() const { return m_gizmos[Z].get_angle(); } - void set_angle_z(double angle) { m_gizmos[Z].set_angle(angle); } + Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } protected: virtual bool on_init(); + virtual std::string on_get_name() const; virtual void on_set_state() { for (GLGizmoRotate& g : m_gizmos) @@ -203,6 +266,7 @@ protected: m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } virtual void on_enable_grabber(unsigned int id) { if ((0 <= id) && (id < 3)) @@ -213,97 +277,115 @@ protected: if ((0 <= id) && (id < 3)) m_gizmos[id].disable_grabber(0); } - virtual void on_start_dragging(const BoundingBoxf3& box); + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); virtual void on_stop_dragging(); - virtual void on_update(const Linef3& mouse_ray) + virtual void on_update(const UpdateData& data) { for (GLGizmoRotate& g : m_gizmos) { - g.update(mouse_ray); + g.update(data); } } - virtual void on_render(const BoundingBoxf3& box) const; - virtual void on_render_for_picking(const BoundingBoxf3& box) const + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const { for (const GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(box); + g.render_for_picking(selection); } } + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); +#endif // ENABLE_IMGUI }; class GLGizmoScale3D : public GLGizmoBase { static const float Offset; - static const Vec3d OffsetVec; mutable BoundingBoxf3 m_box; Vec3d m_scale; + double m_snap_step; + Vec3d m_starting_scale; Vec3d m_starting_drag_position; - bool m_show_starting_box; BoundingBoxf3 m_starting_box; public: explicit GLGizmoScale3D(GLCanvas3D& parent); - double get_scale_x() const { return m_scale(0); } - void set_scale_x(double scale) { m_starting_scale(0) = scale; } + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } - double get_scale_y() const { return m_scale(1); } - void set_scale_y(double scale) { m_starting_scale(1) = scale; } - - double get_scale_z() const { return m_scale(2); } - void set_scale_z(double scale) { m_starting_scale(2) = scale; } - - void set_scale(double scale) { m_starting_scale = scale * Vec3d::Ones(); } + const Vec3d& get_scale() const { return m_scale; } + void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; } protected: virtual bool on_init(); - virtual void on_start_dragging(const BoundingBoxf3& box); - virtual void on_stop_dragging() { m_show_starting_box = false; } - virtual void on_update(const Linef3& mouse_ray); - virtual void on_render(const BoundingBoxf3& box) const; - virtual void on_render_for_picking(const BoundingBoxf3& box) const; + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); +#endif // ENABLE_IMGUI private: - void render_box(const BoundingBoxf3& box) const; void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; - void do_scale_x(const Linef3& mouse_ray); - void do_scale_y(const Linef3& mouse_ray); - void do_scale_z(const Linef3& mouse_ray); - void do_scale_uniform(const Linef3& mouse_ray); + void do_scale_x(const UpdateData& data); + void do_scale_y(const UpdateData& data); + void do_scale_z(const UpdateData& data); + void do_scale_uniform(const UpdateData& data); - double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const; + double calc_ratio(const UpdateData& data) const; }; class GLGizmoMove3D : public GLGizmoBase { static const double Offset; - Vec3d m_position; + Vec3d m_displacement; + + double m_snap_step; + Vec3d m_starting_drag_position; Vec3d m_starting_box_center; Vec3d m_starting_box_bottom_center; + GLUquadricObj* m_quadric; + public: explicit GLGizmoMove3D(GLCanvas3D& parent); + virtual ~GLGizmoMove3D(); - const Vec3d& get_position() const { return m_position; } - void set_position(const Vec3d& position) { m_position = position; } + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + const Vec3d& get_displacement() const { return m_displacement; } protected: virtual bool on_init(); - virtual void on_start_dragging(const BoundingBoxf3& box); - virtual void on_update(const Linef3& mouse_ray); - virtual void on_render(const BoundingBoxf3& box) const; - virtual void on_render_for_picking(const BoundingBoxf3& box) const; + virtual std::string on_get_name() const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_stop_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); +#endif // ENABLE_IMGUI private: - double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const; + double calc_projection(const UpdateData& data) const; + void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; }; class GLGizmoFlatten : public GLGizmoBase @@ -318,24 +400,14 @@ private: Vec3d normal; float area; }; - struct SourceDataSummary { - std::vector bounding_boxes; // bounding boxes of convex hulls of individual volumes - float scaling_factor; - float rotation; - Vec3d mesh_first_point; - }; // This holds information to decide whether recalculation is necessary: - SourceDataSummary m_source_data; + std::vector m_volumes_matrices; std::vector m_planes; -#if ENABLE_MODELINSTANCE_3D_OFFSET - Pointf3s m_instances_positions; -#else - std::vector m_instances_positions; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - Vec3d m_starting_center; + mutable Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; + std::vector instances_matrices; void update_planes(); bool is_plane_update_necessary() const; @@ -348,10 +420,12 @@ public: protected: virtual bool on_init(); - virtual void on_start_dragging(const BoundingBoxf3& box); - virtual void on_update(const Linef3& mouse_ray) {} - virtual void on_render(const BoundingBoxf3& box) const; - virtual void on_render_for_picking(const BoundingBoxf3& box) const; + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data) {} + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; virtual void on_set_state() { if (m_state == On && is_plane_update_necessary()) @@ -359,6 +433,140 @@ protected: } }; +class GLGizmoSlaSupports : public GLGizmoBase +{ +private: + SLAAutoSupports* m_sas = nullptr; + ModelObject* m_model_object = nullptr; +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + ModelObject* m_old_model_object = nullptr; + int m_old_instance_id = -1; +#else + Transform3d m_instance_matrix; +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + Vec3f unproject_on_mesh(const Vec2d& mouse_pos); + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + GLUquadricObj* m_quadric; +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + + Eigen::MatrixXf m_V; // vertices + Eigen::MatrixXi m_F; // facets indices + igl::AABB m_AABB; + + struct SourceDataSummary { +#if !ENABLE_SLA_SUPPORT_GIZMO_MOD + BoundingBoxf3 bounding_box; + Transform3d matrix; +#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD + Vec3d mesh_first_point; + }; + + // This holds information to decide whether recalculation is necessary: + SourceDataSummary m_source_data; + + mutable Vec3d m_starting_center; + +public: + explicit GLGizmoSlaSupports(GLCanvas3D& parent); +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + virtual ~GLGizmoSlaSupports(); + void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); +#else + void set_model_object_ptr(ModelObject* model_object); +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + void clicked_on_object(const Vec2d& mouse_position); + void delete_current_grabber(bool delete_all); + +private: + bool on_init(); + void on_update(const UpdateData& data); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +#if ENABLE_SLA_SUPPORT_GIZMO_MOD + void render_grabbers(const GLCanvas3D::Selection& selection, bool picking = false) const; +#else + void render_grabbers(bool picking = false) const; +#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + bool is_mesh_update_necessary() const; + void update_mesh(); + +#if !ENABLE_IMGUI + void render_tooltip_texture() const; + mutable GLTexture m_tooltip_texture; + mutable GLTexture m_reset_texture; +#endif // not ENABLE_IMGUI + +protected: + void on_set_state() override { + if (m_state == On && is_mesh_update_necessary()) { + update_mesh(); + } + } + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override; +#endif // ENABLE_IMGUI + + virtual std::string on_get_name() const; + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual bool on_is_selectable() const; +}; + + +#if !ENABLE_IMGUI +class GLGizmoCutPanel; +#endif // not ENABLE_IMGUI + +class GLGizmoCut : public GLGizmoBase +{ + static const double Offset; + static const double Margin; + static const std::array GrabberColor; + + mutable double m_cut_z; + double m_start_z; + mutable double m_max_z; + Vec3d m_drag_pos; + Vec3d m_drag_center; + bool m_keep_upper; + bool m_keep_lower; + bool m_rotate_lower; +#if !ENABLE_IMGUI + GLGizmoCutPanel *m_panel; +#endif // not ENABLE_IMGUI + +public: + explicit GLGizmoCut(GLCanvas3D& parent); + +#if !ENABLE_IMGUI + virtual void create_external_gizmo_widgets(wxWindow *parent); +#endif // not ENABLE_IMGUI +#if !ENABLE_IMGUI +#endif // not ENABLE_IMGUI + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_set_state(); + virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; + virtual void on_start_dragging(const GLCanvas3D::Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render(const GLCanvas3D::Selection& selection) const; + virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; + +#if ENABLE_IMGUI + virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); +#endif // ENABLE_IMGUI +private: + void update_max_z(const GLCanvas3D::Selection& selection) const; + void set_cut_z(double cut_z) const; + void perform_cut(const GLCanvas3D::Selection& selection); + double calc_projection(const Linef3& mouse_ray) const; +}; + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index e2995f7c3..791f452ef 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -2,7 +2,7 @@ #include "GLShader.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/Utils.hpp" #include #include @@ -135,7 +135,7 @@ bool GLShader::load_from_file(const char* fragment_shader_filename, const char* return false; vs.seekg(0, vs.end); - int file_length = vs.tellg(); + int file_length = (int)vs.tellg(); vs.seekg(0, vs.beg); std::string vertex_shader(file_length, '\0'); vs.read(const_cast(vertex_shader.data()), file_length); @@ -149,7 +149,7 @@ bool GLShader::load_from_file(const char* fragment_shader_filename, const char* return false; fs.seekg(0, fs.end); - file_length = fs.tellg(); + file_length = (int)fs.tellg(); fs.seekg(0, fs.beg); std::string fragment_shader(file_length, '\0'); fs.read(const_cast(fragment_shader.data()), file_length); diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 803b2f154..0634cce6e 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -1,8 +1,8 @@ #ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 388868b12..43da81809 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -1,24 +1,40 @@ -#include "../../libslic3r/Point.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" + #include "GLToolbar.hpp" -#include "../../libslic3r/libslic3r.h" #include "../../slic3r/GUI/GLCanvas3D.hpp" #include +#include #include #include #include +#include namespace Slic3r { namespace GUI { + +wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); + +wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent); +wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); + GLToolbarItem::Data::Data() : name("") , tooltip("") , sprite_id(-1) , is_toggable(false) - , action_callback(nullptr) { } @@ -49,10 +65,9 @@ const std::string& GLToolbarItem::get_tooltip() const return m_data.tooltip; } -void GLToolbarItem::do_action() +void GLToolbarItem::do_action(wxEvtHandler *target) { - if (m_data.action_callback != nullptr) - m_data.action_callback->call(); + wxPostEvent(target, SimpleEvent(m_data.action_event)); } bool GLToolbarItem::is_enabled() const @@ -60,6 +75,13 @@ bool GLToolbarItem::is_enabled() const return m_state != Disabled; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbarItem::is_disabled() const +{ + return m_state == Disabled; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool GLToolbarItem::is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); @@ -109,28 +131,96 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned i return uvs; } -GLToolbar::ItemsIconsTexture::ItemsIconsTexture() +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +ItemsIconsTexture::Metadata::Metadata() + : filename("") + , icon_size(0) + , icon_border_size(0) + , icon_gap_size(0) +{ +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE +ItemsIconsTexture::ItemsIconsTexture() : items_icon_size(0) , items_icon_border_size(0) , items_icon_gap_size(0) { } +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +BackgroundTexture::Metadata::Metadata() + : filename("") + , left(0) + , right(0) + , top(0) + , bottom(0) +{ +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLToolbar::Layout::Layout() : type(Horizontal) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , orientation(Center) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , top(0.0f) , left(0.0f) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , border(0.0f) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , separator_size(0.0f) , gap_size(0.0f) + , icons_scale(1.0f) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , width(0.0f) + , height(0.0f) + , dirty(true) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +GLToolbar::GLToolbar(GLToolbar::EType type) + : m_type(type) +#else GLToolbar::GLToolbar(GLCanvas3D& parent) : m_parent(parent) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , m_enabled(false) { } +GLToolbar::~GLToolbar() +{ + for (GLToolbarItem* item : m_items) + { + delete item; + } +} + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) +{ + if (m_icons_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); + if (res) + m_icons_texture.metadata = icons_texture; + + if (!background_texture.filename.empty()) + res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); + + if (res) + m_background_texture.metadata = background_texture; + + return res; +} +#else bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size) { std::string path = resources_dir() + "/icons/"; @@ -144,31 +234,69 @@ bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int ite return res; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE -GLToolbar::Layout::Type GLToolbar::get_layout_type() const +GLToolbar::Layout::EType GLToolbar::get_layout_type() const { return m_layout.type; } -void GLToolbar::set_layout_type(GLToolbar::Layout::Type type) +void GLToolbar::set_layout_type(GLToolbar::Layout::EType type) { m_layout.type = type; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +GLToolbar::Layout::EOrientation GLToolbar::get_layout_orientation() const +{ + return m_layout.orientation; +} + +void GLToolbar::set_layout_orientation(GLToolbar::Layout::EOrientation orientation) +{ + m_layout.orientation = orientation; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void GLToolbar::set_position(float top, float left) { m_layout.top = top; m_layout.left = left; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::set_border(float border) +{ + m_layout.border = border; + m_layout.dirty = true; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void GLToolbar::set_separator_size(float size) { m_layout.separator_size = size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } void GLToolbar::set_gap_size(float size) { m_layout.gap_size = size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +} + +void GLToolbar::set_icons_scale(float scale) +{ + m_layout.icons_scale = scale; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } bool GLToolbar::is_enabled() const @@ -188,6 +316,9 @@ bool GLToolbar::add_item(const GLToolbarItem::Data& data) return false; m_items.push_back(item); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return true; } @@ -199,11 +330,20 @@ bool GLToolbar::add_separator() return false; m_items.push_back(item); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return true; } float GLToolbar::get_width() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_layout.dirty) + calc_layout(); + + return m_layout.width; +#else switch (m_layout.type) { default: @@ -216,10 +356,17 @@ float GLToolbar::get_width() const return get_width_vertical(); } } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_layout.dirty) + calc_layout(); + + return m_layout.height; +#else switch (m_layout.type) { default: @@ -232,6 +379,7 @@ float GLToolbar::get_height() const return get_height_vertical(); } } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } void GLToolbar::enable_item(const std::string& name) @@ -258,6 +406,23 @@ void GLToolbar::disable_item(const std::string& name) } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::select_item(const std::string& name) +{ + if (is_item_disabled(name)) + return; + + for (GLToolbarItem* item : m_items) + { + if (!item->is_disabled()) + { + bool hover = item->is_hovered(); + item->set_state((item->get_name() == name) ? (hover ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed) : (hover ? GLToolbarItem::Hover : GLToolbarItem::Normal)); + } + } +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool GLToolbar::is_item_pressed(const std::string& name) const { for (GLToolbarItem* item : m_items) @@ -269,28 +434,69 @@ bool GLToolbar::is_item_pressed(const std::string& name) const return false; } -void GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbar::is_item_disabled(const std::string& name) const { + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == name) + return item->is_disabled(); + } + + return false; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else +std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else +void GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (!m_enabled) + return ""; +#else if (!m_enabled) return; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER switch (m_layout.type) { default: - case Layout::Horizontal: - { - update_hover_state_horizontal(mouse_pos); - break; - } - case Layout::Vertical: - { - update_hover_state_vertical(mouse_pos); - break; - } +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return update_hover_state_vertical(mouse_pos, parent); } +#else + case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos); } + case Layout::Vertical: { return update_hover_state_vertical(mouse_pos); } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos, parent); break; } + case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; } +#else + case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos); break; } + case Layout::Vertical: { update_hover_state_vertical(mouse_pos); break; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (!m_enabled) return -1; @@ -298,18 +504,21 @@ int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const switch (m_layout.type) { default: - case Layout::Horizontal: - { - return contains_mouse_horizontal(mouse_pos); - } - case Layout::Vertical: - { - return contains_mouse_vertical(mouse_pos); - } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } +#else + case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos); } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +#else void GLToolbar::do_action(unsigned int item_id) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (item_id < (unsigned int)m_items.size()) { @@ -324,26 +533,51 @@ void GLToolbar::do_action(unsigned int item_id) else if (state == GLToolbarItem::HoverPressed) item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); + item->do_action(parent.get_wxglcanvas()); +#else m_parent.render(); - item->do_action(); + item->do_action(m_parent.get_wxglcanvas()); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } else { - item->set_state(GLToolbarItem::HoverPressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_type == Radio) + select_item(item->get_name()); + else +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->set_state(GLToolbarItem::HoverPressed); + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); + item->do_action(parent.get_wxglcanvas()); + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) +#else m_parent.render(); - item->do_action(); + item->do_action(m_parent.get_wxglcanvas()); if (item->get_state() != GLToolbarItem::Disabled) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { // the item may get disabled during the action, if not, set it back to hover state item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); +#else m_parent.render(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } } } } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render(const GLCanvas3D& parent) const +#else void GLToolbar::render() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (!m_enabled || m_items.empty()) return; @@ -356,21 +590,42 @@ void GLToolbar::render() const switch (m_layout.type) { default: - case Layout::Horizontal: - { - render_horizontal(); - break; - } - case Layout::Vertical: - { - render_vertical(); - break; - } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { render_horizontal(parent); break; } + case Layout::Vertical: { render_vertical(parent); break; } +#else + case Layout::Horizontal: { render_horizontal(); break; } + case Layout::Vertical: { render_vertical(); break; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } ::glPopMatrix(); } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::calc_layout() const +{ + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + m_layout.width = get_width_horizontal(); + m_layout.height = get_height_horizontal(); + break; + } + case Layout::Vertical: + { + m_layout.width = get_width_vertical(); + m_layout.height = get_height_vertical(); + break; + } + } + + m_layout.dirty = false; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float GLToolbar::get_width_horizontal() const { return get_main_size(); @@ -378,12 +633,20 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { - return m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + return m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height_horizontal() const { - return m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + return m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height_vertical() const @@ -393,13 +656,21 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float size = 2.0f * m_layout.border; +#else float size = 0.0f; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { if (m_items[i]->is_separator()) size += m_layout.separator_size; else - size += (float)m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + size += (float)m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } if (m_items.size() > 1) @@ -408,23 +679,55 @@ float GLToolbar::get_main_size() const return size; } +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else +std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE std::string tooltip = ""; @@ -445,7 +748,14 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) case GLToolbarItem::Normal: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -454,15 +764,29 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Normal); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } case GLToolbarItem::Pressed: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::HoverPressed); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } case GLToolbarItem::HoverPressed: @@ -470,8 +794,15 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Pressed); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } default: @@ -485,26 +816,63 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) } } - m_parent.set_tooltip(tooltip); +#if ENABLE_REMOVE_TABS_FROM_PLATER + return tooltip; +#else + if (!tooltip.empty()) + m_parent.set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else +std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE std::string tooltip = ""; @@ -525,7 +893,14 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) case GLToolbarItem::Normal: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -534,14 +909,28 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Normal); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } case GLToolbarItem::Pressed: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::HoverPressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -550,7 +939,14 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Pressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -565,26 +961,54 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) } } +#if ENABLE_REMOVE_TABS_FROM_PLATER + return tooltip; +#else m_parent.set_tooltip(tooltip); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE int id = -1; @@ -609,23 +1033,47 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const return -1; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE int id = -1; @@ -650,7 +1098,11 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const return -1; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render_horizontal(const GLCanvas3D& parent) const +#else void GLToolbar::render_horizontal() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { unsigned int tex_id = m_icons_texture.texture.get_id(); int tex_size = m_icons_texture.texture.get_width(); @@ -658,18 +1110,124 @@ void GLToolbar::render_horizontal() const if ((tex_id == 0) || (tex_size <= 0)) return; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; + float scaled_width = get_width() * inv_zoom; + float scaled_height = get_height() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE float left = m_layout.left; float top = m_layout.top; + float right = left + scaled_width; + float bottom = top - scaled_height; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + switch (m_layout.orientation) + { + case Layout::Top: + { + bg_uv_top = bg_uv_i_top; + bg_i_top = bg_top; + break; + } + case Layout::Bottom: + { + bg_uv_bottom = bg_uv_i_bottom; + bg_i_bottom = bg_bottom; + break; + } + case Layout::Center: + { + break; + } + }; + + if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + left += scaled_border; + top -= scaled_border; +#else + float left = m_layout.left; + float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE // renders icons for (const GLToolbarItem* item : m_items) @@ -678,13 +1236,21 @@ void GLToolbar::render_horizontal() const left += separator_stride; else { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#else item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE left += icon_stride; } } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render_vertical(const GLCanvas3D& parent) const +#else void GLToolbar::render_vertical() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { unsigned int tex_id = m_icons_texture.texture.get_id(); int tex_size = m_icons_texture.texture.get_width(); @@ -692,18 +1258,124 @@ void GLToolbar::render_vertical() const if ((tex_id == 0) || (tex_size <= 0)) return; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; + float scaled_width = get_width() * inv_zoom; + float scaled_height = get_height() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE float left = m_layout.left; float top = m_layout.top; + float right = left + scaled_width; + float bottom = top - scaled_height; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + switch (m_layout.orientation) + { + case Layout::Left: + { + bg_uv_left = bg_uv_i_left; + bg_i_left = bg_left; + break; + } + case Layout::Right: + { + bg_uv_right = bg_uv_i_right; + bg_i_right = bg_right; + break; + } + case Layout::Center: + { + break; + } + }; + + if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + left += scaled_border; + top -= scaled_border; +#else + float left = m_layout.left; + float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE // renders icons for (const GLToolbarItem* item : m_items) @@ -712,11 +1384,321 @@ void GLToolbar::render_vertical() const top -= separator_stride; else { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#else item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE top -= icon_stride; } } } +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE +GLRadioToolbarItem::Data::Data() + : name("") + , tooltip("") + , sprite_id(-1) +{ +} + +GLRadioToolbarItem::GLRadioToolbarItem(const GLRadioToolbarItem::Data& data) + : m_state(Normal) + , m_data(data) +{ +} + +GLRadioToolbarItem::EState GLRadioToolbarItem::get_state() const +{ + return m_state; +} + +void GLRadioToolbarItem::set_state(GLRadioToolbarItem::EState state) +{ + m_state = state; +} + +const std::string& GLRadioToolbarItem::get_name() const +{ + return m_data.name; +} + +const std::string& GLRadioToolbarItem::get_tooltip() const +{ + return m_data.tooltip; +} + +bool GLRadioToolbarItem::is_hovered() const +{ + return (m_state == Hover) || (m_state == HoverPressed); +} + +bool GLRadioToolbarItem::is_pressed() const +{ + return (m_state == Pressed) || (m_state == HoverPressed); +} + +void GLRadioToolbarItem::do_action(wxEvtHandler *target) +{ + wxPostEvent(target, SimpleEvent(m_data.action_event)); +} + +void GLRadioToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size)); +} + +GLTexture::Quad_UVs GLRadioToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::Quad_UVs uvs; + + float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; + + float scaled_icon_size = (float)icon_size * inv_texture_size; + float scaled_border_size = (float)border_size * inv_texture_size; + float scaled_gap_size = (float)gap_size * inv_texture_size; + float stride = scaled_icon_size + scaled_gap_size; + + float left = scaled_border_size + (float)m_state * stride; + float right = left + scaled_icon_size; + float top = scaled_border_size + (float)m_data.sprite_id * stride; + float bottom = top + scaled_icon_size; + + uvs.left_top = { left, top }; + uvs.left_bottom = { left, bottom }; + uvs.right_bottom = { right, bottom }; + uvs.right_top = { right, top }; + + return uvs; +} + +GLRadioToolbar::GLRadioToolbar() + : m_top(0.0f) + , m_left(0.0f) +{ +} + +GLRadioToolbar::~GLRadioToolbar() +{ + for (GLRadioToolbarItem* item : m_items) + { + delete item; + } +} + +bool GLRadioToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size) +{ + if (m_icons_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false); + if (res) + { + m_icons_texture.items_icon_size = items_icon_size; + m_icons_texture.items_icon_border_size = items_icon_border_size; + m_icons_texture.items_icon_gap_size = items_icon_gap_size; + } + + return res; +} + +bool GLRadioToolbar::add_item(const GLRadioToolbarItem::Data& data) +{ + GLRadioToolbarItem* item = new GLRadioToolbarItem(data); + if (item == nullptr) + return false; + + m_items.push_back(item); + return true; +} + +float GLRadioToolbar::get_height() const +{ + return m_icons_texture.items_icon_size; +} + +void GLRadioToolbar::set_position(float top, float left) +{ + m_top = top; + m_left = left; +} + +void GLRadioToolbar::set_selection(const std::string& name) +{ + for (GLRadioToolbarItem* item : m_items) + { + item->set_state((item->get_name() == name) ? GLRadioToolbarItem::Pressed : GLRadioToolbarItem::Normal); + } +} + +int GLRadioToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +{ + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = parent.get_canvas_size(); + Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + int id = -1; + + for (GLRadioToolbarItem* item : m_items) + { + ++id; + + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return id; + + left += scaled_icons_size; + } + + return -1; +} + +std::string GLRadioToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) +{ + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = parent.get_canvas_size(); + Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + std::string tooltip = ""; + + for (GLRadioToolbarItem* item : m_items) + { + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + GLRadioToolbarItem::EState state = item->get_state(); + bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); + + switch (state) + { + case GLRadioToolbarItem::Normal: + { + if (inside) + { + item->set_state(GLRadioToolbarItem::Hover); + parent.set_as_dirty(); + } + + break; + } + case GLRadioToolbarItem::Hover: + { + if (inside) + tooltip = item->get_tooltip(); + else + { + item->set_state(GLRadioToolbarItem::Normal); + parent.set_as_dirty(); + } + + break; + } + case GLRadioToolbarItem::Pressed: + { + if (inside) + { + item->set_state(GLRadioToolbarItem::HoverPressed); + parent.set_as_dirty(); + } + + break; + } + case GLRadioToolbarItem::HoverPressed: + { + if (inside) + tooltip = item->get_tooltip(); + else + { + item->set_state(GLRadioToolbarItem::Pressed); + parent.set_as_dirty(); + } + + break; + } + default: + { + break; + } + } + + left += scaled_icons_size; + } + + return tooltip; +} + +void GLRadioToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +{ + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (i != item_id) + m_items[i]->set_state(GLRadioToolbarItem::Normal); + } + + if (item_id < (unsigned int)m_items.size()) + { + GLRadioToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && item->is_hovered() && !item->is_pressed()) + { + item->set_state(GLRadioToolbarItem::HoverPressed); + item->do_action(parent.get_wxglcanvas()); + } + } + + parent.set_as_dirty(); +} + +void GLRadioToolbar::render(const GLCanvas3D& parent) const +{ + if (m_items.empty()) + return; + + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + ::glLoadIdentity(); + + unsigned int tex_id = m_icons_texture.texture.get_id(); + int tex_size = m_icons_texture.texture.get_width(); + + if ((tex_id == 0) || (tex_size <= 0)) + return; + + float zoom = parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + + float left = m_left; + float top = m_top; + + // renders icons + for (const GLRadioToolbarItem* item : m_items) + { + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); + left += scaled_icons_size; + } + + ::glPopMatrix(); +} +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 65d6748ff..430d4a844 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -1,17 +1,34 @@ #ifndef slic3r_GLToolbar_hpp_ #define slic3r_GLToolbar_hpp_ -#include "../../slic3r/GUI/GLTexture.hpp" -#include "../../callback.hpp" - +#include #include #include +#include "GLTexture.hpp" +#include "Event.hpp" + + +class wxEvtHandler; + namespace Slic3r { namespace GUI { class GLCanvas3D; +wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); + +wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent); +wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); + class GLToolbarItem { public: @@ -38,7 +55,7 @@ public: std::string tooltip; unsigned int sprite_id; bool is_toggable; - PerlCallback* action_callback; + wxEventType action_event; Data(); }; @@ -57,9 +74,12 @@ public: const std::string& get_name() const; const std::string& get_tooltip() const; - void do_action(); + void do_action(wxEvtHandler *target); bool is_enabled() const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool is_disabled() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool is_hovered() const; bool is_pressed() const; @@ -72,39 +92,114 @@ private: GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; }; +// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done +// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState +// from left to right +struct ItemsIconsTexture +{ +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + struct Metadata + { + // path of the file containing the icons' texture + std::string filename; + // size of the square icons, in pixels + unsigned int icon_size; + // size of the border, in pixels + unsigned int icon_border_size; + // distance between two adjacent icons (to avoid filtering artifacts), in pixels + unsigned int icon_gap_size; + + Metadata(); + }; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLTexture texture; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Metadata metadata; +#else + // size of the square icons, in pixels + unsigned int items_icon_size; + // distance from the border, in pixels + unsigned int items_icon_border_size; + // distance between two adjacent icons (to avoid filtering artifacts), in pixels + unsigned int items_icon_gap_size; + + ItemsIconsTexture(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +}; + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +struct BackgroundTexture +{ + struct Metadata + { + // path of the file containing the background texture + std::string filename; + // size of the left edge, in pixels + unsigned int left; + // size of the right edge, in pixels + unsigned int right; + // size of the top edge, in pixels + unsigned int top; + // size of the bottom edge, in pixels + unsigned int bottom; + + Metadata(); + }; + + GLTexture texture; + Metadata metadata; +}; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + class GLToolbar { public: - // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done - // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState - // from left to right - struct ItemsIconsTexture +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + enum EType : unsigned char { - GLTexture texture; - // size of the square icons, in pixels - unsigned int items_icon_size; - // distance from the border, in pixels - unsigned int items_icon_border_size; - // distance between two adjacent icons (to avoid filtering artifacts), in pixels - unsigned int items_icon_gap_size; - - ItemsIconsTexture(); + Normal, + Radio, + Num_Types }; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE struct Layout { - enum Type : unsigned char + enum EType : unsigned char { Horizontal, Vertical, Num_Types }; - Type type; + enum EOrientation : unsigned int + { + Top, + Bottom, + Left, + Right, + Center, + Num_Locations + }; + + EType type; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + EOrientation orientation; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float top; float left; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float border; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_size; float gap_size; + float icons_scale; + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float width; + float height; + bool dirty; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Layout(); }; @@ -112,24 +207,50 @@ public: private: typedef std::vector ItemsList; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + EType m_type; +#else GLCanvas3D& m_parent; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool m_enabled; ItemsIconsTexture m_icons_texture; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + BackgroundTexture m_background_texture; + mutable Layout m_layout; +#else Layout m_layout; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE ItemsList m_items; public: +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + explicit GLToolbar(EType type); +#else explicit GLToolbar(GLCanvas3D& parent); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + ~GLToolbar(); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); +#else bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size); - - Layout::Type get_layout_type() const; - void set_layout_type(Layout::Type type); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + + Layout::EType get_layout_type() const; + void set_layout_type(Layout::EType type); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Layout::EOrientation get_layout_orientation() const; + void set_layout_orientation(Layout::EOrientation orientation); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE void set_position(float top, float left); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_border(float border); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE void set_separator_size(float size); void set_gap_size(float size); + void set_icons_scale(float scale); bool is_enabled() const; void set_enabled(bool enable); @@ -142,33 +263,169 @@ public: void enable_item(const std::string& name); void disable_item(const std::string& name); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void select_item(const std::string& name); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool is_item_pressed(const std::string& name) const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool is_item_disabled(const std::string& name) const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else + std::string update_hover_state(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else void update_hover_state(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + void do_action(unsigned int item_id, GLCanvas3D& parent); +#else // returns the id of the item under the given mouse position or -1 if none int contains_mouse(const Vec2d& mouse_pos) const; void do_action(unsigned int item_id); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void render(const GLCanvas3D& parent) const; +#else void render() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE private: +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void calc_layout() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float get_width_horizontal() const; float get_width_vertical() const; float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); + std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else + std::string update_hover_state_horizontal(const Vec2d& mouse_pos); + std::string update_hover_state_vertical(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); + void update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else void update_hover_state_horizontal(const Vec2d& mouse_pos); void update_hover_state_vertical(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + void render_horizontal(const GLCanvas3D& parent) const; + void render_vertical(const GLCanvas3D& parent) const; +#else int contains_mouse_horizontal(const Vec2d& mouse_pos) const; int contains_mouse_vertical(const Vec2d& mouse_pos) const; void render_horizontal() const; void render_vertical() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE }; +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE +class GLRadioToolbarItem +{ +public: + struct Data + { + std::string name; + std::string tooltip; + unsigned int sprite_id; + wxEventType action_event; + + Data(); + }; + + enum EState : unsigned char + { + Normal, + Pressed, + Hover, + HoverPressed, + Num_States + }; + +private: + EState m_state; + Data m_data; + +public: + GLRadioToolbarItem(const Data& data); + + EState get_state() const; + void set_state(EState state); + + const std::string& get_name() const; + const std::string& get_tooltip() const; + + bool is_hovered() const; + bool is_pressed() const; + + void do_action(wxEvtHandler *target); + + void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; + +private: + GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; +}; + +class GLRadioToolbar +{ + typedef std::vector ItemsList; + + ItemsIconsTexture m_icons_texture; + + ItemsList m_items; + float m_top; + float m_left; + +public: + GLRadioToolbar(); + ~GLRadioToolbar(); + + bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size); + + bool add_item(const GLRadioToolbarItem::Data& data); + + float get_height() const; + + void set_position(float top, float left); + void set_selection(const std::string& name); + + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); + + void do_action(unsigned int item_id, GLCanvas3D& parent); + + void render(const GLCanvas3D& parent) const; +}; +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 45d71a9db..357b8bb4d 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -1,75 +1,34 @@ #include "GUI.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" #include "WipeTowerDialog.hpp" #include -#include #include #include -#include -#include #if __APPLE__ #import #elif _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include -// Undefine min/max macros incompatible with the standard library -// For example, std::numeric_limits::max() -// produces some weird errors -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif #include "boost/nowide/convert.hpp" #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include "wxExtensions.hpp" - -#include "Tab.hpp" -#include "TabIface.hpp" #include "GUI_Preview.hpp" -#include "GUI_PreviewIface.hpp" #include "AboutDialog.hpp" #include "AppConfig.hpp" -#include "ConfigSnapshotDialog.hpp" -#include "ProgressStatusBar.hpp" -#include "Utils.hpp" -#include "MsgDialog.hpp" #include "ConfigWizard.hpp" -#include "Preferences.hpp" #include "PresetBundle.hpp" #include "UpdateDialogs.hpp" -#include "FirmwareDialog.hpp" -#include "GUI_ObjectParts.hpp" -#include "../Utils/PresetUpdater.hpp" -#include "../Config/Snapshot.hpp" - -#include "3DScene.hpp" -#include "libslic3r/I18N.hpp" -#include "Model.hpp" -#include "LambdaObjectDialog.hpp" - -#include "../../libslic3r/Print.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Print.hpp" namespace Slic3r { namespace GUI { @@ -101,7 +60,7 @@ void enable_screensaver() bool debugged() { #ifdef _WIN32 - return IsDebuggerPresent(); + return IsDebuggerPresent() == TRUE; #else return false; #endif /* _WIN32 */ @@ -115,483 +74,12 @@ void break_to_debugger() #endif /* _WIN32 */ } -// Passing the wxWidgets GUI classes instantiated by the Perl part to C++. -wxApp *g_wxApp = nullptr; -wxFrame *g_wxMainFrame = nullptr; -ProgressStatusBar *g_progress_status_bar = nullptr; -wxNotebook *g_wxTabPanel = nullptr; -wxPanel *g_wxPlater = nullptr; -AppConfig *g_AppConfig = nullptr; -PresetBundle *g_PresetBundle= nullptr; -PresetUpdater *g_PresetUpdater = nullptr; -wxColour g_color_label_modified; -wxColour g_color_label_sys; -wxColour g_color_label_default; - -std::vector g_tabs_list; - -wxLocale* g_wxLocale; - -wxFont g_small_font; -wxFont g_bold_font; - -std::vector > m_optgroups; -double m_brim_width = 0.0; -size_t m_label_width = 100; -wxButton* g_wiping_dialog_button = nullptr; - -//showed/hided controls according to the view mode -wxWindow *g_right_panel = nullptr; -wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; -wxBoxSizer *g_info_sizer = nullptr; -wxBoxSizer *g_object_list_sizer = nullptr; -std::vector g_buttons; -wxStaticBitmap *g_manifold_warning_icon = nullptr; -bool g_show_print_info = false; -bool g_show_manifold_warning_icon = false; - -PreviewIface* g_preview = nullptr; - -static void init_label_colours() -{ - auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - if (luma >= 128) { - g_color_label_modified = wxColour(252, 77, 1); - g_color_label_sys = wxColour(26, 132, 57); - } else { - g_color_label_modified = wxColour(253, 111, 40); - g_color_label_sys = wxColour(115, 220, 103); - } - g_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -} - -void update_label_colours_from_appconfig() -{ - if (g_AppConfig->has("label_clr_sys")){ - auto str = g_AppConfig->get("label_clr_sys"); - if (str != "") - g_color_label_sys = wxColour(str); - } - - if (g_AppConfig->has("label_clr_modified")){ - auto str = g_AppConfig->get("label_clr_modified"); - if (str != "") - g_color_label_modified = wxColour(str); - } -} - -static void init_fonts() -{ - g_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - g_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); -#ifdef __WXMAC__ - g_small_font.SetPointSize(11); - g_bold_font.SetPointSize(13); -#endif /*__WXMAC__*/ -} - -static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } - -void set_wxapp(wxApp *app) -{ - g_wxApp = app; - // Let the libslic3r know the callback, which will translate messages on demand. - Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - init_label_colours(); - init_fonts(); -} - -void set_main_frame(wxFrame *main_frame) -{ - g_wxMainFrame = main_frame; -} - -wxFrame* get_main_frame() { return g_wxMainFrame; } - -void set_progress_status_bar(ProgressStatusBar *prsb) -{ - g_progress_status_bar = prsb; -} - -ProgressStatusBar* get_progress_status_bar() { return g_progress_status_bar; } - -void set_tab_panel(wxNotebook *tab_panel) -{ - g_wxTabPanel = tab_panel; -} - -void set_plater(wxPanel *plater) -{ - g_wxPlater = plater; -} - -void set_app_config(AppConfig *app_config) -{ - g_AppConfig = app_config; -} - -void set_preset_bundle(PresetBundle *preset_bundle) -{ - g_PresetBundle = preset_bundle; -} - -void set_preset_updater(PresetUpdater *updater) -{ - g_PresetUpdater = updater; -} - -enum ActionButtons -{ - abExportGCode, - abReslice, - abPrint, - abSendGCode, -}; - -void set_objects_from_perl( wxWindow* parent, - wxBoxSizer *frequently_changed_parameters_sizer, - wxBoxSizer *info_sizer, - wxButton *btn_export_gcode, - wxButton *btn_reslice, - wxButton *btn_print, - wxButton *btn_send_gcode, - wxStaticBitmap *manifold_warning_icon) -{ - g_right_panel = parent->GetParent(); - g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; - g_info_sizer = info_sizer; - - g_buttons.push_back(btn_export_gcode); - g_buttons.push_back(btn_reslice); - g_buttons.push_back(btn_print); - g_buttons.push_back(btn_send_gcode); - - // Update font style for buttons - for (auto btn : g_buttons) - btn->SetFont(bold_font()); - - g_manifold_warning_icon = manifold_warning_icon; -} - -void set_show_print_info(bool show) -{ - g_show_print_info = show; -} - -void set_show_manifold_warning_icon(bool show) -{ - g_show_manifold_warning_icon = show; - if (!g_manifold_warning_icon) - return; - - // update manifold_warning_icon showing - if (show && !g_info_sizer->IsShown(static_cast(0))) - g_show_manifold_warning_icon = false; - - g_manifold_warning_icon->Show(g_show_manifold_warning_icon); - g_manifold_warning_icon->GetParent()->Layout(); -} - -void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){ - g_object_list_sizer = objects_list_sizer; -} - -std::vector& get_tabs_list() -{ - return g_tabs_list; -} - -bool checked_tab(Tab* tab) -{ - bool ret = true; - if (find(g_tabs_list.begin(), g_tabs_list.end(), tab) == g_tabs_list.end()) - ret = false; - return ret; -} - -void delete_tab_from_list(Tab* tab) -{ - std::vector::iterator itr = find(g_tabs_list.begin(), g_tabs_list.end(), tab); - if (itr != g_tabs_list.end()) - g_tabs_list.erase(itr); -} - -bool select_language(wxArrayString & names, - wxArrayLong & identifiers) -{ - wxCHECK_MSG(names.Count() == identifiers.Count(), false, - _(L("Array of language names and identifiers should have the same size."))); - int init_selection = 0; - long current_language = g_wxLocale ? g_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN; - for (auto lang : identifiers){ - if (lang == current_language) - break; - else - ++init_selection; - } - if (init_selection == identifiers.size()) - init_selection = 0; - long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")), - names, init_selection); - if (index != -1) - { - g_wxLocale = new wxLocale; - g_wxLocale->Init(identifiers[index]); - g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir())); - g_wxLocale->AddCatalog(g_wxApp->GetAppName()); - wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified(); - return true; - } - return false; -} - -bool load_language() -{ - wxString language = wxEmptyString; - if (g_AppConfig->has("translation_language")) - language = g_AppConfig->get("translation_language"); - - if (language.IsEmpty()) - return false; - wxArrayString names; - wxArrayLong identifiers; - get_installed_languages(names, identifiers); - for (size_t i = 0; i < identifiers.Count(); i++) - { - if (wxLocale::GetLanguageCanonicalName(identifiers[i]) == language) - { - g_wxLocale = new wxLocale; - g_wxLocale->Init(identifiers[i]); - g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir())); - g_wxLocale->AddCatalog(g_wxApp->GetAppName()); - wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified(); - return true; - } - } - return false; -} - -void save_language() -{ - wxString language = wxEmptyString; - if (g_wxLocale) - language = g_wxLocale->GetCanonicalName(); - - g_AppConfig->set("translation_language", language.ToStdString()); - g_AppConfig->save(); -} - -void get_installed_languages(wxArrayString & names, - wxArrayLong & identifiers) -{ - names.Clear(); - identifiers.Clear(); - - wxDir dir(wxPathOnly(localization_dir())); - wxString filename; - const wxLanguageInfo * langinfo; - wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT); - if (!name.IsEmpty()) - { - names.Add(_(L("Default"))); - identifiers.Add(wxLANGUAGE_DEFAULT); - } - for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS); - cont; cont = dir.GetNext(&filename)) - { - langinfo = wxLocale::FindLanguageInfo(filename); - if (langinfo != NULL) - { - auto full_file_name = dir.GetName() + wxFileName::GetPathSeparator() + - filename + wxFileName::GetPathSeparator() + - g_wxApp->GetAppName() + wxT(".mo"); - if (wxFileExists(full_file_name)) - { - names.Add(langinfo->Description); - identifiers.Add(langinfo->Language); - } - } - } -} - -enum ConfigMenuIDs { - ConfigMenuWizard, - ConfigMenuSnapshots, - ConfigMenuTakeSnapshot, - ConfigMenuUpdate, - ConfigMenuPreferences, - ConfigMenuModeSimple, - ConfigMenuModeExpert, - ConfigMenuLanguage, - ConfigMenuFlashFirmware, - ConfigMenuCnt, -}; - -ConfigMenuIDs get_view_mode() -{ - if (!g_AppConfig->has("view_mode")) - return ConfigMenuModeSimple; - - const auto mode = g_AppConfig->get("view_mode"); - return mode == "expert" ? ConfigMenuModeExpert : ConfigMenuModeSimple; -} - -static wxString dots("…", wxConvUTF8); - -void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change) -{ - auto local_menu = new wxMenu(); - wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); - - const auto config_wizard_name = _(ConfigWizard::name().wx_str()); - const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name); - // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+dots, _(L("Inspect / activate configuration snapshots"))); - local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot"))); -// local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+dots+"\tCtrl+,", _(L("Application preferences"))); - local_menu->AppendSeparator(); - auto mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode"))); - mode_menu->Check(config_id_base + get_view_mode(), true); - local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer"))); - // TODO: for when we're able to flash dictionaries - // local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer"))); - - local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ - switch (event.GetId() - config_id_base) { - case ConfigMenuWizard: - config_wizard(ConfigWizard::RR_USER); - break; - case ConfigMenuTakeSnapshot: - // Take a configuration snapshot. - if (check_unsaved_changes()) { - wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name"))); - if (dlg.ShowModal() == wxID_OK) - g_AppConfig->set("on_snapshot", - Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( - *g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id); - } - break; - case ConfigMenuSnapshots: - if (check_unsaved_changes()) { - std::string on_snapshot; - if (Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) - on_snapshot = g_AppConfig->get("on_snapshot"); - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); - dlg.ShowModal(); - if (! dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig)) - Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); - g_AppConfig->set("on_snapshot", - Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig).id); - g_PresetBundle->load_presets(*g_AppConfig); - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); - } - } - break; - case ConfigMenuPreferences: - { - PreferencesDialog dlg(g_wxMainFrame, event_preferences_changed); - dlg.ShowModal(); - break; - } - case ConfigMenuLanguage: - { - wxArrayString names; - wxArrayLong identifiers; - get_installed_languages(names, identifiers); - if (select_language(names, identifiers)) { - save_language(); - show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); - if (event_language_change > 0) { - _3DScene::remove_all_canvases();// remove all canvas before recreate GUI - wxCommandEvent event(event_language_change); - g_wxApp->ProcessEvent(event); - } - } - break; - } - case ConfigMenuFlashFirmware: - FirmwareDialog::run(g_wxMainFrame); - break; - default: - break; - } - }); - mode_menu->Bind(wxEVT_MENU, [config_id_base](wxEvent& event) { - std::string mode = event.GetId() - config_id_base == ConfigMenuModeExpert ? - "expert" : "simple"; - g_AppConfig->set("view_mode", mode); - g_AppConfig->save(); - update_mode(); - }); - menu->Append(local_menu, _(L("&Configuration"))); -} - -void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change) -{ - add_config_menu(menu, event_preferences_changed, event_language_change); -} - -void open_model(wxWindow *parent, wxArrayString& input_files){ - t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); - std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" }; - wxString MODEL_WILDCARD; - for (auto file_type : file_types) - MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|"; - - auto dlg_title = _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")); - auto dialog = new wxFileDialog(parent /*? parent : GetTopWindow(g_wxMainFrame)*/, dlg_title, - g_AppConfig->get_last_dir(), "", - MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - if (dialog->ShowModal() != wxID_OK) { - dialog->Destroy(); - return ; - } - - dialog->GetPaths(input_files); - dialog->Destroy(); -} - -// This is called when closing the application, when loading a config file or when starting the config wizard -// to notify the user whether he is aware that some preset changes will be lost. -bool check_unsaved_changes() -{ - std::string dirty; - for (Tab *tab : g_tabs_list) - if (tab->current_preset_is_dirty()) - if (dirty.empty()) - dirty = tab->name(); - else - dirty += std::string(", ") + tab->name(); - if (dirty.empty()) - // No changes, the application may close or reload presets. - return true; - // Ask the user. - auto dialog = new wxMessageDialog(g_wxMainFrame, - _(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")), - _(L("Unsaved Presets")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return dialog->ShowModal() == wxID_YES; -} - bool config_wizard_startup(bool app_config_exists) { - if (! app_config_exists || g_PresetBundle->printers.size() <= 1) { + if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) { config_wizard(ConfigWizard::RR_DATA_EMPTY); return true; - } else if (g_AppConfig->legacy_datadir()) { + } else if (get_app_config()->legacy_datadir()) { // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard @@ -607,88 +95,30 @@ bool config_wizard_startup(bool app_config_exists) void config_wizard(int reason) { // Exit wizard if there are unsaved changes and the user cancels the action. - if (! check_unsaved_changes()) + if (! wxGetApp().check_unsaved_changes()) return; try { ConfigWizard wizard(nullptr, static_cast(reason)); - wizard.run(g_PresetBundle, g_PresetUpdater); + wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater); } catch (const std::exception &e) { show_error(nullptr, e.what()); } // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); -} - -void open_preferences_dialog(int event_preferences) -{ - auto dlg = new PreferencesDialog(g_wxMainFrame, event_preferences); - dlg->ShowModal(); -} - -void create_preset_tabs(int event_value_change, int event_presets_changed) -{ - update_label_colours_from_appconfig(); - add_created_tab(new TabPrint (g_wxTabPanel), event_value_change, event_presets_changed); - add_created_tab(new TabFilament (g_wxTabPanel), event_value_change, event_presets_changed); - add_created_tab(new TabSLAMaterial (g_wxTabPanel), event_value_change, event_presets_changed); - add_created_tab(new TabPrinter (g_wxTabPanel), event_value_change, event_presets_changed); -} - -std::vector preset_tabs = { - { "print", nullptr, ptFFF }, - { "filament", nullptr, ptFFF }, - { "sla_material", nullptr, ptSLA } -}; -const std::vector& get_preset_tabs() { - return preset_tabs; -} - -Tab* get_tab(const std::string& name) -{ - std::vector::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(), - [name](PresetTab& tab){ return name == tab.name; }); - return it != preset_tabs.end() ? it->panel : nullptr; -} - -TabIface* get_preset_tab_iface(char *name) -{ - Tab* tab = get_tab(name); - if (tab) return new TabIface(tab); - - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab) - continue; - if (tab->name() == name) { - return new TabIface(tab); - } - } - return new TabIface(nullptr); -} - -PreviewIface* create_preview_iface(wxNotebook* parent, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data) -{ - if (g_preview == nullptr) - { - Preview* panel = new Preview(parent, config, print, gcode_preview_data); - g_preview = new PreviewIface(panel); - } - - return g_preview; + wxGetApp().load_current_presets(); } // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ - switch (config.def()->get(opt_key)->type){ + switch (config.def()->get(opt_key)->type) { case coFloatOrPercent:{ std::string str = boost::any_cast(value); bool percent = false; - if (str.back() == '%'){ + if (str.back() == '%') { str.pop_back(); percent = true; } @@ -717,11 +147,11 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast(value))); break; case coStrings:{ - if (opt_key.compare("compatible_printers") == 0) { + if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "post_process") { config.option(opt_key)->values = boost::any_cast>(value); } - else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0){ + else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) { std::string str = boost::any_cast(value); if (str.back() == ';') str.pop_back(); // Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values. @@ -729,7 +159,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt std::vector values; boost::split(values, str, boost::is_any_of(";")); if (values.size() == 1 && values[0] == "") - break; + values.resize(0);//break; config.option(opt_key)->values = values; } else{ @@ -742,7 +172,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast(value))); break; case coBools:{ - ConfigOptionBools* vec_new = new ConfigOptionBools{ (bool)boost::any_cast(value) }; + ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast(value) != 0 }; config.option(opt_key)->set_at(vec_new, opt_index, 0); break;} case coInt: @@ -767,10 +197,12 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("infill_dense_algo") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); + else if (opt_key.compare("display_orientation") == 0) + config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; case coPoints:{ - if (opt_key.compare("bed_shape") == 0){ + if (opt_key.compare("bed_shape") == 0) { config.option(opt_key)->values = boost::any_cast>(value); break; } @@ -784,147 +216,38 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt break; } } - catch (const std::exception &e) + catch (const std::exception & /* e */) { int i = 0;//no reason, just experiment } } -void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed) +void show_error(wxWindow* parent, const wxString& message) { - panel->create_preset_tab(g_PresetBundle); - - // Load the currently selected preset into the GUI, update the preset selection box. - panel->load_current_preset(); - - panel->set_event_value_change(wxEventType(event_value_change)); - panel->set_event_presets_changed(wxEventType(event_presets_changed)); - - const wxString& tab_name = panel->GetName(); - bool add_panel = true; - - auto it = std::find_if( preset_tabs.begin(), preset_tabs.end(), - [tab_name](PresetTab& tab){return tab.name == tab_name; }); - if (it != preset_tabs.end()) { - it->panel = panel; - add_panel = it->technology == g_PresetBundle->printers.get_edited_preset().printer_technology(); - } - - if (add_panel) - g_wxTabPanel->AddPage(panel, panel->title()); -} - -void load_current_presets() -{ - for (Tab *tab : g_tabs_list) { - tab->load_current_preset(); - } -} - -void show_error(wxWindow* parent, const wxString& message) { ErrorDialog msg(parent, message); msg.ShowModal(); } -void show_error_id(int id, const std::string& message) { +void show_error_id(int id, const std::string& message) +{ auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr; show_error(parent, wxString::FromUTF8(message.data())); } -void show_info(wxWindow* parent, const wxString& message, const wxString& title){ +void show_info(wxWindow* parent, const wxString& message, const wxString& title) +{ wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION); msg_wingow.ShowModal(); } -void warning_catcher(wxWindow* parent, const wxString& message){ +void warning_catcher(wxWindow* parent, const wxString& message) +{ if (message == "GLUquadricObjPtr | " + _(L("Attempt to free unreferenced scalar")) ) return; wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); msg.ShowModal(); } -// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID -// to deliver a progress status message. -void set_print_callback_event(Print *print, int id) -{ - print->set_status_callback([id](int percent, const std::string &message){ - wxCommandEvent event(id); - event.SetInt(percent); - event.SetString(message); - wxQueueEvent(g_wxMainFrame, event.Clone()); - }); -} - -wxApp* get_app(){ - return g_wxApp; -} - -PresetBundle* get_preset_bundle() -{ - return g_PresetBundle; -} - -const wxColour& get_label_clr_modified() { - return g_color_label_modified; -} - -const wxColour& get_label_clr_sys() { - return g_color_label_sys; -} - -void set_label_clr_modified(const wxColour& clr) { - g_color_label_modified = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); - g_AppConfig->set("label_clr_modified", str); - g_AppConfig->save(); -} - -void set_label_clr_sys(const wxColour& clr) { - g_color_label_sys = clr; - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); - std::string str = clr_str.ToStdString(); - g_AppConfig->set("label_clr_sys", str); - g_AppConfig->save(); -} - -const wxFont& small_font(){ - return g_small_font; -} - -const wxFont& bold_font(){ - return g_bold_font; -} - -const wxColour& get_label_clr_default() { - return g_color_label_default; -} - -unsigned get_colour_approx_luma(const wxColour &colour) -{ - double r = colour.Red(); - double g = colour.Green(); - double b = colour.Blue(); - - return std::round(std::sqrt( - r * r * .241 + - g * g * .691 + - b * b * .068 - )); -} - -wxWindow* get_right_panel(){ - return g_right_panel; -} - -wxNotebook * get_tab_panel() { - return g_wxTabPanel; -} - -const size_t& label_width(){ - return m_label_width; -} - void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { if (comboCtrl == nullptr) @@ -979,13 +302,7 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl) AppConfig* get_app_config() { - return g_AppConfig; -} - -wxString L_str(const std::string &str) -{ - //! Explicitly specify that the source string is already in UTF-8 encoding - return wxGetTranslation(wxString(str.c_str(), wxConvUTF8)); + return wxGetApp().app_config; } wxString from_u8(const std::string &str) @@ -993,284 +310,10 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } -void set_model_events_from_perl(Model &model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene) +std::string into_u8(const wxString &str) { - set_event_object_selection_changed(event_object_selection_changed); - set_event_object_settings_changed(event_object_settings_changed); - set_event_remove_object(event_remove_object); - set_event_update_scene(event_update_scene); - set_objects_from_model(model); - init_mesh_icons(); - -// wxWindowUpdateLocker noUpdates(parent); - -// add_objects_list(parent, sizer); - -// add_collapsible_panes(parent, sizer); -} - -void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) -{ - DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - std::shared_ptr optgroup = std::make_shared(parent, "", config); - const wxArrayInt& ar = preset_sizer->GetColWidths(); - m_label_width = ar.IsEmpty() ? 100 : ar.front()-4; - optgroup->label_width = m_label_width; - - //Frequently changed parameters - optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ - TabPrint* tab_print = nullptr; - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (!tab) - continue; - if (tab->name() == "print"){ - tab_print = static_cast(tab); - break; - } - } - if (tab_print == nullptr) - return; - - if (opt_key == "fill_density"){ - value = m_optgroups[ogFrequentlyChangingParameters]->get_config_value(*config, opt_key); - tab_print->set_value(opt_key, value); - tab_print->update(); - } - else{ - DynamicPrintConfig new_conf = *config; - if (opt_key == "brim"){ - double new_val; - double brim_width = config->opt_float("brim_width"); - if (boost::any_cast(value) == true) - { - new_val = m_brim_width == 0.0 ? 5 : - m_brim_width < 0.0 ? m_brim_width * (-1) : - m_brim_width; - } - else{ - m_brim_width = brim_width * (-1); - new_val = 0; - } - new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); - } - else{ //(opt_key == "support") - const wxString& selection = boost::any_cast(value); - - auto support_material = selection == _("None") ? false : true; - new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); - - if (selection == _("Everywhere")) - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); - else if (selection == _("Support on build plate only")) - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); - } - tab_print->load_config(new_conf); - } - - tab_print->update_dirty(); - }; - - Option option = optgroup->get_option("fill_density"); - option.opt.sidetext = ""; - option.opt.full_width = true; - optgroup->append_single_option_line(option); - - ConfigOptionDef def; - - def.label = L("Support"); - def.type = coStrings; - def.gui_type = "select_open"; - def.tooltip = L("Select what kind of support do you need"); - def.enum_labels.push_back(L("None")); - def.enum_labels.push_back(L("Support on build plate only")); - def.enum_labels.push_back(L("Everywhere")); - std::string selection = !config->opt_bool("support_material") ? - "None" : - config->opt_bool("support_material_buildplate_only") ? - "Support on build plate only" : - "Everywhere"; - def.default_value = new ConfigOptionStrings { selection }; - option = Option(def, "support"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); - - m_brim_width = config->opt_float("brim_width"); - def.label = L("Brim"); - def.type = coBool; - def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); - def.gui_type = ""; - def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; - option = Option(def, "brim"); - optgroup->append_single_option_line(option); - - - Line line = { "", "" }; - line.widget = [config](wxWindow* parent){ - g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(g_wiping_dialog_button); - g_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) - { - auto &config = g_PresetBundle->project_config; - const std::vector &init_matrix = (config.option("wiping_volumes_matrix"))->values; - const std::vector &init_extruders = (config.option("wiping_volumes_extruders"))->values; - - WipingDialog dlg(parent,cast(init_matrix),cast(init_extruders)); - - if (dlg.ShowModal() == wxID_OK) { - std::vector matrix = dlg.get_matrix(); - std::vector extruders = dlg.get_extruders(); - (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); - (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); - g_on_request_update_callback.call(); - } - })); - return sizer; - }; - optgroup->append_line(line); - - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2); - - m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters - - // Object List - add_objects_list(parent, sizer); - - // Frequently Object Settings - add_object_settings(parent, sizer); -} - -void show_frequently_changed_parameters(bool show) -{ - g_frequently_changed_parameters_sizer->Show(show); - if (!show) return; - - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (!tab) - continue; - tab->update_wiping_button_visibility(); - break; - } -} - -void show_buttons(bool show) -{ - g_buttons[abReslice]->Show(show); - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { - TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (!tab) - continue; - if (g_PresetBundle->printers.get_selected_preset().printer_technology() == ptFFF) { - g_buttons[abPrint]->Show(show && !tab->m_config->opt_string("serial_port").empty()); - g_buttons[abSendGCode]->Show(show && !tab->m_config->opt_string("print_host").empty()); - } - break; - } -} - -void show_info_sizer(const bool show) -{ - g_info_sizer->Show(static_cast(0), show); - g_info_sizer->Show(1, show && g_show_print_info); - g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); -} - -void show_object_name(bool show) -{ - wxGridSizer* grid_sizer = get_optgroup(ogFrequentlyObjectSettings)->get_grid_sizer(); - grid_sizer->Show(static_cast(0), show); - grid_sizer->Show(static_cast(1), show); -} - -void update_mode() -{ - wxWindowUpdateLocker noUpdates(g_right_panel->GetParent()); - - ConfigMenuIDs mode = get_view_mode(); - - g_object_list_sizer->Show(mode == ConfigMenuModeExpert); - show_info_sizer(mode == ConfigMenuModeExpert); - show_buttons(mode == ConfigMenuModeExpert); - show_object_name(mode == ConfigMenuModeSimple); - show_manipulation_sizer(mode == ConfigMenuModeSimple); - - // TODO There is a not the best place of it! - // *** Update showing of the collpane_settings -// show_collpane_settings(mode == ConfigMenuModeExpert); - // ************************* - g_right_panel->Layout(); - g_right_panel->GetParent()->Layout(); -} - -bool is_expert_mode(){ - return get_view_mode() == ConfigMenuModeExpert; -} - -ConfigOptionsGroup* get_optgroup(size_t i) -{ - return m_optgroups[i].get(); -} - -std::vector >& get_optgroups() { - return m_optgroups; -} - -wxButton* get_wiping_dialog_button() -{ - return g_wiping_dialog_button; -} - -wxWindow* export_option_creator(wxWindow* parent) -{ - wxPanel* panel = new wxPanel(parent, -1); - wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config")); - cbox->SetValue(true); - sizer->AddSpacer(5); - sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); - panel->SetSizer(sizer); - sizer->SetSizeHints(panel); - return panel; -} - -void add_export_option(wxFileDialog* dlg, const std::string& format) -{ - if ((dlg != nullptr) && (format == "AMF") || (format == "3MF")) - { - if (dlg->SupportsExtraControl()) - dlg->SetExtraControlCreator(export_option_creator); - } -} - -int get_export_option(wxFileDialog* dlg) -{ - if (dlg != nullptr) - { - wxWindow* wnd = dlg->GetExtraControl(); - if (wnd != nullptr) - { - wxPanel* panel = dynamic_cast(wnd); - if (panel != nullptr) - { - wxWindow* child = panel->FindWindow(wxID_HIGHEST + 1); - if (child != nullptr) - { - wxCheckBox* cbox = dynamic_cast(child); - if (cbox != nullptr) - return cbox->IsChecked() ? 1 : 0; - } - } - } - } - - return 0; - + auto buffer_utf8 = str.utf8_str(); + return std::string(buffer_utf8.data()); } bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height) @@ -1294,8 +337,8 @@ void save_window_size(wxTopLevelWindow *window, const std::string &name) const wxPoint pos = window->GetPosition(); const auto maximized = window->IsMaximized() ? "1" : "0"; - g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str()); - g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized); + get_app_config()->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str()); + get_app_config()->set((boost::format("window_%1%_maximized") % name).str(), maximized); } void restore_window_size(wxTopLevelWindow *window, const std::string &name) @@ -1314,8 +357,8 @@ void restore_window_size(wxTopLevelWindow *window, const std::string &name) try { const auto key_size = (boost::format("window_%1%_size") % name).str(); - if (g_AppConfig->has(key_size)) { - if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) { + if (get_app_config()->has(key_size)) { + if (unescape_strings_cstyle(get_app_config()->get(key_size), pair) && pair.size() == 2) { auto width = boost::lexical_cast(pair[0]); auto height = boost::lexical_cast(pair[1]); @@ -1327,25 +370,11 @@ void restore_window_size(wxTopLevelWindow *window, const std::string &name) // Maximizing should be the last thing to do. // This ensure the size and position are sane when the user un-maximizes the window. const auto key_maximized = (boost::format("window_%1%_maximized") % name).str(); - if (g_AppConfig->get(key_maximized) == "1") { + if (get_app_config()->get(key_maximized) == "1") { window->Maximize(true); } } -void enable_action_buttons(bool enable) -{ - if (g_buttons.empty()) - return; - - // Update background colour for buttons - const wxColour bgrd_color = enable ? wxColour(224, 224, 224/*255, 96, 0*/) : wxColour(204, 204, 204); - - for (auto btn : g_buttons) { - btn->Enable(enable); - btn->SetBackgroundColour(bgrd_color); - } -} - void about() { AboutDialog dlg; diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 8dfaf42c6..e7ab0443d 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -1,158 +1,34 @@ #ifndef slic3r_GUI_hpp_ #define slic3r_GUI_hpp_ -#include -#include -#include "PrintConfig.hpp" -#include "../../callback.hpp" -#include "GUI_ObjectParts.hpp" +#include "libslic3r/Config.hpp" -#include -#include - -class wxApp; class wxWindow; -class wxFrame; class wxMenuBar; class wxNotebook; -class wxPanel; class wxComboCtrl; -class wxString; -class wxArrayString; -class wxArrayLong; -class wxColour; -class wxBoxSizer; -class wxFlexGridSizer; -class wxButton; class wxFileDialog; -class wxStaticBitmap; -class wxFont; +class wxString; class wxTopLevelWindow; namespace Slic3r { -class PresetBundle; -class PresetCollection; -class Print; -class ProgressStatusBar; class AppConfig; -class PresetUpdater; class DynamicPrintConfig; -class TabIface; -class PreviewIface; class Print; class GCodePreviewData; -#define _(s) Slic3r::GUI::I18N::translate((s)) - -namespace GUI { namespace I18N { - inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); } - inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); } - inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); } - inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); } -} } - -// !!! If you needed to translate some wxString, -// !!! please use _(L(string)) -// !!! _() - is a standard wxWidgets macro to translate -// !!! L() is used only for marking localizable string -// !!! It will be used in "xgettext" to create a Locating Message Catalog. -#define L(s) s - -//! macro used to localization, return wxScopedCharBuffer -//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding -#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str() - -// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L]) -#define MIN_BUF_LENGTH_FOR_L 512 - namespace GUI { -class Tab; -class ConfigOptionsGroup; -// Map from an file_type name to full file wildcard name. -typedef std::map t_file_wild_card; -inline t_file_wild_card& get_file_wild_card() { - static t_file_wild_card FILE_WILDCARDS; - if (FILE_WILDCARDS.empty()){ - FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; - FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; - FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; - FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"; - FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;"; - FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; - FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; - FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"; - FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; - } - return FILE_WILDCARDS; -} - -struct PresetTab { - std::string name; - Tab* panel; - PrinterTechnology technology; -}; - - void disable_screensaver(); void enable_screensaver(); bool debugged(); void break_to_debugger(); -// Passing the wxWidgets GUI classes instantiated by the Perl part to C++. -void set_wxapp(wxApp *app); -void set_main_frame(wxFrame *main_frame); -void set_progress_status_bar(ProgressStatusBar *prsb); -void set_tab_panel(wxNotebook *tab_panel); -void set_plater(wxPanel *plater); -void set_app_config(AppConfig *app_config); -void set_preset_bundle(PresetBundle *preset_bundle); -void set_preset_updater(PresetUpdater *updater); -void set_objects_from_perl( wxWindow* parent, - wxBoxSizer *frequently_changed_parameters_sizer, - wxBoxSizer *info_sizer, - wxButton *btn_export_gcode, - wxButton *btn_reslice, - wxButton *btn_print, - wxButton *btn_send_gcode, - wxStaticBitmap *manifold_warning_icon); -void set_show_print_info(bool show); -void set_show_manifold_warning_icon(bool show); -void set_objects_list_sizer(wxBoxSizer *objects_list_sizer); - AppConfig* get_app_config(); -wxApp* get_app(); -PresetBundle* get_preset_bundle(); -wxFrame* get_main_frame(); -ProgressStatusBar* get_progress_status_bar(); -wxNotebook * get_tab_panel(); -wxNotebook* get_tab_panel(); - -const wxColour& get_label_clr_modified(); -const wxColour& get_label_clr_sys(); -const wxColour& get_label_clr_default(); -unsigned get_colour_approx_luma(const wxColour &colour); -void set_label_clr_modified(const wxColour& clr); -void set_label_clr_sys(const wxColour& clr); - -const wxFont& small_font(); -const wxFont& bold_font(); - -void open_model(wxWindow *parent, wxArrayString& input_files); - -wxWindow* get_right_panel(); -const size_t& label_width(); - -Tab* get_tab(const std::string& name); -const std::vector& get_preset_tabs(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); -// This is called when closing the application, when loading a config file or when starting the config wizard -// to notify the user whether he is aware that some preset changes will be lost. -extern bool check_unsaved_changes(); - // Checks if configuration wizard needs to run, calls config_wizard if so. // Returns whether the Wizard ran. extern bool config_wizard_startup(bool app_config_exists); @@ -161,49 +37,14 @@ extern bool config_wizard_startup(bool app_config_exists); // The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl. extern void config_wizard(int run_reason); -// Create "Preferences" dialog after selecting menu "Preferences" in Perl part -extern void open_preferences_dialog(int event_preferences); - -// Create a new preset tab (print, filament and printer), -void create_preset_tabs(int event_value_change, int event_presets_changed); -TabIface* get_preset_tab_iface(char *name); - -PreviewIface* create_preview_iface(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data); - -// add it at the end of the tab panel. -void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed); // Change option value in config void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); -// Update UI / Tabs to reflect changes in the currently loaded presets -void load_current_presets(); - void show_error(wxWindow* parent, const wxString& message); void show_error_id(int id, const std::string& message); // For Perl void show_info(wxWindow* parent, const wxString& message, const wxString& title); void warning_catcher(wxWindow* parent, const wxString& message); -// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID -// to deliver a progress status message. -void set_print_callback_event(Print *print, int id); - -// load language saved at application config -bool load_language(); -// save language at application config -void save_language(); -// get list of installed languages -void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers); -// select language from the list of installed languages -bool select_language(wxArrayString & names, wxArrayLong & identifiers); -// update right panel of the Plater according to view mode -void update_mode(); - -void show_info_sizer(const bool show); - -std::vector& get_tabs_list(); -bool checked_tab(Tab* tab); -void delete_tab_from_list(Tab* tab); - // Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items. // Items are all initialized to the given value. // Items must be separated by '|', for example "Item1|Item2|Item3", and so on. @@ -213,30 +54,10 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string // encoded inside an int. int combochecklist_get_flags(wxComboCtrl* comboCtrl); -// Return translated std::string as a wxString -wxString L_str(const std::string &str); // Return wxString from std::string in UTF8 wxString from_u8(const std::string &str); - -void set_model_events_from_perl(Model &model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene); -void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); -// Update view mode according to selected menu -void update_mode(); -bool is_expert_mode(); - -// Callback to trigger a configuration update timer on the Plater. -static PerlCallback g_on_request_update_callback; - -ConfigOptionsGroup* get_optgroup(size_t i); -std::vector >& get_optgroups(); -wxButton* get_wiping_dialog_button(); - -void add_export_option(wxFileDialog* dlg, const std::string& format); -int get_export_option(wxFileDialog* dlg); +// Return std::string in UTF8 from wxString +std::string into_u8(const wxString &str); // Returns the dimensions of the screen on which the main frame is displayed bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height); @@ -246,9 +67,6 @@ void save_window_size(wxTopLevelWindow *window, const std::string &name); // Restore the above void restore_window_size(wxTopLevelWindow *window, const std::string &name); -// Update buttons view according to enable/disable -void enable_action_buttons(bool enable); - // Display an About dialog extern void about(); // Ask the destop to open the datadir using the default file explorer. diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp new file mode 100644 index 000000000..f4047ae3e --- /dev/null +++ b/src/slic3r/GUI/GUI_App.cpp @@ -0,0 +1,759 @@ +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "I18N.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/I18N.hpp" + +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "AppConfig.hpp" +#include "PresetBundle.hpp" +#include "3DScene.hpp" + +#include "../Utils/PresetUpdater.hpp" +#include "../Utils/PrintHost.hpp" +#include "ConfigWizard_private.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "ConfigSnapshotDialog.hpp" +#include "FirmwareDialog.hpp" +#include "Preferences.hpp" +#include "Tab.hpp" +#include "SysInfoDialog.hpp" + +namespace Slic3r { +namespace GUI { + + +wxString file_wildcards(FileType file_type, const std::string &custom_extension) +{ + static const wxString defaults[FT_SIZE] = { + /* FT_STL */ "STL files (*.stl)|*.stl;*.STL", + /* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ", + /* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML", + /* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;", + /* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA", + /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC", + /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", + + /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", + /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", + /* FT_PNGZIP */"Zipped PNG files (*.zip)|*.zip;*.ZIP", // This is lame, but that's what we use for SLA + }; + + wxString out = defaults[file_type]; + if (! custom_extension.empty()) { + // Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it. + out += ";*"; + out += from_u8(custom_extension); + } + return out; +} + +static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } + +IMPLEMENT_APP(GUI_App) + +GUI_App::GUI_App() + : wxApp() +#if ENABLE_IMGUI + , m_imgui(new ImGuiWrapper()) + , m_printhost_queue(new PrintHostJobQueue()) +#endif // ENABLE_IMGUI +{} + +bool GUI_App::OnInit() +{ +#if ENABLE_IMGUI + wxCHECK_MSG(m_imgui->init(), false, "Failed to initialize ImGui"); +#endif // ENABLE_IMGUI + + SetAppName("Slic3rPE-alpha"); + SetAppDisplayName("Slic3r Prusa Edition"); + +// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; + + // Set the Slic3r data directory at the Slic3r XS module. + // Unix: ~/ .Slic3r + // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" + // Mac : "~/Library/Application Support/Slic3r" + if (data_dir().empty()) + set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); + + app_config = new AppConfig(); + preset_bundle = new PresetBundle(); + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + try { + preset_bundle->setup_directories(); + } catch (const std::exception &ex) { + show_error(nullptr, ex.what()); + // Exit the application. + return false; + } + + app_conf_exists = app_config->exists(); + // load settings + if (app_conf_exists) + app_config->load(); + app_config->set("version", SLIC3R_VERSION); + app_config->save(); + + preset_updater = new PresetUpdater(); + + load_language(); + + // Suppress the '- default -' presets. + preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); + try { + preset_bundle->load_presets(*app_config); + } catch (const std::exception &ex) { + show_error(nullptr, ex.what()); + } + + // Let the libslic3r know the callback, which will translate messages on demand. + Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); + // initialize label colors and fonts + init_label_colours(); + init_fonts(); + + // application frame + std::cerr << "Creating main frame..." << std::endl; + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler()); + mainframe = new MainFrame(no_plater, false); + sidebar().obj_list()->init_objects(); // propagate model objects to object list + update_mode(); + SetTopWindow(mainframe); + + CallAfter([this]() { + // temporary workaround for the correct behavior of the Scrolled sidebar panel + auto& panel = sidebar(); + if (panel.obj_list()->GetMinHeight() > 200) { + wxWindowUpdateLocker noUpdates_sidebar(&panel); + panel.obj_list()->SetMinSize(wxSize(-1, 200)); + panel.Layout(); + } + }); + + // This makes CallAfter() work + Bind(wxEVT_IDLE, [this](wxIdleEvent& event) + { + std::function cur_cb{ nullptr }; + // try to get the mutex. If we can't, just skip this idle event and get the next one. + if (!callback_register.try_lock()) return; + // pop callback + if (m_cb.size() != 0) { + cur_cb = m_cb.top(); + m_cb.pop(); + } + // unlock mutex + this->callback_register.unlock(); + + try { // call the function if it's not nullptr; + if (cur_cb != nullptr) cur_cb(); + } + catch (std::exception& e) { + std::cerr << "Exception thrown: " << e.what() << std::endl; + } + + if (app_config->dirty()) + app_config->save(); + }); + + // On OS X the UI tends to freeze in weird ways if modal dialogs(config wizard, update notifications, ...) + // are shown before or in the same event callback with the main frame creation. + // Therefore we schedule them for later using CallAfter. + CallAfter([this]() { + try { + if (!preset_updater->config_update()) + mainframe->Close(); + } catch (const std::exception &ex) { + show_error(nullptr, ex.what()); + mainframe->Close(); + } + }); + + CallAfter([this]() { + if (!config_wizard_startup(app_conf_exists)) { + // Only notify if there was not wizard so as not to bother too much ... + preset_updater->slic3r_update_notify(); + } + preset_updater->sync(preset_bundle); + }); + + + mainframe->Show(true); + return true; +} + +unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) +{ + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); +} + +void GUI_App::init_label_colours() +{ + auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + if (luma >= 128) { + m_color_label_modified = wxColour(252, 77, 1); + m_color_label_sys = wxColour(26, 132, 57); + } + else { + m_color_label_modified = wxColour(253, 111, 40); + m_color_label_sys = wxColour(115, 220, 103); + } + m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +} + +void GUI_App::update_label_colours_from_appconfig() +{ + if (app_config->has("label_clr_sys")) { + auto str = app_config->get("label_clr_sys"); + if (str != "") + m_color_label_sys = wxColour(str); + } + + if (app_config->has("label_clr_modified")) { + auto str = app_config->get("label_clr_modified"); + if (str != "") + m_color_label_modified = wxColour(str); + } +} + +void GUI_App::init_fonts() +{ + m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); +#ifdef __WXMAC__ + m_small_font.SetPointSize(11); + m_bold_font.SetPointSize(13); +#endif /*__WXMAC__*/ +} + +void GUI_App::set_label_clr_modified(const wxColour& clr) { + m_color_label_modified = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + app_config->set("label_clr_modified", str); + app_config->save(); +} + +void GUI_App::set_label_clr_sys(const wxColour& clr) { + m_color_label_sys = clr; + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue()); + std::string str = clr_str.ToStdString(); + app_config->set("label_clr_sys", str); + app_config->save(); +} + +void GUI_App::recreate_GUI() +{ + std::cerr << "recreate_GUI" << std::endl; + + auto topwindow = GetTopWindow(); + mainframe = new MainFrame(no_plater,false); + sidebar().obj_list()->init_objects(); // propagate model objects to object list + update_mode(); + + if (topwindow) { + SetTopWindow(mainframe); + topwindow->Destroy(); + } + + // On OSX the UI was not initialized correctly if the wizard was called + // before the UI was up and running. + CallAfter([]() { + // Run the config wizard, don't offer the "reset user profile" checkbox. + config_wizard_startup(true); + }); +} + +void GUI_App::system_info() +{ + SysInfoDialog dlg; + dlg.ShowModal(); + dlg.Destroy(); +} + +// static method accepting a wxWindow object as first parameter +bool GUI_App::catch_error(std::function cb, + // wxMessageDialog* message_dialog, + const std::string& err /*= ""*/) +{ + if (!err.empty()) { + if (cb) + cb(); + // if (message_dialog) + // message_dialog->(err, "Error", wxOK | wxICON_ERROR); + show_error(/*this*/nullptr, err); + return true; + } + return false; +} + +// static method accepting a wxWindow object as first parameter +void fatal_error(wxWindow* parent) +{ + show_error(parent, ""); + // exit 1; // #ys_FIXME +} + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void GUI_App::update_ui_from_settings() +{ + mainframe->update_ui_from_settings(); +} + +void GUI_App::load_project(wxWindow *parent, wxString& input_file) +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _(L("Choose one file (3MF):")), + app_config->get_last_dir(), "", + file_wildcards(FT_3MF), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) +{ + input_files.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")), + app_config->get_last_dir(), "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); +} + +void GUI_App::CallAfter(std::function cb) +{ + // set mutex + callback_register.lock(); + // push function onto stack + m_cb.emplace(cb); + // unset mutex + callback_register.unlock(); +} + +void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + WindowMetrics metrics = WindowMetrics::from_window(window); + app_config->set(config_key, metrics.serialize()); + app_config->save(); +} + +void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + if (! app_config->has(config_key)) { return; } + + auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); + if (! metrics) { return; } + + window->SetSize(metrics->get_rect()); + window->Maximize(metrics->get_maximized()); +} + +void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) +{ + const auto display_idx = wxDisplay::GetFromWindow(window); + if (display_idx == wxNOT_FOUND) { return; } + + const auto display = wxDisplay(display_idx).GetClientArea(); + + auto metrics = WindowMetrics::from_window(window); + + metrics.sanitize_for_display(display); + if (window->GetScreenRect() != metrics.get_rect()) { + window->SetSize(metrics.get_rect()); + } +} + +// select language from the list of installed languages +bool GUI_App::select_language( wxArrayString & names, + wxArrayLong & identifiers) +{ + wxCHECK_MSG(names.Count() == identifiers.Count(), false, + _(L("Array of language names and identifiers should have the same size."))); + int init_selection = 0; + long current_language = m_wxLocale ? m_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN; + for (auto lang : identifiers) { + if (lang == current_language) + break; + ++init_selection; + } + if (init_selection == identifiers.size()) + init_selection = 0; + long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")), + names, init_selection); + if (index != -1) + { + m_wxLocale = new wxLocale; + m_wxLocale->Init(identifiers[index]); + m_wxLocale->AddCatalogLookupPathPrefix(localization_dir()); + m_wxLocale->AddCatalog(GetAppName()); + wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified(); + return true; + } + return false; +} + +// load language saved at application config +bool GUI_App::load_language() +{ + wxString language = wxEmptyString; + if (app_config->has("translation_language")) + language = app_config->get("translation_language"); + + if (language.IsEmpty()) + return false; + wxArrayString names; + wxArrayLong identifiers; + get_installed_languages(names, identifiers); + for (size_t i = 0; i < identifiers.Count(); i++) + { + if (wxLocale::GetLanguageCanonicalName(identifiers[i]) == language) + { + m_wxLocale = new wxLocale; + m_wxLocale->Init(identifiers[i]); + m_wxLocale->AddCatalogLookupPathPrefix(localization_dir()); + m_wxLocale->AddCatalog(GetAppName()); + wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified(); + return true; + } + } + return false; +} + +// save language at application config +void GUI_App::save_language() +{ + wxString language = wxEmptyString; + if (m_wxLocale) + language = m_wxLocale->GetCanonicalName(); + + app_config->set("translation_language", language.ToStdString()); + app_config->save(); +} + +// get list of installed languages +void GUI_App::get_installed_languages(wxArrayString & names, wxArrayLong & identifiers) +{ + names.Clear(); + identifiers.Clear(); + + wxDir dir(localization_dir()); + wxString filename; + const wxLanguageInfo * langinfo; + wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT); + if (!name.IsEmpty()) + { + names.Add(_(L("Default"))); + identifiers.Add(wxLANGUAGE_DEFAULT); + } + for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS); + cont; cont = dir.GetNext(&filename)) + { + langinfo = wxLocale::FindLanguageInfo(filename); + if (langinfo != NULL) + { + auto full_file_name = dir.GetName() + wxFileName::GetPathSeparator() + + filename + wxFileName::GetPathSeparator() + + GetAppName() + wxT(".mo"); + if (wxFileExists(full_file_name)) + { + names.Add(langinfo->Description); + identifiers.Add(langinfo->Language); + } + } + } +} + +Tab* GUI_App::get_tab(Preset::Type type) +{ + for (Tab* tab: tabs_list) + if (tab->type() == type) + return tab; + return nullptr; +} + +ConfigMenuIDs GUI_App::get_view_mode() +{ + if (!app_config->has("view_mode")) + return ConfigMenuModeSimple; + + const auto mode = app_config->get("view_mode"); + return mode == "expert" ? ConfigMenuModeExpert : + mode == "simple" ? ConfigMenuModeSimple : ConfigMenuModeAdvanced; +} + +// Update view mode according to selected menu +void GUI_App::update_mode() +{ + wxWindowUpdateLocker noUpdates(&sidebar()); + + ConfigMenuIDs mode = wxGetApp().get_view_mode(); + + obj_list()->get_sizer()->Show(mode == ConfigMenuModeExpert); + sidebar().set_mode_value(mode); +// sidebar().show_buttons(mode == ConfigMenuModeExpert); + obj_list()->update_selections(); + + sidebar().Layout(); + + ConfigOptionMode opt_mode = mode == ConfigMenuModeSimple ? comSimple : + mode == ConfigMenuModeExpert ? comExpert : comAdvanced; + for (auto tab : tabs_list) + tab->update_visibility(opt_mode); + + plater()->update_object_menu(); +} + +void GUI_App::add_config_menu(wxMenuBar *menu) +{ + auto local_menu = new wxMenu(); + wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); + + const auto config_wizard_name = _(ConfigWizard::name().wx_str()); + const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name); + // Cmd+, is standard on OS X - what about other operating systems? + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots")) + dots, _(L("Inspect / activate configuration snapshots"))); + local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot"))); + // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences")) + dots + "\tCtrl+P", _(L("Application preferences"))); + local_menu->AppendSeparator(); + auto mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode"))); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("Advanced")), _(L("Advanced View Mode"))); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("Expert")), _(L("Expert View Mode"))); + mode_menu->Check(config_id_base + get_view_mode(), true); + local_menu->AppendSubMenu(mode_menu, _(L("Mode")), _(L("Slic3r View Mode"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer"))); + // TODO: for when we're able to flash dictionaries + // local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer"))); + + local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { + switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + config_wizard(ConfigWizard::RR_USER); + break; + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + if (check_unsaved_changes()) { + wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name"))); + if (dlg.ShowModal() == wxID_OK) + app_config->set("on_snapshot", + Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( + *app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id); + } + break; + case ConfigMenuSnapshots: + if (check_unsaved_changes()) { + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + on_snapshot = app_config->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); + dlg.ShowModal(); + if (!dlg.snapshot_to_activate().empty()) { + if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); + app_config->set("on_snapshot", + Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); + preset_bundle->load_presets(*app_config); + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_presets(); + } + } + break; + case ConfigMenuPreferences: + { + PreferencesDialog dlg(mainframe); + dlg.ShowModal(); + break; + } + case ConfigMenuLanguage: + { + wxArrayString names; + wxArrayLong identifiers; + get_installed_languages(names, identifiers); + if (select_language(names, identifiers)) { + save_language(); + show_info(mainframe->m_tabpanel, _(L("Application will be restarted")), _(L("Attention!"))); + _3DScene::remove_all_canvases();// remove all canvas before recreate GUI + recreate_GUI(); + } + break; + } + case ConfigMenuFlashFirmware: + FirmwareDialog::run(mainframe); + break; + default: + break; + } + }); + mode_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) { + int id_mode = event.GetId() - config_id_base; + std::string mode = id_mode == ConfigMenuModeExpert ? "expert" : + id_mode == ConfigMenuModeSimple ? "simple" : "advanced"; + app_config->set("view_mode", mode); + app_config->save(); + update_mode(); + }); + menu->Append(local_menu, _(L("&Configuration"))); +} + +// This is called when closing the application, when loading a config file or when starting the config wizard +// to notify the user whether he is aware that some preset changes will be lost. +bool GUI_App::check_unsaved_changes() +{ + std::string dirty; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab *tab : tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + if (dirty.empty()) + dirty = tab->name(); + else + dirty += std::string(", ") + tab->name(); + if (dirty.empty()) + // No changes, the application may close or reload presets. + return true; + // Ask the user. + auto dialog = new wxMessageDialog(mainframe, + _(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")), + _(L("Unsaved Presets")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + return dialog->ShowModal() == wxID_YES; +} + +bool GUI_App::checked_tab(Tab* tab) +{ + bool ret = true; + if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) + ret = false; + return ret; +} + +void GUI_App::delete_tab_from_list(Tab* tab) +{ + std::vector::iterator itr = find(tabs_list.begin(), tabs_list.end(), tab); + if (itr != tabs_list.end()) + tabs_list.erase(itr); +} + +// Update UI / Tabs to reflect changes in the currently loaded presets +void GUI_App::load_current_presets() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + this->plater()->set_printer_technology(printer_technology); + for (Tab *tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) { + if (tab->name() == "printer") + static_cast(tab)->update_pages(); + tab->load_current_preset(); + } +} + +Sidebar& GUI_App::sidebar() +{ + return plater_->sidebar(); +} + +ObjectManipulation* GUI_App::obj_manipul() +{ + return sidebar().obj_manipul(); +} + +ObjectSettings* GUI_App::obj_settings() +{ + return sidebar().obj_settings(); +} + +ObjectList* GUI_App::obj_list() +{ + return sidebar().obj_list(); +} + +Plater* GUI_App::plater() +{ + return plater_; +} + +ModelObjectPtrs* GUI_App::model_objects() +{ + return &plater_->model().objects; +} + +wxNotebook* GUI_App::tab_panel() const +{ + return mainframe->m_tabpanel; +} + +// static method accepting a wxWindow object as first parameter +// void warning_catcher{ +// my($self, $message_dialog) = @_; +// return sub{ +// my $message = shift; +// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; +// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); +// $message_dialog +// ? $message_dialog->(@params) +// : Wx::MessageDialog->new($self, @params)->ShowModal; +// }; +// } + +// Do we need this function??? +// void GUI_App::notify(message) { +// auto frame = GetTopWindow(); +// // try harder to attract user attention on OS X +// if (!frame->IsActive()) +// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); +// +// // There used to be notifier using a Growl application for OSX, but Growl is dead. +// // The notifier also supported the Linux X D - bus notifications, but that support was broken. +// //TODO use wxNotificationMessage ? +// } + + +} // GUI +} //Slic3r diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp new file mode 100644 index 000000000..875a92456 --- /dev/null +++ b/src/slic3r/GUI/GUI_App.hpp @@ -0,0 +1,175 @@ +#ifndef slic3r_GUI_App_hpp_ +#define slic3r_GUI_App_hpp_ + +#include +#include +#include "libslic3r/PrintConfig.hpp" +#include "MainFrame.hpp" +#if ENABLE_IMGUI +#include "ImGuiWrapper.hpp" +#endif // ENABLE_IMGUI + +#include +#include +#include +#include + +#include +#include + +class wxMenuItem; +class wxMenuBar; +class wxTopLevelWindow; +class wxNotebook; + +namespace Slic3r { +class AppConfig; +class PresetBundle; +class PresetUpdater; +class ModelObject; +class PrintHostJobQueue; + +namespace GUI +{ + +enum FileType +{ + FT_STL, + FT_OBJ, + FT_AMF, + FT_3MF, + FT_PRUSA, + FT_GCODE, + FT_MODEL, + + FT_INI, + FT_SVG, + FT_PNGZIP, + + FT_SIZE, +}; + +extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string()); + +enum ConfigMenuIDs { + ConfigMenuWizard, + ConfigMenuSnapshots, + ConfigMenuTakeSnapshot, + ConfigMenuUpdate, + ConfigMenuPreferences, + ConfigMenuModeSimple, + ConfigMenuModeAdvanced, + ConfigMenuModeExpert, + ConfigMenuLanguage, + ConfigMenuFlashFirmware, + ConfigMenuCnt, +}; + +class Tab; + +static wxString dots("…", wxConvUTF8); + +class GUI_App : public wxApp +{ + bool no_plater{ false }; + bool app_conf_exists{ false }; + + // Lock to guard the callback stack + std::mutex callback_register; + // callbacks registered to run during idle event. + std::stack> m_cb{}; + + wxColour m_color_label_modified; + wxColour m_color_label_sys; + wxColour m_color_label_default; + + wxFont m_small_font; + wxFont m_bold_font; + + wxLocale* m_wxLocale{ nullptr }; + +#if ENABLE_IMGUI + std::unique_ptr m_imgui; +#endif // ENABLE_IMGUI + + std::unique_ptr m_printhost_queue; + +public: + bool OnInit() override; + + GUI_App(); + + unsigned get_colour_approx_luma(const wxColour &colour); + void init_label_colours(); + void update_label_colours_from_appconfig(); + void init_fonts(); + void set_label_clr_modified(const wxColour& clr); + void set_label_clr_sys(const wxColour& clr); + + const wxColour& get_label_clr_modified(){ return m_color_label_modified; } + const wxColour& get_label_clr_sys() { return m_color_label_sys; } + const wxColour& get_label_clr_default() { return m_color_label_default; } + + const wxFont& small_font() { return m_small_font; } + const wxFont& bold_font() { return m_bold_font; } + + void recreate_GUI(); + void system_info(); + void load_project(wxWindow *parent, wxString& input_file); + void import_model(wxWindow *parent, wxArrayString& input_files); + static bool catch_error(std::function cb, +// wxMessageDialog* message_dialog, + const std::string& err); +// void notify(/*message*/); + void update_ui_from_settings(); + void CallAfter(std::function cb); + + void window_pos_save(wxTopLevelWindow* window, const std::string &name); + void window_pos_restore(wxTopLevelWindow* window, const std::string &name); + void window_pos_sanitize(wxTopLevelWindow* window); + + bool select_language(wxArrayString & names, wxArrayLong & identifiers); + bool load_language(); + void save_language(); + void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers); + + Tab* get_tab(Preset::Type type); + ConfigMenuIDs get_view_mode(); + void update_mode(); + + void add_config_menu(wxMenuBar *menu); + bool check_unsaved_changes(); + bool checked_tab(Tab* tab); + void delete_tab_from_list(Tab* tab); + void load_current_presets(); + + Sidebar& sidebar(); + ObjectManipulation* obj_manipul(); + ObjectSettings* obj_settings(); + ObjectList* obj_list(); + Plater* plater(); + std::vector *model_objects(); + + AppConfig* app_config{ nullptr }; + PresetBundle* preset_bundle{ nullptr }; + PresetUpdater* preset_updater{ nullptr }; + MainFrame* mainframe{ nullptr }; + Plater* plater_{ nullptr }; + + wxNotebook* tab_panel() const ; + + std::vector tabs_list; + +#if ENABLE_IMGUI + ImGuiWrapper* imgui() { return m_imgui.get(); } +#endif // ENABLE_IMGUI + + PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); } + +}; +DECLARE_APP(GUI_App) + +} // GUI +} //Slic3r + +#endif // slic3r_GUI_App_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp new file mode 100644 index 000000000..cba42470a --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -0,0 +1,1716 @@ +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_App.hpp" +#include "I18N.hpp" + +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "Tab.hpp" +#include "wxExtensions.hpp" +#include "libslic3r/Model.hpp" +#include "LambdaObjectDialog.hpp" +#include "GLCanvas3D.hpp" + +#include +#include "slic3r/Utils/FixModelByWin10.hpp" + +namespace Slic3r +{ +namespace GUI +{ + +wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); + +ObjectList::ObjectList(wxWindow* parent) : + wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), + m_parent(parent) +{ + // Fill CATEGORY_ICON + { + // ptFFF + CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(var("layers.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(var("infill.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Speed")] = wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extruders")] = wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extrusion Width")] = wxBitmap(from_u8(var("funnel.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(var("box.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG); + // ptSLA + CATEGORY_ICON[L("Supports")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Pad")] = wxBitmap(from_u8(var("brick.png")), wxBITMAP_TYPE_PNG); + } + + // create control + create_objects_ctrl(); + + init_icons(); + + // create popup menus for object and part + create_object_popupmenu(&m_menu_object); + create_part_popupmenu(&m_menu_part); + create_sla_object_popupmenu(&m_menu_sla_object); + + // describe control behavior + Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { + selection_changed(); +#ifndef __WXMSW__ + set_tooltip_for_item(get_mouse_position_in_control()); +#endif //__WXMSW__ + }); + +// Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX + +#ifdef __WXMSW__ + GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { + set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); + event.Skip(); + }); +#endif //__WXMSW__ + + Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &ObjectList::OnContextMenu, this); + + Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, &ObjectList::OnBeginDrag, this); + Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); + Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); + + Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); + + Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); + + Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); + +#ifdef __WXOSX__ + Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); +#endif //__WXOSX__ +} + +ObjectList::~ObjectList() +{ + if (m_default_config) + delete m_default_config; +} + +void ObjectList::create_objects_ctrl() +{ + // temporary workaround for the correct behavior of the Scrolled sidebar panel: + // 1. set a height of the list to some big value + // 2. change it to the normal min value (200) after first whole App updating/layouting + SetMinSize(wxSize(-1, 1500)); // #ys_FIXME + + m_sizer = new wxBoxSizer(wxVERTICAL); + m_sizer->Add(this, 1, wxGROW | wxLEFT, 20); + + m_objects_model = new PrusaObjectDataViewModel; + AssociateModel(m_objects_model); + m_objects_model->SetAssociatedControl(this); +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + EnableDragSource(wxDF_UNICODETEXT); + EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0(Icon+Text) of the view control: + // And Icon can be consisting of several bitmaps + AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), + 0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + + // column 1 of the view control: + AppendColumn(create_objects_list_extruder_column(4)); + + // column 2 of the view control: + AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, 25, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); +} + +void ObjectList::set_tooltip_for_item(const wxPoint& pt) +{ + wxDataViewItem item; + wxDataViewColumn* col; + HitTest(pt, item, col); + if (!item) return; + + if (col->GetTitle() == " " && GetSelectedItemsCount()<2) + GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); + else if (col->GetTitle() == _("Name") && + m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) { + int obj_idx = m_objects_model->GetIdByItem(item); + auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); + + std::map error_msg; + error_msg[L("degenerate facets")] = stats.degenerate_facets; + error_msg[L("edges fixed")] = stats.edges_fixed; + error_msg[L("facets removed")] = stats.facets_removed; + error_msg[L("facets added")] = stats.facets_added; + error_msg[L("facets reversed")] = stats.facets_reversed; + error_msg[L("backwards edges")] = stats.backwards_edges; + + for (auto error : error_msg) + { + if (error.second > 0) + tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); + } +// OR +// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " +// "%d facets added, %d facets reversed, %d backwards edges")), +// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, +// stats.facets_added, stats.facets_reversed, stats.backwards_edges); + + if (is_windows10()) + tooltip += _(L("Right button click the icon to fix STL through Netfabb")); + + GetMainWindow()->SetToolTip(tooltip); + } + else + GetMainWindow()->SetToolTip(""); // hide tooltip +} + +wxPoint ObjectList::get_mouse_position_in_control() +{ + const wxPoint& pt = wxGetMousePosition(); +// wxWindow* win = GetMainWindow(); +// wxPoint screen_pos = win->GetScreenPosition(); + return wxPoint(pt.x - /*win->*/GetScreenPosition().x, pt.y - /*win->*/GetScreenPosition().y); +} + +int ObjectList::get_selected_obj_idx() const +{ + if (GetSelectedItemsCount() == 1) + return m_objects_model->GetIdByItem(m_objects_model->GetTopParent(GetSelection())); + + return -1; +} + +wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_count) +{ + wxArrayString choices; + choices.Add("default"); + for (int i = 1; i <= extruders_count; ++i) + choices.Add(wxString::Format("%d", i)); + wxDataViewChoiceRenderer *c = + new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); + wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, 80, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + return column; +} + +void ObjectList::update_extruder_values_for_items(const int max_extruder) +{ + for (int i = 0; i < m_objects->size(); ++i) + { + wxDataViewItem item = m_objects_model->GetItemById(i); + if (!item) continue; + + auto object = (*m_objects)[i]; + wxString extruder; + if (!object->config.has("extruder") || + object->config.option("extruder")->value > max_extruder) + extruder = "default"; + else + extruder = wxString::Format("%d", object->config.option("extruder")->value); + + m_objects_model->SetValue(extruder, item, 1); + + if (object->volumes.size() > 1) { + for (auto id = 0; id < object->volumes.size(); id++) { + item = m_objects_model->GetItemByVolumeId(i, id); + if (!item) continue; + if (!object->volumes[id]->config.has("extruder") || + object->volumes[id]->config.option("extruder")->value > max_extruder) + extruder = "default"; + else + extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); + + m_objects_model->SetValue(extruder, item, 1); + } + } + } +} + +void ObjectList::update_objects_list_extruder_column(int extruders_count) +{ + if (!this) return; // #ys_FIXME + if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + extruders_count = 1; + + wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); + if (ch_render->GetChoices().GetCount() - 1 == extruders_count) + return; + + m_prevent_update_extruder_in_config = true; + + if (m_objects && extruders_count > 1) + update_extruder_values_for_items(extruders_count); + + // delete old 2nd column + DeleteColumn(GetColumn(1)); + // insert new created 3rd column + InsertColumn(1, create_objects_list_extruder_column(extruders_count)); + // set show/hide for this column + set_extruder_column_hidden(extruders_count <= 1); + //a workaround for a wrong last column width updating under OSX + GetColumn(2)->SetWidth(25); + + m_prevent_update_extruder_in_config = false; +} + +void ObjectList::set_extruder_column_hidden(const bool hide) const +{ + GetColumn(1)->SetHidden(hide); +} + +void ObjectList::update_extruder_in_config(const wxDataViewItem& item) +{ + if (m_prevent_update_extruder_in_config) + return; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + const int obj_idx = m_objects_model->GetIdByItem(item); + m_config = &(*m_objects)[obj_idx]->config; + } + else { + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int volume_id = m_objects_model->GetVolumeIdByItem(item); + if (obj_idx < 0 || volume_id < 0) + return; + m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + + wxVariant variant; + m_objects_model->GetValue(variant, item, 1); + const wxString selection = variant.GetString(); + + if (!m_config || selection.empty()) + return; + + const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + + // update scene + wxGetApp().plater()->update(); +} + +void ObjectList::update_name_in_model(const wxDataViewItem& item) +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx < 0) return; + + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToStdString(); + return; + } + + const int volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) return; + (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToStdString(); +} + +void ObjectList::init_icons() +{ + m_bmp_modifiermesh = wxBitmap(from_u8(var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_bmp_solidmesh = wxBitmap(from_u8(var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + + m_bmp_support_enforcer = wxBitmap(from_u8(var("support_enforcer_.png")), wxBITMAP_TYPE_PNG); + m_bmp_support_blocker = wxBitmap(from_u8(var("support_blocker_.png")), wxBITMAP_TYPE_PNG); + + m_bmp_vector.reserve(4); // bitmaps for different types of parts + m_bmp_vector.push_back(&m_bmp_solidmesh); // Add part + m_bmp_vector.push_back(&m_bmp_modifiermesh); // Add modifier + m_bmp_vector.push_back(&m_bmp_support_enforcer); // Add support enforcer + m_bmp_vector.push_back(&m_bmp_support_blocker); // Add support blocker + m_objects_model->SetVolumeBitmaps(m_bmp_vector); + + // init icon for manifold warning + m_bmp_manifold_warning = wxBitmap(from_u8(var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + + // init bitmap for "Split to sub-objects" context menu + m_bmp_split = wxBitmap(from_u8(var("split.png")), wxBITMAP_TYPE_PNG); + + // init bitmap for "Add Settings" context menu + m_bmp_cog = wxBitmap(from_u8(var("cog.png")), wxBITMAP_TYPE_PNG); +} + + +void ObjectList::selection_changed() +{ + if (m_prevent_list_events) return; + + fix_multiselection_conflicts(); + + // update object selection on Plater + update_selections_on_canvas(); + + // to update the toolbar and info sizer + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { + auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT); + event.SetEventObject(this); + wxPostEvent(this, event); + } + + part_selection_changed(); +} + +void ObjectList::OnChar(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_BACK){ + remove(); + } + else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) + select_item_all_children(); + + event.Skip(); +} + +void ObjectList::OnContextMenu(wxDataViewEvent&) +{ + wxDataViewItem item; + wxDataViewColumn* col; + const wxPoint pt = get_mouse_position_in_control(); + HitTest(pt, item, col); + if (!item) +#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX + // after Yosemite OS X version, HitTest return undefined item + item = GetSelection(); + if (item) + show_context_menu(); + else + printf("undefined item\n"); + return; +#else + return; +#endif // __WXOSX__ + const wxString title = col->GetTitle(); + + if (title == " ") + show_context_menu(); + else if (title == _("Name") && pt.x >15 && + m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) + { + if (is_windows10()) { + const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + wxGetApp().plater()->fix_through_netfabb(obj_idx); + } + } +#ifndef __WXMSW__ + GetMainWindow()->SetToolTip(""); // hide tooltip +#endif //__WXMSW__ +} + +void ObjectList::show_context_menu() +{ + const auto item = GetSelection(); + if (item) + { + if (!(m_objects_model->GetItemType(item) & (itObject | itVolume))) + return; + wxMenu* menu = m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : + wxGetApp().plater()->printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; + + append_menu_item_settings(menu); + + wxGetApp().plater()->PopupMenu(menu); + + wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(is_splittable()); }, m_menu_item_split->GetId()); + + wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(is_splittable()); }, m_menu_item_split_part->GetId()); + } +} + + +void ObjectList::key_event(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_TAB) + Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else if (event.GetKeyCode() == WXK_DELETE +#ifdef __WXOSX__ + || event.GetKeyCode() == WXK_BACK +#endif //__WXOSX__ + ) { + printf("WXK_BACK\n"); + remove(); + } + else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) + select_item_all_children(); + else + event.Skip(); +} + +void ObjectList::OnBeginDrag(wxDataViewEvent &event) +{ + const wxDataViewItem item(event.GetItem()); + + // only allow drags for item, not containers + if (multiple_selection() || GetSelection()!=item || + m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume ) { + event.Veto(); + return; + } + + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item)); + + /* Under MSW or OSX, DnD moves an item to the place of another selected item + * But under GTK, DnD moves an item between another two items. + * And as a result - call EVT_CHANGE_SELECTION to unselect all items. + * To prevent such behavior use m_prevent_list_events + **/ + m_prevent_list_events = true;//it's needed for GTK + + /* Under GTK, DnD requires to the wxTextDataObject been initialized with some valid value, + * so set some nonempty string + */ + wxTextDataObject* obj = new wxTextDataObject; + obj->SetText("Some text");//it's needed for GTK + + event.SetDataObject(obj); + event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move; +} + +void ObjectList::OnDropPossible(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drags for item or background, not containers + if (!item.IsOk() || + m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume || + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) + event.Veto(); +} + +void ObjectList::OnDrop(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + if (!item.IsOk() || m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume || + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) { + event.Veto(); + m_dragged_data.clear(); + return; + } + + const int from_volume_id = m_dragged_data.vol_idx(); + int to_volume_id = m_objects_model->GetVolumeIdByItem(item); + +// It looks like a fixed in current version of the wxWidgets +// #ifdef __WXGTK__ +// /* Under GTK, DnD moves an item between another two items. +// * And event.GetItem() return item, which is under "insertion line" +// * So, if we move item down we should to decrease the to_volume_id value +// **/ +// if (to_volume_id > from_volume_id) to_volume_id--; +// #endif // __WXGTK__ + + auto& volumes = (*m_objects)[/*m_selected_object_id*/m_dragged_data.obj_idx()]->volumes; + auto delta = to_volume_id < from_volume_id ? -1 : 1; + int cnt = 0; + for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) + std::swap(volumes[id], volumes[id + delta]); + + select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, + m_objects_model->GetParent(item))); + + m_parts_changed = true; + parts_changed(/*m_selected_object_id*/m_dragged_data.obj_idx()); + + m_dragged_data.clear(); +} + + +// Context Menu + +std::vector get_options(const bool is_part, const bool is_sla) +{ + if (is_sla) { + SLAPrintObjectConfig full_sla_config; + auto options = full_sla_config.keys(); + options.erase(find(options.begin(), options.end(), "layer_height")); + return options; + } + + PrintRegionConfig reg_config; + auto options = reg_config.keys(); + if (!is_part) { + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + options.insert(options.end(), obj_options.begin(), obj_options.end()); + } + return options; +} + +std::vector get_options(const bool is_part) +{ + return get_options(is_part, wxGetApp().plater()->printer_technology() == ptSLA); +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part, const bool is_sla) +{ + auto options = get_options(is_part, is_sla); + + auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + + DynamicPrintConfig config; + for (auto& option : options) + { + auto const opt = config.def()->get(option); + auto category = opt->category; + if (category.empty() || + (category == "Extruders" && extruders_cnt == 1)) continue; + + const std::string& label = opt->label.empty() ? opt->full_label : + opt->full_label.empty() ? opt->label : + opt->full_label + " " + opt->label;; + std::pair option_label(option, label); + std::vector< std::pair > new_category; + auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); + cat_opt_label.push_back(option_label); + if (cat_opt_label.size() == 1) + settings_menu[category] = cat_opt_label; + } +} + +void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) +{ + get_options_menu(settings_menu, is_part, wxGetApp().plater()->printer_technology() == ptSLA); +} + +void ObjectList::get_settings_choice(const wxString& category_name) +{ + wxArrayString names; + wxArrayInt selections; + + settings_menu_hierarchy settings_menu; + const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); + get_options_menu(settings_menu, is_part); + std::vector< std::pair > *settings_list = nullptr; + + auto opt_keys = m_config->keys(); + + for (auto& cat : settings_menu) + { + if (_(cat.first) == category_name) { + int sel = 0; + for (auto& pair : cat.second) { + names.Add(_(pair.second)); + if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) + selections.Add(sel); + sel++; + } + settings_list = &cat.second; + break; + } + } + + if (!settings_list) + return; + + if (wxGetSelectedChoices(selections, _(L("Select showing settings")), category_name, names) == -1) + return; + + std::vector selected_options; + for (auto sel : selections) + selected_options.push_back((*settings_list)[sel].first); + + for (auto& setting : (*settings_list)) + { + auto& opt_key = setting.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) + m_config->erase(opt_key); + + if (find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) + m_config->set_key_value(opt_key, m_default_config->option(opt_key)->clone()); + } + + + // Add settings item for object + const auto item = GetSelection(); + if (item) { + const auto settings_item = m_objects_model->GetSettingsItem(item); + select_item(settings_item ? settings_item : + m_objects_model->AddSettingsChild(item)); + } + else { + auto panel = wxGetApp().sidebar().scrolled_panel(); + panel->Freeze(); + wxGetApp().obj_settings()->UpdateAndShow(true);//obj_manipul()->update_settings_list(); + panel->Thaw(); + } +} + +void ObjectList::append_menu_item_add_generic(wxMenuItem* menu, const int type) { + auto sub_menu = new wxMenu; + + append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", + [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu->GetMenu()); + sub_menu->AppendSeparator(); + + std::vector menu_items = { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }; + for (auto& item : menu_items) { + append_menu_item(sub_menu, wxID_ANY, _(item), "", + [this, type, item](wxCommandEvent&) { load_generic_subobject(_(item).ToStdString(), type); }, "", menu->GetMenu()); + } + + menu->SetSubMenu(sub_menu); +} + +void ObjectList::append_menu_items_add_volume(wxMenu* menu) +{ + // Note: id accords to type of the sub-object, so sequence of the menu items is important + std::vector menu_object_types_items = {L("Add part"), // ~ModelVolume::MODEL_PART + L("Add modifier"), // ~ModelVolume::PARAMETER_MODIFIER + L("Add support enforcer"), // ~ModelVolume::SUPPORT_ENFORCER + L("Add support blocker") }; // ~ModelVolume::SUPPORT_BLOCKER + + // Update "add" items(delete old & create new) settings popupmenu + for (auto& item : menu_object_types_items){ + const auto settings_id = menu->FindItem(_(item)); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + } + + if (wxGetApp().get_view_mode() == ConfigMenuModeSimple) + { + append_menu_item(menu, wxID_ANY, _(L("Add part")), "", + [this](wxCommandEvent&) { load_subobject(ModelVolume::MODEL_PART); }, *m_bmp_vector[ModelVolume::MODEL_PART]); + append_menu_item(menu, wxID_ANY, _(L("Add support enforcer")), "", + [this](wxCommandEvent&) { load_generic_subobject(_(L("Box")).ToStdString(), ModelVolume::SUPPORT_ENFORCER); }, + *m_bmp_vector[ModelVolume::SUPPORT_ENFORCER]); + append_menu_item(menu, wxID_ANY, _(L("Add support blocker")), "", + [this](wxCommandEvent&) { load_generic_subobject(_(L("Box")).ToStdString(), ModelVolume::SUPPORT_BLOCKER); }, + *m_bmp_vector[ModelVolume::SUPPORT_BLOCKER]); + + return; + } + + for (int type = 0; type < menu_object_types_items.size(); type++) + { + auto& item = menu_object_types_items[type]; + + auto menu_item = new wxMenuItem(menu, wxID_ANY, _(item)); + menu_item->SetBitmap(*m_bmp_vector[type]); + append_menu_item_add_generic(menu_item, type); + + menu->Append(menu_item); + } +} + +wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Split to parts")), "", + [this](wxCommandEvent&) { split(); }, m_bmp_split, menu); +} + +wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu) +{ + // Update (delete old & create new) settings popupmenu + const auto settings_id = menu->FindItem(_("Add settings")); + if (settings_id != wxNOT_FOUND) + menu->Destroy(settings_id); + + if (wxGetApp().get_view_mode() == ConfigMenuModeSimple) + return nullptr; + + auto menu_item = new wxMenuItem(menu, wxID_ANY, _(L("Add settings"))); + menu_item->SetBitmap(m_bmp_cog); + + const auto sel_vol = get_selected_model_volume(); + if (sel_vol && sel_vol->type() >= ModelVolume::SUPPORT_ENFORCER) + menu_item->Enable(false); + else + menu_item->SetSubMenu(create_settings_popupmenu(menu)); + + return menu->Append(menu_item); +} + +wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Change type")), "", + [this](wxCommandEvent&) { change_part_type(); }, "", menu); + +} + +void ObjectList::create_object_popupmenu(wxMenu *menu) +{ + append_menu_items_add_volume(menu); + + // Split object to parts + menu->AppendSeparator(); + m_menu_item_split = append_menu_item_split(menu); + + // Settings + menu->AppendSeparator(); +} + +void ObjectList::create_sla_object_popupmenu(wxMenu *menu) +{ +} + +void ObjectList::create_part_popupmenu(wxMenu *menu) +{ + m_menu_item_split_part = append_menu_item_split(menu); + + // Append change part type + menu->AppendSeparator(); + append_menu_item_change_type(menu); + + // Append settings popupmenu + menu->AppendSeparator(); +} + +wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) +{ + wxMenu *menu = new wxMenu; + + settings_menu_hierarchy settings_menu; + const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); + get_options_menu(settings_menu, is_part); + + for (auto cat : settings_menu) { + append_menu_item(menu, wxID_ANY, _(cat.first), "", + [menu, this](wxCommandEvent& event) { get_settings_choice(menu->GetLabel(event.GetId())); }, + CATEGORY_ICON.find(cat.first) == CATEGORY_ICON.end() ? wxNullBitmap : CATEGORY_ICON.at(cat.first), parent_menu); + } + + return menu; +} + +void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) +{ + auto full_current_opts = get_options(false); + for (int i = opt_keys.size()-1; i >= 0; --i) + if (find(full_current_opts.begin(), full_current_opts.end(), opt_keys[i]) == full_current_opts.end()) + opt_keys.erase(opt_keys.begin() + i); +} + +void ObjectList::load_subobject(int type) +{ + auto item = GetSelection(); + if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + return; + int obj_idx = m_objects_model->GetIdByItem(item); + + if (obj_idx < 0) return; + wxArrayString part_names; + load_part((*m_objects)[obj_idx], part_names, type); + + parts_changed(obj_idx); + + for (int i = 0; i < part_names.size(); ++i) { + const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type); + + if (i == part_names.size() - 1) + select_item(sel_item); + } +} + +void ObjectList::load_part( ModelObject* model_object, + wxArrayString& part_names, + int type) +{ + wxWindow* parent = wxGetApp().tab_panel()->GetPage(0); + + m_parts_changed = false; + wxArrayString input_files; + wxGetApp().import_model(parent, input_files); + for (int i = 0; i < input_files.size(); ++i) { + std::string input_file = input_files.Item(i).ToStdString(); + + Model model; + try { + model = Model::read_from_file(input_file); + } + catch (std::exception &e) { + auto msg = _(L("Error! ")) + input_file + " : " + e.what() + "."; + show_error(parent, msg); + exit(1); + } + + for (auto object : model.objects) { + Vec3d delta = Vec3d::Zero(); + if (model_object->origin_translation != Vec3d::Zero()) + { + object->center_around_origin(); +#if !ENABLE_MODELVOLUME_TRANSFORM + object->ensure_on_bed(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM + delta = model_object->origin_translation - object->origin_translation; + } + for (auto volume : object->volumes) { +#if ENABLE_MODELVOLUME_TRANSFORM + volume->center_geometry(); + volume->translate(delta); +#endif // ENABLE_MODELVOLUME_TRANSFORM + auto new_volume = model_object->add_volume(*volume); + new_volume->set_type(static_cast(type)); + new_volume->name = boost::filesystem::path(input_file).filename().string(); + + part_names.Add(new_volume->name); + +#if !ENABLE_MODELVOLUME_TRANSFORM + if (delta != Vec3d::Zero()) + new_volume->translate(delta); +#endif // !ENABLE_MODELVOLUME_TRANSFORM + + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + m_parts_changed = true; + } + } + } + +} + +void ObjectList::load_generic_subobject(const std::string& type_name, const int type) +{ + const auto obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + const std::string name = "lambda-" + type_name; + TriangleMesh mesh; + + auto& bed_shape = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("bed_shape")->values; + const auto& sz = BoundingBoxf(bed_shape).size(); + const auto side = 0.1 * std::max(sz(0), sz(1)); + + if (type_name == _("Box")) { + mesh = make_cube(side, side, side); + // box sets the base coordinate at 0, 0, move to center of plate + mesh.translate(-side * 0.5, -side * 0.5, 0); + } + else if (type_name == _("Cylinder")) + mesh = make_cylinder(0.5*side, side); + else if (type_name == _("Sphere")) + mesh = make_sphere(0.5*side, PI/18); + else if (type_name == _("Slab")) { + const auto& size = (*m_objects)[obj_idx]->bounding_box().size(); + mesh = make_cube(size(0)*1.5, size(1)*1.5, size(2)*0.5); + // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z + mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, 0); + } + mesh.repair(); + + auto new_volume = (*m_objects)[obj_idx]->add_volume(mesh); + new_volume->set_type(static_cast(type)); + +#if ENABLE_MODELVOLUME_TRANSFORM + new_volume->set_offset(Vec3d(0.0, 0.0, (*m_objects)[obj_idx]->origin_translation(2) - mesh.stl.stats.min(2))); + new_volume->center_geometry(); +#endif // ENABLE_MODELVOLUME_TRANSFORM + + new_volume->name = name; + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + m_parts_changed = true; + parts_changed(obj_idx); + + select_item(m_objects_model->AddVolumeChild(GetSelection(), name, type)); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + +void ObjectList::del_object(const int obj_idx) +{ + wxGetApp().plater()->delete_object_from_model(obj_idx); +} + +// Delete subobject +void ObjectList::del_subobject_item(wxDataViewItem& item) +{ + if (!item) return; + + int obj_idx, idx; + ItemType type; + + m_objects_model->GetItemInfo(item, type, obj_idx, idx); + if (type == itUndef) + return; + + if (type == itSettings) + del_settings_from_config(); + else if (type == itInstanceRoot && obj_idx != -1) + del_instances_from_object(obj_idx); + else if (idx == -1) + return; + else if (!del_subobject_from_object(obj_idx, idx, type)) + return; + + m_objects_model->Delete(item); +} + +void ObjectList::del_settings_from_config() +{ + auto opt_keys = m_config->keys(); + if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + return; + int extruder = -1; + if (m_config->has("extruder")) + extruder = m_config->option("extruder")->value; + + m_config->clear(); + + if (extruder >= 0) + m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); +} + +void ObjectList::del_instances_from_object(const int obj_idx) +{ + auto& instances = (*m_objects)[obj_idx]->instances; + if (instances.size() <= 1) + return; + + while ( instances.size()> 1) + instances.pop_back(); + + (*m_objects)[obj_idx]->invalidate_bounding_box(); // ? #ys_FIXME + + m_parts_changed = true; + parts_changed(obj_idx); +} + +bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) +{ + if (type == itVolume) { + const auto volume = (*m_objects)[obj_idx]->volumes[idx]; + + // if user is deleting the last solid part, throw error + int solid_cnt = 0; + for (auto vol : (*m_objects)[obj_idx]->volumes) + if (vol->is_model_part()) + ++solid_cnt; + if (volume->is_model_part() && solid_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from object."))); + return false; + } + + (*m_objects)[obj_idx]->delete_volume(idx); + } + else if (type == itInstance) { + if ((*m_objects)[obj_idx]->instances.size() == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); + return false; + } + (*m_objects)[obj_idx]->delete_instance(idx); + } + else + return false; + + m_parts_changed = true; + parts_changed(obj_idx); + + return true; +} + +void ObjectList::split() +{ + const auto item = GetSelection(); + const int obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return; + + ModelVolume* volume; + if (!get_volume_by_item(item, volume)) return; + DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); + const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast(nozzle_dmtrs_opt)->values.size(); + if (volume->split(nozzle_dmrs_cnt) == 1) { + wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); + return; + } + + auto model_object = (*m_objects)[obj_idx]; + + auto parent = m_objects_model->GetTopParent(item); + if (parent) + m_objects_model->DeleteVolumeChildren(parent); + else + parent = item; + + for (auto id = 0; id < model_object->volumes.size(); id++) { + const auto vol_item = m_objects_model->AddVolumeChild(parent, model_object->volumes[id]->name, + model_object->volumes[id]->is_modifier() ? + ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0, + false); + // add settings to the part, if it has those + auto opt_keys = model_object->volumes[id]->config.keys(); + if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { + select_item(m_objects_model->AddSettingsChild(vol_item)); + Collapse(vol_item); + } + } + + if (parent == item) + Expand(parent); + + m_parts_changed = true; + parts_changed(obj_idx); +} + +bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) +{ + auto obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return false; + const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + const bool split_part = m_objects_model->GetItemType(item) == itVolume; + + // object is selected + if (volume_id < 0) { + if ( split_part || (*m_objects)[obj_idx]->volumes.size() > 1 ) + return false; + volume = (*m_objects)[obj_idx]->volumes[0]; + } + // volume is selected + else + volume = (*m_objects)[obj_idx]->volumes[volume_id]; + + return true; +} + +bool ObjectList::is_splittable() +{ + const wxDataViewItem item = GetSelection(); + if (!item) return false; + + ModelVolume* volume; + if (!get_volume_by_item(item, volume) || !volume) + return false; + + TriangleMeshPtrs meshptrs = volume->mesh.split(); + bool splittable = meshptrs.size() > 1; + for (TriangleMesh* m : meshptrs) { delete m; } + + return splittable; +} + +void ObjectList::part_settings_changed() +{ + m_part_settings_changed = true; + wxGetApp().plater()->changed_object(get_selected_obj_idx()); + m_part_settings_changed = false; +} + +void ObjectList::parts_changed(int obj_idx) +{ + wxGetApp().plater()->changed_object(obj_idx); + m_parts_changed = false; +} + +void ObjectList::part_selection_changed() +{ + int obj_idx = -1; + m_config = nullptr; + wxString og_name = wxEmptyString; + + bool update_and_show_manipulations = false; + bool update_and_show_settings = false; + + if (multiple_selection()) { + og_name = _(L("Group manipulation")); + update_and_show_manipulations = true; + } + else + { + const auto item = GetSelection(); + if (item) + { + bool is_part = false; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + obj_idx = m_objects_model->GetIdByItem(item); + og_name = _(L("Object manipulation")); + m_config = &(*m_objects)[obj_idx]->config; + update_and_show_manipulations = true; + } + else { + auto parent = m_objects_model->GetParent(item); + // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + obj_idx = m_objects_model->GetIdByItem(parent); + if (m_objects_model->GetItemType(item) == itSettings) { + if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + og_name = _(L("Object Settings to modify")); + m_config = &(*m_objects)[obj_idx]->config; + } + else { + og_name = _(L("Part Settings to modify")); + is_part = true; + auto main_parent = m_objects_model->GetParent(parent); + obj_idx = m_objects_model->GetIdByItem(main_parent); + const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + update_and_show_settings = true; + } + else if (m_objects_model->GetItemType(item) == itVolume) { + og_name = _(L("Part manipulation")); + is_part = true; + const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + update_and_show_manipulations = true; + } + else if (m_objects_model->GetItemType(item) == itInstance) { + og_name = _(L("Instance manipulation")); + update_and_show_manipulations = true; + } + } + + if (m_default_config) delete m_default_config; + m_default_config = DynamicPrintConfig::new_from_defaults_keys(get_options(is_part)); + } + } + + m_selected_object_id = obj_idx; + + if (update_and_show_manipulations) { + wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); + wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(GetSelection())); + } + + if (update_and_show_settings) + wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); + + Sidebar& panel = wxGetApp().sidebar(); + panel.Freeze(); + + wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); + wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); + wxGetApp().sidebar().show_info_sizer(); + + panel.Layout(); + panel.Thaw(); +} + +void ObjectList::add_object_to_list(size_t obj_idx) +{ + auto model_object = (*m_objects)[obj_idx]; + wxString item_name = model_object->name; + const auto item = m_objects_model->Add(item_name, + !model_object->config.has("extruder") ? 0 : + model_object->config.option("extruder")->value); + + // Add error icon if detected auto-repaire + auto stats = model_object->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + if (errors > 0) { + wxVariant variant; + variant << PrusaDataViewBitmapText(item_name, m_bmp_manifold_warning); + m_objects_model->SetValue(variant, item, 0); + } + + // add volumes to the object + if (model_object->volumes.size() > 1) { + for (auto id = 0; id < model_object->volumes.size(); id++) { + auto vol_item = m_objects_model->AddVolumeChild(item, + model_object->volumes[id]->name, + model_object->volumes[id]->type()/*ModelVolume::MODEL_PART*/, + !model_object->volumes[id]->config.has("extruder") ? 0 : + model_object->volumes[id]->config.option("extruder")->value, + false); + auto opt_keys = model_object->volumes[id]->config.keys(); + if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { + select_item(m_objects_model->AddSettingsChild(vol_item)); + Collapse(vol_item); + } + } + Expand(item); + } + + // add instances to the object, if it has those + if (model_object->instances.size()>1) + increase_object_instances(obj_idx, model_object->instances.size()); + + // add settings to the object, if it has those + auto opt_keys = model_object->config.keys(); + if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { + select_item(m_objects_model->AddSettingsChild(item)); + Collapse(item); + } + +#ifndef __WXOSX__ + selection_changed(); +#endif //__WXMSW__ +} + +void ObjectList::delete_object_from_list() +{ + auto item = GetSelection(); + if (!item) + return; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + select_item(m_objects_model->Delete(item)); + else + select_item(m_objects_model->Delete(m_objects_model->GetParent(item))); +} + +void ObjectList::delete_object_from_list(const size_t obj_idx) +{ + select_item(m_objects_model->Delete(m_objects_model->GetItemById(obj_idx))); +} + +void ObjectList::delete_volume_from_list(const size_t obj_idx, const size_t vol_idx) +{ + select_item(m_objects_model->Delete(m_objects_model->GetItemByVolumeId(obj_idx, vol_idx))); +} + +void ObjectList::delete_instance_from_list(const size_t obj_idx, const size_t inst_idx) +{ + select_item(m_objects_model->Delete(m_objects_model->GetItemByInstanceId(obj_idx, inst_idx))); +} + +void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) +{ + if ( !(type&(itObject|itVolume|itInstance)) ) + return; + + if (type&itObject) { + del_object(obj_idx); + delete_object_from_list(obj_idx); + } + else { + del_subobject_from_object(obj_idx, sub_obj_idx, type); + + type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) : + delete_instance_from_list(obj_idx, sub_obj_idx); + } +} + +void ObjectList::delete_from_model_and_list(const std::vector& items_for_delete) +{ + if (items_for_delete.empty()) + return; + + for (std::vector::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) + { + if (!(item->type&(itObject | itVolume | itInstance))) + continue; + if (item->type&itObject) { + del_object(item->obj_idx); + m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); + } + else { + if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) + continue; + if (item->type&itVolume) + { + m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); + wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); + } + else + m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx)); + } + } + part_selection_changed(); +} + +void ObjectList::delete_all_objects_from_list() +{ + m_objects_model->DeleteAll(); + part_selection_changed(); +} + +void ObjectList::increase_object_instances(const size_t obj_idx, const size_t num) +{ + select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), num)); +} + +void ObjectList::decrease_object_instances(const size_t obj_idx, const size_t num) +{ + select_item(m_objects_model->DeleteLastInstance(m_objects_model->GetItemById(obj_idx), num)); +} + +void ObjectList::unselect_objects() +{ + if (!GetSelection()) + return; + + m_prevent_list_events = true; + UnselectAll(); + part_selection_changed(); + m_prevent_list_events = false; +} + +void ObjectList::select_current_object(int idx) +{ + m_prevent_list_events = true; + UnselectAll(); + if (idx >= 0) + Select(m_objects_model->GetItemById(idx)); + part_selection_changed(); + m_prevent_list_events = false; +} + +void ObjectList::select_current_volume(int idx, int vol_idx) +{ + if (vol_idx < 0) { + select_current_object(idx); + return; + } + m_prevent_list_events = true; + UnselectAll(); + if (idx >= 0) + Select(m_objects_model->GetItemByVolumeId(idx, vol_idx)); + part_selection_changed(); + m_prevent_list_events = false; +} + +void ObjectList::remove() +{ + if (GetSelectedItemsCount() == 0) + return; + + wxDataViewItemArray sels; + GetSelections(sels); + + for (auto& item : sels) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); + else + del_subobject_item(item); + } +} + +void ObjectList::init_objects() +{ + m_objects = wxGetApp().model_objects(); +} + +bool ObjectList::multiple_selection() const +{ + return GetSelectedItemsCount() > 1; +} + +void ObjectList::update_selections() +{ + auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + wxDataViewItemArray sels; + + if (selection.is_single_full_object()) + { + sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + } + else if (selection.is_single_volume() || selection.is_modifier() || + selection.is_multiple_volume() || selection.is_multiple_full_object()) { + for (auto idx : selection.get_volume_idxs()) { + const auto gl_vol = selection.get_volume(idx); + if (selection.is_multiple_full_object()) + sels.Add(m_objects_model->GetItemById(gl_vol->object_idx())); + else if (gl_vol->volume_idx() >= 0) + // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids + // are not associated with ModelVolumes, but they are temporarily generated by the backend + // (for example, SLA supports or SLA pad). + sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); + } + } + else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { + for (auto idx : selection.get_instance_idxs()) { + sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), idx)); + } + } + else if (selection.is_mixed()) + { + auto& objects_content_list = selection.get_content(); + + for (auto idx : selection.get_volume_idxs()) { + const auto gl_vol = selection.get_volume(idx); + const auto& glv_obj_idx = gl_vol->object_idx(); + const auto& glv_ins_idx = gl_vol->instance_idx(); + + bool is_selected = false; + + for (auto obj_ins : objects_content_list) { + if (obj_ins.first == glv_obj_idx) { + if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { + if (glv_ins_idx == 0 && (*m_objects)[glv_obj_idx]->instances.size() == 1) + sels.Add(m_objects_model->GetItemById(glv_obj_idx)); + else + sels.Add(m_objects_model->GetItemByInstanceId(glv_obj_idx, glv_ins_idx)); + + is_selected = true; + break; + } + } + } + + if (is_selected) + continue; + + const auto& glv_vol_idx = gl_vol->volume_idx(); + if (glv_vol_idx == 0 && (*m_objects)[glv_obj_idx]->volumes.size() == 1) + sels.Add(m_objects_model->GetItemById(glv_obj_idx)); + else + sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx)); + } + } + + select_items(sels); + + if (GetSelection()) { + const int sel_item_row = m_objects_model->GetRowByItem(GetSelection()); + ScrollLines(sel_item_row - m_selected_row); + m_selected_row = sel_item_row; + } +} + +void ObjectList::update_selections_on_canvas() +{ + auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + const int sel_cnt = GetSelectedItemsCount(); + if (sel_cnt == 0) { + selection.clear(); + wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); + return; + } + + auto add_to_selection = [this](const wxDataViewItem& item, GLCanvas3D::Selection& selection, bool as_single_selection) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection); + return; + } + + if (m_objects_model->GetItemType(item) == itVolume) { + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int vol_idx = m_objects_model->GetVolumeIdByItem(item); + selection.add_volume(obj_idx, vol_idx, 0, as_single_selection); + } + else if (m_objects_model->GetItemType(item) == itInstance) { + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + const int inst_idx = m_objects_model->GetInstanceIdByItem(item); + selection.add_instance(obj_idx, inst_idx, as_single_selection); + } + }; + + if (sel_cnt == 1) { + wxDataViewItem item = GetSelection(); + if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + add_to_selection(m_objects_model->GetParent(item), selection, true); + else + add_to_selection(item, selection, true); + + wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); + return; + } + + wxDataViewItemArray sels; + GetSelections(sels); + + selection.clear(); + for (auto item: sels) + add_to_selection(item, selection, false); + + wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); +} + +void ObjectList::select_item(const wxDataViewItem& item) +{ + m_prevent_list_events = true; + + UnselectAll(); + Select(item); + part_selection_changed(); + + m_prevent_list_events = false; +} + +void ObjectList::select_items(const wxDataViewItemArray& sels) +{ + m_prevent_list_events = true; + + UnselectAll(); + SetSelections(sels); + part_selection_changed(); + + m_prevent_list_events = false; +} + +void ObjectList::select_all() +{ + SelectAll(); + selection_changed(); +} + +void ObjectList::select_item_all_children() +{ + wxDataViewItemArray sels; + + // There is no selection before OR some object is selected => select all objects + if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { + for (int i = 0; i < m_objects->size(); i++) + sels.Add(m_objects_model->GetItemById(i)); + } + else { + const auto item = GetSelection(); + // Some volume(instance) is selected => select all volumes(instances) inside the current object + if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) { + m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); + } + } + + SetSelections(sels); + selection_changed(); +} + +void ObjectList::fix_multiselection_conflicts() +{ + if (GetSelectedItemsCount() <= 1) + return; + + m_prevent_list_events = true; + + wxDataViewItemArray sels; + GetSelections(sels); + + for (auto item : sels) { + if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + Unselect(item); + else if (m_objects_model->GetParent(item) != wxDataViewItem(0)) + Unselect(m_objects_model->GetParent(item)); + } + + m_prevent_list_events = false; +} + +ModelVolume* ObjectList::get_selected_model_volume() +{ + auto item = GetSelection(); + if (!item || m_objects_model->GetItemType(item) != itVolume) + return nullptr; + + const auto vol_idx = m_objects_model->GetVolumeIdByItem(item); + const auto obj_idx = get_selected_obj_idx(); + if (vol_idx < 0 || obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]->volumes[vol_idx]; +} + +void ObjectList::change_part_type() +{ + ModelVolume* volume = get_selected_model_volume(); + if (!volume) + return; + + const auto type = volume->type(); + if (type == ModelVolume::MODEL_PART) + { + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + int model_part_cnt = 0; + for (auto vol : (*m_objects)[obj_idx]->volumes) { + if (vol->type() == ModelVolume::MODEL_PART) + ++model_part_cnt; + } + + if (model_part_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't change a type of the last solid part of the object."))); + return; + } + } + + const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" }; + + auto new_type = wxGetSingleChoiceIndex("Type: ", _(L("Select type of part")), wxArrayString(4, names), type); + + if (new_type == type || new_type < 0) + return; + + const auto item = GetSelection(); + volume->set_type(static_cast(new_type)); + m_objects_model->SetVolumeType(item, new_type); + + m_parts_changed = true; + parts_changed(get_selected_obj_idx()); + + // Update settings showing, if we have it + //(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer) + const auto settings_item = m_objects_model->GetSettingsItem(item); + if (settings_item && + (new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER)) { + m_objects_model->Delete(settings_item); + } + else if (!settings_item && + (new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER)) { + select_item(m_objects_model->AddSettingsChild(item)); + } +} + +void ObjectList::last_volume_is_deleted(const int obj_idx) +{ + + if (obj_idx < 0 || m_objects->empty() || + obj_idx <= m_objects->size() || + (*m_objects)[obj_idx]->volumes.empty()) + return; + auto volume = (*m_objects)[obj_idx]->volumes[0]; + + // clear volume's config values + volume->config.clear(); + + // set a default extruder value, since user can't add it manually + volume->config.set_key_value("extruder", new ConfigOptionInt(0)); +} + +bool ObjectList::has_multi_part_objects() +{ + if (!m_objects_model->IsEmpty()) { + wxDataViewItemArray items; + m_objects_model->GetChildren(wxDataViewItem(0), items); + + for (auto& item : items) + if (m_objects_model->GetItemByType(item, itVolume)) + return true; + } + return false; +} + +void ObjectList::update_settings_items() +{ + wxDataViewItemArray items; + m_objects_model->GetChildren(wxDataViewItem(0), items); + + for (auto& item : items) { + const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); + select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + } + UnselectAll(); +} + +void ObjectList::ItemValueChanged(wxDataViewEvent &event) +{ + if (event.GetColumn() == 0) + update_name_in_model(event.GetItem()); + else if (event.GetColumn() == 1) + update_extruder_in_config(event.GetItem()); +} + +void ObjectList::OnEditingDone(wxDataViewEvent &event) +{ + if (event.GetColumn() != 0) + return; + + const auto renderer = dynamic_cast(GetColumn(0)->GetRenderer()); + + if (renderer->WasCanceled()) + show_error(this, _(L("The supplied name is not valid;")) + "\n" + + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp new file mode 100644 index 000000000..7631782df --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -0,0 +1,240 @@ +#ifndef slic3r_GUI_ObjectList_hpp_ +#define slic3r_GUI_ObjectList_hpp_ + +#include +#include + +#include +#include +#include + +#include "Event.hpp" +#include "wxExtensions.hpp" + +class wxBoxSizer; +class wxMenuItem; +class PrusaObjectDataViewModel; + +namespace Slic3r { +class ConfigOptionsGroup; +class DynamicPrintConfig; +class ModelObject; +class ModelVolume; + +// FIXME: broken build on mac os because of this is missing: +typedef std::vector t_config_option_keys; + +namespace GUI { + +wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); + +struct ItemForDelete +{ + ItemType type; + int obj_idx; + int sub_obj_idx; + + ItemForDelete(ItemType type, int obj_idx, int sub_obj_idx) + : type(type), obj_idx(obj_idx), sub_obj_idx(sub_obj_idx) + {} + + bool operator==(const ItemForDelete& r) const + { + return (type == r.type && obj_idx == r.obj_idx && sub_obj_idx == r.sub_obj_idx); + } + + bool operator<(const ItemForDelete& r) const + { + if (obj_idx != r.obj_idx) + return (obj_idx < r.obj_idx); + return (sub_obj_idx < r.sub_obj_idx); + } +}; + +class ObjectList : public wxDataViewCtrl +{ + + struct dragged_item_data + { + void init(const int obj_idx, const int vol_idx) { + m_obj_idx = obj_idx; + m_vol_idx = vol_idx; + } + + void clear() { + m_obj_idx = -1; + m_vol_idx = -1; + } + + int obj_idx() const { return m_obj_idx; } + int vol_idx() const { return m_vol_idx; } + + private: + int m_obj_idx = -1; + int m_vol_idx = -1; + } m_dragged_data; + + wxBoxSizer *m_sizer {nullptr}; + + DynamicPrintConfig *m_default_config {nullptr}; + + wxWindow *m_parent {nullptr}; + + wxBitmap m_bmp_modifiermesh; + wxBitmap m_bmp_solidmesh; + wxBitmap m_bmp_support_enforcer; + wxBitmap m_bmp_support_blocker; + wxBitmap m_bmp_manifold_warning; + wxBitmap m_bmp_cog; + wxBitmap m_bmp_split; + + wxMenu m_menu_object; + wxMenu m_menu_part; + wxMenu m_menu_sla_object; + wxMenuItem* m_menu_item_split { nullptr }; + wxMenuItem* m_menu_item_split_part { nullptr }; + wxMenuItem* m_menu_item_settings { nullptr }; + + std::vector m_bmp_vector; + + int m_selected_object_id = -1; + bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() + // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler + // calls this method again and again and again + + bool m_prevent_update_extruder_in_config = false; // We use this flag to avoid updating of the extruder value in config + // during updating of the extruder count. + + bool m_parts_changed = false; + bool m_part_settings_changed = false; + + int m_selected_row = 0; + +public: + ObjectList(wxWindow* parent); + ~ObjectList(); + + + std::map CATEGORY_ICON; + + PrusaObjectDataViewModel *m_objects_model{ nullptr }; + DynamicPrintConfig *m_config {nullptr}; + + std::vector *m_objects{ nullptr }; + + + void create_objects_ctrl(); + wxDataViewColumn* create_objects_list_extruder_column(int extruders_count); + void update_objects_list_extruder_column(int extruders_count); + // show/hide "Extruder" column for Objects List + void set_extruder_column_hidden(const bool hide) const; + // update extruder in current config + void update_extruder_in_config(const wxDataViewItem& item); + // update changed name in the object model + void update_name_in_model(const wxDataViewItem& item); + void update_extruder_values_for_items(const int max_extruder); + + void init_icons(); + + void set_tooltip_for_item(const wxPoint& pt); + + void selection_changed(); + void show_context_menu(); + void key_event(wxKeyEvent& event); + + void get_settings_choice(const wxString& category_name); + void append_menu_item_add_generic(wxMenuItem* menu, const int type); + void append_menu_items_add_volume(wxMenu* menu); + wxMenuItem* append_menu_item_split(wxMenu* menu); + wxMenuItem* append_menu_item_settings(wxMenu* menu); + wxMenuItem* append_menu_item_change_type(wxMenu* menu); + void create_object_popupmenu(wxMenu *menu); + void create_sla_object_popupmenu(wxMenu*menu); + void create_part_popupmenu(wxMenu*menu); + wxMenu* create_settings_popupmenu(wxMenu *parent_menu); + + void update_opt_keys(t_config_option_keys& t_optopt_keys); + + void load_subobject(int type); + void load_part(ModelObject* model_object, wxArrayString& part_names, int type); + void load_generic_subobject(const std::string& type_name, const int type); + void del_object(const int obj_idx); + void del_subobject_item(wxDataViewItem& item); + void del_settings_from_config(); + void del_instances_from_object(const int obj_idx); + bool del_subobject_from_object(const int obj_idx, const int idx, const int type); + void split(); + bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); + bool is_splittable(); + + wxPoint get_mouse_position_in_control(); + wxBoxSizer* get_sizer() {return m_sizer;} + int get_selected_obj_idx() const; + bool is_parts_changed() const { return m_parts_changed; } + bool is_part_settings_changed() const { return m_part_settings_changed; } + void part_settings_changed(); + + void parts_changed(int obj_idx); + void part_selection_changed(); + + // Add object to the list + void add_object_to_list(size_t obj_idx); + // Delete object from the list + void delete_object_from_list(); + void delete_object_from_list(const size_t obj_idx); + void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx); + void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx); + void delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx); + void delete_from_model_and_list(const std::vector& items_for_delete); + // Delete all objects from the list + void delete_all_objects_from_list(); + // Increase instances count + void increase_object_instances(const size_t obj_idx, const size_t num); + // Decrease instances count + void decrease_object_instances(const size_t obj_idx, const size_t num); + + // #ys_FIXME_to_delete + // Unselect all objects in the list on c++ side + void unselect_objects(); + // Select current object in the list on c++ side + void select_current_object(int idx); + // Select current volume in the list on c++ side + void select_current_volume(int idx, int vol_idx); + + // Remove objects/sub-object from the list + void remove(); + + void init_objects(); + bool multiple_selection() const ; + void update_selections(); + void update_selections_on_canvas(); + void select_item(const wxDataViewItem& item); + void select_items(const wxDataViewItemArray& sels); + void select_all(); + void select_item_all_children(); + // correct current selections to avoid of the possible conflicts + void fix_multiselection_conflicts(); + + ModelVolume* get_selected_model_volume(); + void change_part_type(); + + void last_volume_is_deleted(const int obj_idx); + bool has_multi_part_objects(); + void update_settings_items(); + +private: + void OnChar(wxKeyEvent& event); + void OnContextMenu(wxDataViewEvent &event); + + void OnBeginDrag(wxDataViewEvent &event); + void OnDropPossible(wxDataViewEvent &event); + void OnDrop(wxDataViewEvent &event); + + void ItemValueChanged(wxDataViewEvent &event); + void OnEditingDone(wxDataViewEvent &event); +}; + + +}} + +#endif //slic3r_GUI_ObjectList_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp new file mode 100644 index 000000000..d193a11a9 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -0,0 +1,480 @@ +#include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectList.hpp" +#include "I18N.hpp" + +#include "OptionsGroup.hpp" +#include "wxExtensions.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Geometry.hpp" + +#include + +namespace Slic3r +{ +namespace GUI +{ + +ObjectManipulation::ObjectManipulation(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_og->set_name(_(L("Object Manipulation"))); + m_og->label_width = 100; + m_og->set_grid_vgap(5); + + m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) { + std::vector axes{ "_x", "_y", "_z" }; + + if (opt_key == "scale_unit") { + const wxString& selection = boost::any_cast(value); + for (auto axis : axes) { + std::string key = "scale" + axis; + m_og->set_side_text(key, selection); + } + + m_is_percent_scale = selection == _("%"); + update_scale_values(); + return; + } + + std::string param; + std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); + + size_t i = 0; + Vec3d new_value; + for (auto axis : axes) + new_value(i++) = boost::any_cast(m_og->get_value(param+axis)); + + if (param == "position") + change_position_value(new_value); + else if (param == "rotation") + change_rotation_value(new_value); + else if (param == "scale") + change_scale_value(new_value); + }; + + m_og->m_fill_empty_value = [this](const std::string& opt_key) + { + if (opt_key == "scale_unit") + return; + + std::string param; + std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); + if (param == "position") { + int axis = opt_key.back() == 'x' ? 0 : + opt_key.back() == 'y' ? 1 : 2; + + m_og->set_value(opt_key, double_to_string(cache_position(axis))); + return; + } + + m_og->set_value(opt_key, double_to_string(0.0)); + }; + + m_og->m_set_focus = [this](const std::string& opt_key) + { + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key); + }; + + ConfigOptionDef def; + + // Objects(sub-objects) name + def.label = L("Name"); + // def.type = coString; + def.gui_type = "legend"; + def.tooltip = L("Object name"); + def.full_width = true; + def.default_value = new ConfigOptionString{ " " }; + m_og->append_single_option_line(Option(def, "object_name")); + + // Legend for object modification + auto line = Line{ "", "" }; + def.label = ""; + def.type = coString; + def.width = 50; + + std::vector axes{ "x", "y", "z" }; + for (const auto axis : axes) { + const auto label = boost::algorithm::to_upper_copy(axis); + def.default_value = new ConfigOptionString{ " " + label }; + Option option = Option(def, axis + "_axis_legend"); + line.append_option(option); + } + m_og->append_line(line); + + + auto add_og_to_object_settings = [](const std::string& option_name, const std::string& sidetext) + { + Line line = { _(option_name), "" }; + if (option_name == "Scale") { + line.near_label_widget = [](wxWindow* parent) { + auto btn = new PrusaLockButton(parent, wxID_ANY); + btn->Bind(wxEVT_BUTTON, [btn](wxCommandEvent &event) { + event.Skip(); + wxTheApp->CallAfter([btn]() { + wxGetApp().obj_manipul()->set_uniform_scaling(btn->IsLocked()); + }); + }); + return btn; + }; + } + + ConfigOptionDef def; + def.type = coFloat; + def.default_value = new ConfigOptionFloat(0.0); + def.width = 50; + + if (option_name == "Rotation") + def.min = -360; + + const std::string lower_name = boost::algorithm::to_lower_copy(option_name); + + std::vector axes{ "x", "y", "z" }; + for (auto axis : axes) { + if (axis == "z" && option_name != "Scale") + def.sidetext = sidetext; + Option option = Option(def, lower_name + "_" + axis); + option.opt.full_width = true; + line.append_option(option); + } + + if (option_name == "Scale") + { + def.width = 45; + def.type = coStrings; + def.gui_type = "select_open"; + def.enum_labels.push_back(L("%")); + def.enum_labels.push_back(L("mm")); + def.default_value = new ConfigOptionStrings{ "mm" }; + + const Option option = Option(def, lower_name + "_unit"); + line.append_option(option); + } + + return line; + }; + + + // Settings table + m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); + m_og->append_line(add_og_to_object_settings(L("Rotation"), "°")); + m_og->append_line(add_og_to_object_settings(L("Scale"), "mm")); + + /* Unused parameter at this time + def.label = L("Place on bed"); + def.type = coBool; + def.tooltip = L("Automatic placing of models on printing bed in Y axis"); + def.gui_type = ""; + def.sidetext = ""; + def.default_value = new ConfigOptionBool{ false }; + m_og->append_single_option_line(Option(def, "place_on_bed")); + */ +} + +void ObjectManipulation::Show(const bool show) +{ + if (show == IsShown()) + return; + + m_og->Show(show); + + if (show && wxGetApp().get_view_mode() != ConfigMenuModeSimple) { + m_og->get_grid_sizer()->Show(size_t(0), false); + m_og->get_grid_sizer()->Show(size_t(1), false); + } +} + +bool ObjectManipulation::IsShown() +{ + return m_og->get_grid_sizer()->IsShown(2); +} + +void ObjectManipulation::UpdateAndShow(const bool show) +{ + if (show) + update_settings_value(wxGetApp().plater()->canvas3D()->get_selection()); + + OG_Settings::UpdateAndShow(show); +} + +int ObjectManipulation::ol_selection() +{ + return wxGetApp().obj_list()->get_selected_obj_idx(); +} + +void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) +{ + wxString move_label = _(L("Position")); +#if ENABLE_MODELVOLUME_TRANSFORM + if (selection.is_single_full_instance() || selection.is_single_full_object()) +#else + if (selection.is_single_full_object()) + { + auto obj_idx = selection.get_object_idx(); + if (obj_idx >=0 && !wxGetApp().model_objects()->empty() && (*wxGetApp().model_objects())[obj_idx]->instances.size() == 1) + { + // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + update_position_value(volume->get_offset()); + update_rotation_value(volume->get_rotation()); + update_scale_value(volume->get_scaling_factor()); + m_og->enable(); + } + else + reset_settings_value(); + } + else if (selection.is_single_full_instance()) +#endif // ENABLE_MODELVOLUME_TRANSFORM + { + // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_MODELVOLUME_TRANSFORM + update_position_value(volume->get_instance_offset()); + update_rotation_value(volume->get_instance_rotation()); + update_scale_value(volume->get_instance_scaling_factor()); +#else + update_position_value(volume->get_offset()); + update_rotation_value(volume->get_rotation()); + update_scale_value(volume->get_scaling_factor()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + m_og->enable(); + } + else if (selection.is_wipe_tower()) + { + // the selection contains a single volume + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_MODELVOLUME_TRANSFORM + update_position_value(volume->get_volume_offset()); + update_rotation_value(volume->get_volume_rotation()); + update_scale_value(volume->get_volume_scaling_factor()); +#else + update_position_value(volume->get_offset()); + update_rotation_value(volume->get_rotation()); + update_scale_value(volume->get_scaling_factor()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + m_og->enable(); + } + else if (selection.is_single_modifier() || selection.is_single_volume()) + { + // the selection contains a single volume + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_MODELVOLUME_TRANSFORM + update_position_value(volume->get_volume_offset()); + update_rotation_value(volume->get_volume_rotation()); + update_scale_value(volume->get_volume_scaling_factor()); +#else + update_position_value(volume->get_offset()); + update_rotation_value(volume->get_rotation()); + update_scale_value(volume->get_scaling_factor()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + m_og->enable(); + } + else if (wxGetApp().obj_list()->multiple_selection()) + { + reset_settings_value(); + move_label = _(L("Displacement")); + m_og->enable(); + } + else + reset_settings_value(); + + m_move_Label->SetLabel(move_label); + m_og->get_field("scale_unit")->disable();// temporary decision +} + +void ObjectManipulation::reset_settings_value() +{ + reset_position_value(); + reset_rotation_value(); + reset_scale_value(); + m_og->disable(); +} + +wxString def_0 {"0"}; +wxString def_100 {"100"}; + +void ObjectManipulation::reset_position_value() +{ + m_og->set_value("position_x", def_0); + m_og->set_value("position_y", def_0); + m_og->set_value("position_z", def_0); + + cache_position = { 0., 0., 0. }; +} + +void ObjectManipulation::reset_rotation_value() +{ + m_og->set_value("rotation_x", def_0); + m_og->set_value("rotation_y", def_0); + m_og->set_value("rotation_z", def_0); +} + +void ObjectManipulation::reset_scale_value() +{ + m_is_percent_scale = true; + m_og->set_value("scale_unit", _("%")); + m_og->set_value("scale_x", def_100); + m_og->set_value("scale_y", def_100); + m_og->set_value("scale_z", def_100); +} + +void ObjectManipulation::update_values() +{ + int selection = ol_selection(); + if (selection < 0 || wxGetApp().mainframe->m_plater->model().objects.size() <= selection) { + m_og->set_value("position_x", def_0); + m_og->set_value("position_y", def_0); + m_og->set_value("position_z", def_0); + m_og->set_value("scale_x" , def_0); + m_og->set_value("scale_y" , def_0); + m_og->set_value("scale_z" , def_0); + m_og->set_value("rotation_x", def_0); + m_og->set_value("rotation_y", def_0); + m_og->set_value("rotation_z", def_0); + m_og->disable(); + return; + } + m_is_percent_scale = boost::any_cast(m_og->get_value("scale_unit")) == _("%"); + + update_position_values(); + update_scale_values(); + update_rotation_values(); + m_og->enable(); +} + +void ObjectManipulation::update_scale_values() +{ + int selection = ol_selection(); + ModelObjectPtrs& objects = wxGetApp().mainframe->m_plater->model().objects; + + auto instance = objects[selection]->instances.front(); + auto size = objects[selection]->instance_bounding_box(0).size(); + + if (m_is_percent_scale) { + m_og->set_value("scale_x", double_to_string(instance->get_scaling_factor(X) * 100, 2)); + m_og->set_value("scale_y", double_to_string(instance->get_scaling_factor(Y) * 100, 2)); + m_og->set_value("scale_z", double_to_string(instance->get_scaling_factor(Z) * 100, 2)); + } + else { + m_og->set_value("scale_x", double_to_string(size(0), 2)); + m_og->set_value("scale_y", double_to_string(size(1), 2)); + m_og->set_value("scale_z", double_to_string(size(2), 2)); + } +} + +void ObjectManipulation::update_position_values() +{ + auto instance = wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front(); + + m_og->set_value("position_x", double_to_string(instance->get_offset(X), 2)); + m_og->set_value("position_y", double_to_string(instance->get_offset(Y), 2)); + m_og->set_value("position_z", double_to_string(instance->get_offset(Z), 2)); +} + +void ObjectManipulation::update_position_value(const Vec3d& position) +{ + m_og->set_value("position_x", double_to_string(position(0), 2)); + m_og->set_value("position_y", double_to_string(position(1), 2)); + m_og->set_value("position_z", double_to_string(position(2), 2)); + + cache_position = position; +} + +void ObjectManipulation::update_scale_value(const Vec3d& scaling_factor) +{ + // this is temporary + // to be able to update the values as size + // we need to store somewhere the original size + // or have it passed as parameter + if (!m_is_percent_scale) { + m_is_percent_scale = true; + m_og->set_value("scale_unit", _("%")); + } + + auto scale = scaling_factor * 100.0; + m_og->set_value("scale_x", double_to_string(scale(0), 2)); + m_og->set_value("scale_y", double_to_string(scale(1), 2)); + m_og->set_value("scale_z", double_to_string(scale(2), 2)); +} + +void ObjectManipulation::update_rotation_values() +{ + update_rotation_value(wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front()->get_rotation()); +} + +void ObjectManipulation::update_rotation_value(double angle, Axis axis) +{ + std::string axis_str; + switch (axis) { + case X: { + axis_str = "rotation_x"; + break; } + case Y: { + axis_str = "rotation_y"; + break; } + case Z: { + axis_str = "rotation_z"; + break; } + } + + m_og->set_value(axis_str, round_nearest(int(Geometry::rad2deg(angle)), 0)); +} + +void ObjectManipulation::update_rotation_value(const Vec3d& rotation) +{ + m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(rotation(0)), 0), 2)); + m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(rotation(1)), 0), 2)); + m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(rotation(2)), 0), 2)); +} + + +void ObjectManipulation::change_position_value(const Vec3d& position) +{ + Vec3d displacement(position - cache_position); + + auto canvas = wxGetApp().plater()->canvas3D(); + canvas->get_selection().start_dragging(); + canvas->get_selection().translate(displacement); + canvas->do_move(); + + cache_position = position; +} + +void ObjectManipulation::change_rotation_value(const Vec3d& rotation) +{ + Vec3d rad_rotation; + for (size_t i = 0; i < 3; ++i) + rad_rotation(i) = Geometry::deg2rad(rotation(i)); + auto canvas = wxGetApp().plater()->canvas3D(); + canvas->get_selection().start_dragging(); + canvas->get_selection().rotate(rad_rotation, false); + canvas->do_rotate(); +} + +void ObjectManipulation::change_scale_value(const Vec3d& scale) +{ + Vec3d scaling_factor; + if (m_is_percent_scale) + scaling_factor = scale*0.01; + else { + int selection = ol_selection(); + ModelObjectPtrs& objects = *wxGetApp().model_objects(); + + auto size = objects[selection]->instance_bounding_box(0).size(); + for (size_t i = 0; i < 3; ++i) + scaling_factor(i) = scale(i) / size(i); + } + + auto canvas = wxGetApp().plater()->canvas3D(); + canvas->get_selection().start_dragging(); + canvas->get_selection().scale(scaling_factor, false); + canvas->do_scale(); +} + +void ObjectManipulation::print_cashe_value(const std::string& label, const Vec3d& v) +{ + std::cout << label << " => " << " X:" << v(0) << " Y:" << v(1) << " Z:" << v(2) << std::endl; +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp new file mode 100644 index 000000000..3a8df4111 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -0,0 +1,68 @@ +#ifndef slic3r_GUI_ObjectManipulation_hpp_ +#define slic3r_GUI_ObjectManipulation_hpp_ + +#include + +#include "GUI_ObjectSettings.hpp" +#include "GLCanvas3D.hpp" + +class wxStaticText; + +namespace Slic3r { +namespace GUI { + + +class ObjectManipulation : public OG_Settings +{ + bool m_is_percent_scale = false; // true -> percentage scale unit + // false -> uniform scale unit + bool m_is_uniform_scale = false; // It indicates if scale is uniform + + Vec3d cache_position { 0., 0., 0. }; + wxStaticText* m_move_Label = nullptr; + +public: + ObjectManipulation(wxWindow* parent); + ~ObjectManipulation() {} + + void Show(const bool show) override; + bool IsShown() override; + void UpdateAndShow(const bool show) override; + + int ol_selection(); + + void update_settings_value(const GLCanvas3D::Selection& selection); + void reset_settings_value(); + void reset_position_value(); + void reset_rotation_value(); + void reset_scale_value(); + + void update_values(); + // update position values displacements or "gizmos" + void update_position_values(); + void update_position_value(const Vec3d& position); + // update scale values after scale unit changing or "gizmos" + void update_scale_values(); + void update_scale_value(const Vec3d& scaling_factor); + // update rotation values object selection changing + void update_rotation_values(); + // update rotation value after "gizmos" + void update_rotation_value(double angle, Axis axis); + void update_rotation_value(const Vec3d& rotation); + + void set_uniform_scaling(const bool uniform_scale) { m_is_uniform_scale = uniform_scale; } + + + // change values + void change_position_value(const Vec3d& position); + void change_rotation_value(const Vec3d& rotation); + void change_scale_value(const Vec3d& scale); + + +private: + void print_cashe_value(const std::string& label, const Vec3d& value); +}; + +}} + +#endif // slic3r_GUI_ObjectManipulation_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectParts.cpp b/src/slic3r/GUI/GUI_ObjectParts.cpp deleted file mode 100644 index ae34359ce..000000000 --- a/src/slic3r/GUI/GUI_ObjectParts.cpp +++ /dev/null @@ -1,2041 +0,0 @@ -#include "GUI.hpp" -#include "OptionsGroup.hpp" -#include "PresetBundle.hpp" -#include "GUI_ObjectParts.hpp" -#include "Model.hpp" -#include "wxExtensions.hpp" -#include "LambdaObjectDialog.hpp" -#include "../../libslic3r/Utils.hpp" - -#include -#include -#include -#include "Geometry.hpp" -#include "slic3r/Utils/FixModelByWin10.hpp" - -#include -#include "3DScene.hpp" - -namespace Slic3r -{ -namespace GUI -{ -wxSizer *m_sizer_object_buttons = nullptr; -wxSizer *m_sizer_part_buttons = nullptr; -wxSizer *m_sizer_object_movers = nullptr; -wxDataViewCtrl *m_objects_ctrl = nullptr; -PrusaObjectDataViewModel *m_objects_model = nullptr; -wxCollapsiblePane *m_collpane_settings = nullptr; -PrusaDoubleSlider *m_slider = nullptr; -wxGLCanvas *m_preview_canvas = nullptr; - -wxBitmap m_icon_modifiermesh; -wxBitmap m_icon_solidmesh; -wxBitmap m_icon_manifold_warning; -wxBitmap m_bmp_cog; -wxBitmap m_bmp_split; - -wxSlider* m_mover_x = nullptr; -wxSlider* m_mover_y = nullptr; -wxSlider* m_mover_z = nullptr; -wxButton* m_btn_move_up = nullptr; -wxButton* m_btn_move_down = nullptr; -Vec3d m_move_options; -Vec3d m_last_coords; -int m_selected_object_id = -1; - -bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() - // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler - // calls this method again and again and again -bool g_is_percent_scale = false; // It indicates if scale unit is percentage -bool g_is_uniform_scale = false; // It indicates if scale is uniform -ModelObjectPtrs* m_objects; -std::shared_ptr m_config; -std::shared_ptr m_default_config; -wxBoxSizer* m_option_sizer = nullptr; - -// option groups for settings -std::vector > m_og_settings; - -int m_event_object_selection_changed = 0; -int m_event_object_settings_changed = 0; -int m_event_remove_object = 0; -int m_event_update_scene = 0; - -bool m_parts_changed = false; -bool m_part_settings_changed = false; - -#ifdef __WXOSX__ - wxString g_selected_extruder = ""; -#endif //__WXOSX__ - -inline t_category_icon& get_category_icon() { - static t_category_icon CATEGORY_ICON; - if (CATEGORY_ICON.empty()){ - CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(Slic3r::var("layers.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(Slic3r::var("infill.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(Slic3r::var("building.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Speed")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Extruders")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Extrusion Width")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); -// CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(Slic3r::var("box.png")), wxBITMAP_TYPE_PNG); -// CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); - CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(Slic3r::var("wand.png")), wxBITMAP_TYPE_PNG); - } - return CATEGORY_ICON; -} - -std::vector get_options(const bool is_part) -{ - PrintRegionConfig reg_config; - auto options = reg_config.keys(); - if (!is_part) { - PrintObjectConfig obj_config; - std::vector obj_options = obj_config.keys(); - options.insert(options.end(), obj_options.begin(), obj_options.end()); - } - return options; -} - -// category -> vector ( option ; label ) -typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; -void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) -{ - auto options = get_options(is_part); - - auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); - - DynamicPrintConfig config; - for (auto& option : options) - { - auto const opt = config.def()->get(option); - auto category = opt->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; - - std::pair option_label(option, opt->label); - std::vector< std::pair > new_category; - auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); - cat_opt_label.push_back(option_label); - if (cat_opt_label.size() == 1) - settings_menu[category] = cat_opt_label; - } -} - -void set_event_object_selection_changed(const int& event){ - m_event_object_selection_changed = event; -} -void set_event_object_settings_changed(const int& event){ - m_event_object_settings_changed = event; -} -void set_event_remove_object(const int& event){ - m_event_remove_object = event; -} -void set_event_update_scene(const int& event){ - m_event_update_scene = event; -} - -void set_objects_from_model(Model &model) { - m_objects = &(model.objects); -} - -void init_mesh_icons(){ - m_icon_modifiermesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); - m_icon_solidmesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); - - // init icon for manifold warning - m_icon_manifold_warning = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); - - // init bitmap for "Split to sub-objects" context menu - m_bmp_split = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("split.png")), wxBITMAP_TYPE_PNG); - - // init bitmap for "Add Settings" context menu - m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); -} - -bool is_parts_changed(){return m_parts_changed;} -bool is_part_settings_changed(){ return m_part_settings_changed; } - -static wxString dots("…", wxConvUTF8); - -void set_tooltip_for_item(const wxPoint& pt) -{ - wxDataViewItem item; - wxDataViewColumn* col; - m_objects_ctrl->HitTest(pt, item, col); - if (!item) return; - - if (col->GetTitle() == " ") - m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); - else if (col->GetTitle() == _("Name") && - m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { - int obj_idx = m_objects_model->GetIdByItem(item); - auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - - wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); - - std::map error_msg; - error_msg[L("degenerate facets")] = stats.degenerate_facets; - error_msg[L("edges fixed")] = stats.edges_fixed; - error_msg[L("facets removed")] = stats.facets_removed; - error_msg[L("facets added")] = stats.facets_added; - error_msg[L("facets reversed")] = stats.facets_reversed; - error_msg[L("backwards edges")] = stats.backwards_edges; - - for (auto error : error_msg) - { - if (error.second > 0) - tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); - } -// OR -// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " -// "%d facets added, %d facets reversed, %d backwards edges")), -// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, -// stats.facets_added, stats.facets_reversed, stats.backwards_edges); - - if (is_windows10()) - tooltip += _(L("Right button click the icon to fix STL through Netfabb")); - - m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); - } - else - m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip -} - -wxPoint get_mouse_position_in_control() { - const wxPoint& pt = wxGetMousePosition(); - wxWindow* win = m_objects_ctrl->GetMainWindow(); - return wxPoint(pt.x - win->GetScreenPosition().x, - pt.y - win->GetScreenPosition().y); -} - -bool is_mouse_position_in_control(wxPoint& pt) { - pt = get_mouse_position_in_control(); - const wxSize& cz = m_objects_ctrl->GetSize(); - if (pt.x > 0 && pt.x < cz.x && - pt.y > 0 && pt.y < cz.y) - return true; - return false; -} - -wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count) -{ - wxArrayString choices; - choices.Add("default"); - for (int i = 1; i <= extruders_count; ++i) - choices.Add(wxString::Format("%d", i)); - wxDataViewChoiceRenderer *c = - new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); - wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 2, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); - return column; -} - -void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz) -{ - m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_objects_ctrl->SetMinSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects - - objects_sz = new wxBoxSizer(wxVERTICAL); - objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); - - m_objects_model = new PrusaObjectDataViewModel; - m_objects_ctrl->AssociateModel(m_objects_model); -#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); - m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); -#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - - // column 0(Icon+Text) of the view control: - // And Icon can be consisting of several bitmaps - m_objects_ctrl->AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), - 0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); - - // column 1 of the view control: - m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); - - // column 2 of the view control: - m_objects_ctrl->AppendColumn(object_ctrl_create_extruder_column(4)); - - // column 3 of the view control: - m_objects_ctrl->AppendBitmapColumn(" ", 3, wxDATAVIEW_CELL_INERT, 25, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); -} - -// ****** from GUI.cpp -wxBoxSizer* create_objects_list(wxWindow *win) -{ - wxBoxSizer* objects_sz; - // create control - create_objects_ctrl(win, objects_sz); - - // describe control behavior - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { - object_ctrl_selection_changed(); -#ifndef __WXMSW__ - set_tooltip_for_item(get_mouse_position_in_control()); -#endif //__WXMSW__ - }); - - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) { - object_ctrl_context_menu(); -// event.Skip(); - }); - - m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX - -#ifdef __WXMSW__ - // Extruder value changed - m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); - - m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { - set_tooltip_for_item(event.GetPosition()); - event.Skip(); - }); -#else - // equivalent to wxEVT_CHOICE on __WXMSW__ - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); }); -#endif //__WXMSW__ - - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [](wxDataViewEvent& e) {on_begin_drag(e);}); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [](wxDataViewEvent& e) {on_drop_possible(e); }); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP, [](wxDataViewEvent& e) {on_drop(e);}); - return objects_sz; -} - -wxBoxSizer* create_edit_object_buttons(wxWindow* win) -{ - auto sizer = new wxBoxSizer(wxVERTICAL); - - auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - m_btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); - m_btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); - - //*** button's functions - btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) { -// on_btn_load(win); - }); - - btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { -// on_btn_load(win, true); - }); - - btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { -// on_btn_load(win, true, true); - }); - - btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); }); - btn_split ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(true); }); - m_btn_move_up ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_up(); }); - m_btn_move_down ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_down(); }); - //*** - - m_btn_move_up->SetMinSize(wxSize(20, -1)); - m_btn_move_down->SetMinSize(wxSize(20, -1)); - btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); - btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); - m_btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); - m_btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); - - m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); - m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); - m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); - m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); - m_sizer_object_buttons->Show(false); - - m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0); - m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); - m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); - { - auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); - up_down_sizer->Add(m_btn_move_up, 1, wxEXPAND); - up_down_sizer->Add(m_btn_move_down, 1, wxEXPAND); - m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); - } - m_sizer_part_buttons->Show(false); - - btn_load_part->SetFont(Slic3r::GUI::small_font()); - btn_load_modifier->SetFont(Slic3r::GUI::small_font()); - btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); - btn_delete->SetFont(Slic3r::GUI::small_font()); - btn_split->SetFont(Slic3r::GUI::small_font()); - m_btn_move_up->SetFont(Slic3r::GUI::small_font()); - m_btn_move_down->SetFont(Slic3r::GUI::small_font()); - - sizer->Add(m_sizer_object_buttons, 0, wxEXPAND | wxLEFT, 20); - sizer->Add(m_sizer_part_buttons, 0, wxEXPAND | wxLEFT, 20); - return sizer; -} - -void update_after_moving() -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item || m_selected_object_id<0) - return; - - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0) - return; - - auto d = m_move_options - m_last_coords; - auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - volume->mesh.translate(d(0), d(1), d(2)); - m_last_coords = m_move_options; - - m_parts_changed = true; - parts_changed(m_selected_object_id); -} - -wxSizer* object_movers(wxWindow *win) -{ -// DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Move"/*, config*/); - optgroup->label_width = 20; - optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ - int val = boost::any_cast(value); - bool update = false; - if (opt_key == "x" && m_move_options(0) != val){ - update = true; - m_move_options(0) = val; - } - else if (opt_key == "y" && m_move_options(1) != val){ - update = true; - m_move_options(1) = val; - } - else if (opt_key == "z" && m_move_options(2) != val){ - update = true; - m_move_options(2) = val; - } - if (update) update_after_moving(); - }; - - ConfigOptionDef def; - def.label = L("X"); - def.type = coInt; - def.gui_type = "slider"; - def.default_value = new ConfigOptionInt(0); - - Option option = Option(def, "x"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); - m_mover_x = dynamic_cast(optgroup->get_field("x")->getWindow()); - - def.label = L("Y"); - option = Option(def, "y"); - optgroup->append_single_option_line(option); - m_mover_y = dynamic_cast(optgroup->get_field("y")->getWindow()); - - def.label = L("Z"); - option = Option(def, "z"); - optgroup->append_single_option_line(option); - m_mover_z = dynamic_cast(optgroup->get_field("z")->getWindow()); - - get_optgroups().push_back(optgroup); // ogObjectMovers - - m_sizer_object_movers = optgroup->sizer; - m_sizer_object_movers->Show(false); - - m_move_options = Vec3d(0, 0, 0); - m_last_coords = Vec3d(0, 0, 0); - - return optgroup->sizer; -} - -wxBoxSizer* content_settings(wxWindow *win) -{ - DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); - optgroup->label_width = label_width(); - - Option option = optgroup->get_option("extruder"); - option.opt.default_value = new ConfigOptionInt(1); - optgroup->append_single_option_line(option); - - get_optgroups().push_back(optgroup); // ogObjectSettings - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(create_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** - - sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); - - auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); - sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); - - sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20); - - return sizer; -} - -void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) -{ - const auto ol_sizer = create_objects_list(parent); - sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); - set_objects_list_sizer(ol_sizer); -} - -Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) -{ - Line line = { _(option_name), "" }; - if (option_name == "Scale") { - line.near_label_widget = [](wxWindow* parent) { - auto btn = new PrusaLockButton(parent, wxID_ANY); - btn->Bind(wxEVT_BUTTON, [btn](wxCommandEvent &event){ - event.Skip(); - wxTheApp->CallAfter([btn]() { set_uniform_scaling(btn->IsLocked()); }); - }); - return btn; - }; - } - - ConfigOptionDef def; - def.type = coInt; - def.default_value = new ConfigOptionInt(def_value); - def.width = 55; - - if (option_name == "Rotation") - def.min = -360; - - const std::string lower_name = boost::algorithm::to_lower_copy(option_name); - - std::vector axes{ "x", "y", "z" }; - for (auto axis : axes) { - if (axis == "z" && option_name != "Scale") - def.sidetext = sidetext; - Option option = Option(def, lower_name + "_" + axis); - option.opt.full_width = true; - line.append_option(option); - } - - if (option_name == "Scale") - { - def.width = 45; - def.type = coStrings; - def.gui_type = "select_open"; - def.enum_labels.push_back(L("%")); - def.enum_labels.push_back(L("mm")); - def.default_value = new ConfigOptionStrings{ "mm" }; - - const Option option = Option(def, lower_name + "_unit"); - line.append_option(option); - } - - return line; -} - -void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) -{ - auto optgroup = std::make_shared(parent, _(L("Object Settings"))); - optgroup->label_width = 100; - optgroup->set_grid_vgap(5); - - optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ - if (opt_key == "scale_unit"){ - const wxString& selection = boost::any_cast(value); - std::vector axes{ "x", "y", "z" }; - for (auto axis : axes) { - std::string key = "scale_" + axis; - get_optgroup(ogFrequentlyObjectSettings)->set_side_text(key, selection); - } - - g_is_percent_scale = selection == _("%"); - update_scale_values(); - } - }; - - ConfigOptionDef def; - - // Objects(sub-objects) name - def.label = L("Name"); -// def.type = coString; - def.gui_type = "legend"; - def.tooltip = L("Object name"); - def.full_width = true; - def.default_value = new ConfigOptionString{ " " }; - optgroup->append_single_option_line(Option(def, "object_name")); - - - // Legend for object modification - auto line = Line{ "", "" }; - def.label = ""; - def.type = coString; - def.width = 55; - - std::vector axes{ "x", "y", "z" }; - for (const auto axis : axes) { - const auto label = boost::algorithm::to_upper_copy(axis); - def.default_value = new ConfigOptionString{ " "+label }; - Option option = Option(def, axis + "_axis_legend"); - line.append_option(option); - } - optgroup->append_line(line); - - - // Settings table - optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); - optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°")); - optgroup->append_line(add_og_to_object_settings(L("Scale"), "mm")); - - - def.label = L("Place on bed"); - def.type = coBool; - def.tooltip = L("Automatic placing of models on printing bed in Y axis"); - def.gui_type = ""; - def.sidetext = ""; - def.default_value = new ConfigOptionBool{ false }; - optgroup->append_single_option_line(Option(def, "place_on_bed")); - - m_option_sizer = new wxBoxSizer(wxVERTICAL); - optgroup->sizer->Add(m_option_sizer, 1, wxEXPAND | wxLEFT, 5); - - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); - - optgroup->disable(); - - get_optgroups().push_back(optgroup); // ogFrequentlyObjectSettings -} - - -// add Collapsible Pane to sizer -wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) -{ -#ifdef __WXMSW__ - auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name); -#else - auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name); -#endif // __WXMSW__ - // add the pane with a zero proportion value to the sizer which contains it - sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); - - wxWindow *win = collpane->GetPane(); - - wxSizer *sizer = content_function(win); - - wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); - sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); - win->SetSizer(sizer_pane); - // sizer_pane->SetSizeHints(win); - return collpane; -} - -void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) -{ - // *** Objects List *** - auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", create_objects_list); - collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ - // wxWindowUpdateLocker noUpdates(g_right_panel); - if (collpane->IsCollapsed()) { - m_sizer_object_buttons->Show(false); - m_sizer_part_buttons->Show(false); - m_sizer_object_movers->Show(false); - if (!m_objects_ctrl->HasSelection()) - m_collpane_settings->Show(false); - } - })); - - // *** Object/Part Settings *** - m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings); -} - -void show_collpane_settings(bool expert_mode) -{ - m_collpane_settings->Show(expert_mode && !m_objects_model->IsEmpty()); -} - -void add_object_to_list(const std::string &name, ModelObject* model_object) -{ - wxString item_name = name; - auto item = m_objects_model->Add(item_name, model_object->instances.size()); - m_objects_ctrl->Select(item); - - // Add error icon if detected auto-repaire - auto stats = model_object->volumes[0]->mesh.stl.stats; - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - if (errors > 0) { - const PrusaDataViewBitmapText data(item_name, m_icon_manifold_warning); - wxVariant variant; - variant << data; - m_objects_model->SetValue(variant, item, 0); - } - - if (model_object->volumes.size() > 1) { - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddChild(item, - model_object->volumes[id]->name, - m_icon_solidmesh, - model_object->volumes[id]->config.option("extruder")->value, - false); - m_objects_ctrl->Expand(item); - } - -#ifndef __WXOSX__ - object_ctrl_selection_changed(); -#endif //__WXMSW__ -} - -void delete_object_from_list() -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) - return; -// m_objects_ctrl->Select(m_objects_model->Delete(item)); - m_objects_model->Delete(item); - - part_selection_changed(); - -// if (m_objects_model->IsEmpty()) -// m_collpane_settings->Show(false); -} - -void delete_all_objects_from_list() -{ - m_objects_model->DeleteAll(); - - part_selection_changed(); -// m_collpane_settings->Show(false); -} - -void set_object_count(int idx, int count) -{ - m_objects_model->SetValue(wxString::Format("%d", count), idx, 1); - m_objects_ctrl->Refresh(); -} - -void unselect_objects() -{ - if (!m_objects_ctrl->GetSelection()) - return; - - g_prevent_list_events = true; - m_objects_ctrl->UnselectAll(); - part_selection_changed(); - g_prevent_list_events = false; -} - -void select_current_object(int idx) -{ - g_prevent_list_events = true; - m_objects_ctrl->UnselectAll(); - if (idx>=0) - m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); - part_selection_changed(); - g_prevent_list_events = false; -} - -void select_current_volume(int idx, int vol_idx) -{ - if (vol_idx < 0) { - select_current_object(idx); - return; - } - g_prevent_list_events = true; - m_objects_ctrl->UnselectAll(); - if (idx >= 0) - m_objects_ctrl->Select(m_objects_model->GetItemByVolumeId(idx, vol_idx)); - part_selection_changed(); - g_prevent_list_events = false; -} - -void remove() -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item) - return; - - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - if (m_event_remove_object > 0) { - wxCommandEvent event(m_event_remove_object); - get_main_frame()->ProcessWindowEvent(event); - } -// delete_object_from_list(); - } - else - on_btn_del(); -} - -void object_ctrl_selection_changed() -{ - if (g_prevent_list_events) return; - - part_selection_changed(); - - if (m_event_object_selection_changed > 0) { - wxCommandEvent event(m_event_object_selection_changed); - event.SetId(m_selected_object_id); // set $obj_idx - const wxDataViewItem item = m_objects_ctrl->GetSelection(); - if (!item || m_objects_model->GetParent(item) == wxDataViewItem(0)) - event.SetInt(-1); // set $vol_idx - else { - const int vol_idx = m_objects_model->GetVolumeIdByItem(item); - if (vol_idx == -2) // is settings item - event.SetInt(m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item))); // set $vol_idx - else - event.SetInt(vol_idx); - } - get_main_frame()->ProcessWindowEvent(event); - } - -#ifdef __WXOSX__ - update_extruder_in_config(g_selected_extruder); -#endif //__WXOSX__ -} - -void object_ctrl_context_menu() -{ - wxDataViewItem item; - wxDataViewColumn* col; -// printf("object_ctrl_context_menu\n"); - const wxPoint pt = get_mouse_position_in_control(); -// printf("mouse_position_in_control: x = %d, y = %d\n", pt.x, pt.y); - m_objects_ctrl->HitTest(pt, item, col); - if (!item) -#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX - // after Yosemite OS X version, HitTest return undefined item - item = m_objects_ctrl->GetSelection(); - if (item) - show_context_menu(); - else - printf("undefined item\n"); - return; -#else - return; -#endif // __WXOSX__ -// printf("item exists\n"); - const wxString title = col->GetTitle(); -// printf("title = *%s*\n", title.data().AsChar()); - - if (title == " ") - show_context_menu(); -// #ys_FIXME -// else if (title == _("Name") && pt.x >15 && -// m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) -// { -// if (is_windows10()) -// fix_through_netfabb(); -// } -#ifndef __WXMSW__ - m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip -#endif //__WXMSW__ -} - -void object_ctrl_key_event(wxKeyEvent& event) -{ - if (event.GetKeyCode() == WXK_TAB) - m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else if (event.GetKeyCode() == WXK_DELETE -#ifdef __WXOSX__ - || event.GetKeyCode() == WXK_BACK -#endif //__WXOSX__ - ){ - printf("WXK_BACK\n"); - remove(); - } - else - event.Skip(); -} - -void object_ctrl_item_value_change(wxDataViewEvent& event) -{ - if (event.GetColumn() == 2) - { - wxVariant variant; - m_objects_model->GetValue(variant, event.GetItem(), 2); -#ifdef __WXOSX__ - g_selected_extruder = variant.GetString(); -#else // --> for Linux - update_extruder_in_config(variant.GetString()); -#endif //__WXOSX__ - } -} - -void show_manipulation_og(const bool show) -{ - wxGridSizer* grid_sizer = get_optgroup(ogFrequentlyObjectSettings)->get_grid_sizer(); - if (show == grid_sizer->IsShown(2)) - return; - for (size_t id = 2; id < 12; id++) - grid_sizer->Show(id, show); -} - -//update_optgroup -void update_settings_list() -{ -#ifdef __WXGTK__ - auto parent = get_optgroup(ogFrequentlyObjectSettings)->get_parent(); -#else - auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); -#endif /* __WXGTK__ */ - -// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952. -// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, -// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. -#ifdef __linux__ - std::unique_ptr no_updates(new wxWindowUpdateLocker(parent)); -#else - wxWindowUpdateLocker noUpdates(parent); -#endif - - m_option_sizer->Clear(true); - - bool show_manipulations = true; - const auto item = m_objects_ctrl->GetSelection(); - if (m_config && m_objects_model->IsSettingsItem(item)) - { - auto extra_column = [](wxWindow* parent, const Line& line) - { - auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line - - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG), - wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); -#ifdef __WXMSW__ - btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif // __WXMSW__ - btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){ - (*m_config)->erase(opt_key); - wxTheApp->CallAfter([]() { update_settings_list(); }); - }); - return btn; - }; - - std::map> cat_options; - auto opt_keys = (*m_config)->keys(); - m_og_settings.resize(0); - std::vector categories; - if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; - { - auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); - - for (auto& opt_key : opt_keys) { - auto category = (*m_config)->def()->get(opt_key)->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; - - std::vector< std::string > new_category; - - auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); - cat_opt.push_back(opt_key); - if (cat_opt.size() == 1) - cat_options[category] = cat_opt; - } - - for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") - continue; - - auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column); - optgroup->label_width = 150; - optgroup->sidetext_width = 70; - - for (auto& opt : cat.second) - { - if (opt == "extruder") - continue; - Option option = optgroup->get_option(opt); - option.opt.width = 70; - optgroup->append_single_option_line(option); - } - optgroup->reload_config(); - m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); - m_og_settings.push_back(optgroup); - - categories.push_back(cat.first); - } - } - - if (m_og_settings.empty()) { - m_objects_ctrl->Select(m_objects_model->Delete(item)); - part_selection_changed(); - } - else { - if (!categories.empty()) - m_objects_model->UpdateSettingsDigest(item, categories); - show_manipulations = false; - } - } - - show_manipulation_og(show_manipulations); - show_info_sizer(show_manipulations && item && m_objects_model->GetParent(item) == wxDataViewItem(0)); - -#ifdef __linux__ - no_updates.reset(nullptr); -#endif - - parent->Layout(); - get_right_panel()->GetParent()->Layout(); -} - -void get_settings_choice(wxMenu *menu, int id, bool is_part) -{ - const auto category_name = menu->GetLabel(id); - - wxArrayString names; - wxArrayInt selections; - - settings_menu_hierarchy settings_menu; - get_options_menu(settings_menu, is_part); - std::vector< std::pair > *settings_list = nullptr; - - auto opt_keys = (*m_config)->keys(); - - for (auto& cat : settings_menu) - { - if (_(cat.first) == category_name) { - int sel = 0; - for (auto& pair : cat.second) { - names.Add(_(pair.second)); - if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) - selections.Add(sel); - sel++; - } - settings_list = &cat.second; - break; - } - } - - if (!settings_list) - return; - - if (wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names) ==0 ) - return; - - std::vector selected_options; - for (auto sel : selections) - selected_options.push_back((*settings_list)[sel].first); - - for (auto& setting:(*settings_list) ) - { - auto& opt_key = setting.first; - if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) - (*m_config)->erase(opt_key); - - if(find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && - find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) - (*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone()); - } - - - // Add settings item for object - const auto item = m_objects_ctrl->GetSelection(); - if (item) { - const auto settings_item = m_objects_model->HasSettings(item); - m_objects_ctrl->Select(settings_item ? settings_item : - m_objects_model->AddSettingsChild(item)); -#ifndef __WXOSX__ - part_selection_changed(); -#endif //no __WXOSX__ - } - else - update_settings_list(); -} - -void menu_item_add_generic(wxMenuItem* &menu, int id) { - auto sub_menu = new wxMenu; - - std::vector menu_items = { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }; - for (auto& item : menu_items) - sub_menu->Append(new wxMenuItem(sub_menu, ++id, _(item))); - -#ifndef __WXMSW__ - sub_menu->Bind(wxEVT_MENU, [sub_menu](wxEvent &event) { - load_lambda(sub_menu->GetLabel(event.GetId()).ToStdString()); - }); -#endif //no __WXMSW__ - - menu->SetSubMenu(sub_menu); -} - -wxMenuItem* menu_item_split(wxMenu* menu, int id) { - auto menu_item = new wxMenuItem(menu, id, _(L("Split to parts"))); - menu_item->SetBitmap(m_bmp_split); - return menu_item; -} - -wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part) { - auto menu_item = new wxMenuItem(menu, id, _(L("Add settings"))); - menu_item->SetBitmap(m_bmp_cog); - - auto sub_menu = create_add_settings_popupmenu(is_part); - menu_item->SetSubMenu(sub_menu); - return menu_item; -} - -wxMenu *create_add_part_popupmenu() -{ - wxMenu *menu = new wxMenu; - std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") }; - - wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+4+2); - - int i = 0; - for (auto& item : menu_items) { - auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item)); - menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh); - if (item == "Add generic") - menu_item_add_generic(menu_item, config_id_base + i); - menu->Append(menu_item); - i++; - } - - menu->AppendSeparator(); - auto menu_item = menu_item_split(menu, config_id_base + i + 4); - menu->Append(menu_item); - menu_item->Enable(is_splittable_object(false)); - - menu->AppendSeparator(); - // Append settings popupmenu - menu->Append(menu_item_settings(menu, config_id_base + i + 5, false)); - - menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ - switch (event.GetId() - config_id_base) { - case 0: - on_btn_load(); - break; - case 1: - on_btn_load(true); - break; - case 2: -// on_btn_load(true, true); - break; - case 3: - case 4: - case 5: - case 6: -#ifdef __WXMSW__ - load_lambda(menu->GetLabel(event.GetId()).ToStdString()); -#endif // __WXMSW__ - break; - case 7: //3: - on_btn_split(false); - break; - default: -#ifdef __WXMSW__ - get_settings_choice(menu, event.GetId(), false); -#endif // __WXMSW__ - break; - } - }); - - return menu; -} - -wxMenu *create_part_settings_popupmenu() -{ - wxMenu *menu = new wxMenu; - wxWindowID config_id_base = wxWindow::NewControlId(2); - - auto menu_item = menu_item_split(menu, config_id_base); - menu->Append(menu_item); - menu_item->Enable(is_splittable_object(true)); - - menu->AppendSeparator(); - // Append settings popupmenu - menu->Append(menu_item_settings(menu, config_id_base + 1, true)); - - menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ - switch (event.GetId() - config_id_base) { - case 0: - on_btn_split(true); - break; - default:{ - get_settings_choice(menu, event.GetId(), true); - break; } - } - }); - - return menu; -} - -wxMenu *create_add_settings_popupmenu(bool is_part) -{ - wxMenu *menu = new wxMenu; - - auto categories = get_category_icon(); - - settings_menu_hierarchy settings_menu; - get_options_menu(settings_menu, is_part); - - for (auto cat : settings_menu) - { - auto menu_item = new wxMenuItem(menu, wxID_ANY, _(cat.first)); - menu_item->SetBitmap(categories.find(cat.first) == categories.end() ? - wxNullBitmap : categories.at(cat.first)); - menu->Append(menu_item); - } -#ifndef __WXMSW__ - menu->Bind(wxEVT_MENU, [menu,is_part](wxEvent &event) { - get_settings_choice(menu, event.GetId(), is_part); - }); -#endif //no __WXMSW__ - return menu; -} - -void show_context_menu() -{ - const auto item = m_objects_ctrl->GetSelection(); - if (item) - { - if (m_objects_model->IsSettingsItem(item)) - return; - const auto menu = m_objects_model->GetParent(item) == wxDataViewItem(0) ? - create_add_part_popupmenu() : - create_part_settings_popupmenu(); - get_tab_panel()->GetPage(0)->PopupMenu(menu); - } -} - -// ****** - -void load_part( ModelObject* model_object, - wxArrayString& part_names, const bool is_modifier) -{ - wxWindow* parent = get_tab_panel()->GetPage(0); - - wxArrayString input_files; - open_model(parent, input_files); - for (int i = 0; i < input_files.size(); ++i) { - std::string input_file = input_files.Item(i).ToStdString(); - - Model model; - try { - model = Model::read_from_file(input_file); - } - catch (std::exception &e) { - auto msg = _(L("Error! ")) + input_file + " : " + e.what() + "."; - show_error(parent, msg); - exit(1); - } - - for ( auto object : model.objects) { - for (auto volume : object->volumes) { - auto new_volume = model_object->add_volume(*volume); - new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); - boost::filesystem::path(input_file).filename().string(); - new_volume->name = boost::filesystem::path(input_file).filename().string(); - - part_names.Add(new_volume->name); - - // apply the same translation we applied to the object - new_volume->mesh.translate( model_object->origin_translation(0), - model_object->origin_translation(1), - model_object->origin_translation(2) ); - // set a default extruder value, since user can't add it manually - new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); - - m_parts_changed = true; - } - } - } -} - -void load_lambda( ModelObject* model_object, - wxArrayString& part_names, const bool is_modifier) -{ - auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow()); - if (dlg->ShowModal() == wxID_CANCEL) { - return; - } - - std::string name = "lambda-"; - TriangleMesh mesh; - - auto params = dlg->ObjectParameters(); - switch (params.type) - { - case LambdaTypeBox:{ - mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]); - name += "Box"; - break;} - case LambdaTypeCylinder:{ - mesh = make_cylinder(params.cyl_r, params.cyl_h); - name += "Cylinder"; - break;} - case LambdaTypeSphere:{ - mesh = make_sphere(params.sph_rho); - name += "Sphere"; - break;} - case LambdaTypeSlab:{ - const auto& size = model_object->bounding_box().size(); - mesh = make_cube(size(0)*1.5, size(1)*1.5, params.slab_h); - // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z - mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, params.slab_z); - name += "Slab"; - break; } - default: - break; - } - mesh.repair(); - - auto new_volume = model_object->add_volume(mesh); - new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); - - new_volume->name = name; - // set a default extruder value, since user can't add it manually - new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); - - part_names.Add(name); - - m_parts_changed = true; -} - -void load_lambda(const std::string& type_name) -{ - if (m_selected_object_id < 0) return; - - auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow(), type_name); - if (dlg->ShowModal() == wxID_CANCEL) - return; - - const std::string name = "lambda-"+type_name; - TriangleMesh mesh; - - const auto params = dlg->ObjectParameters(); - if (type_name == _("Box")) - mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]); - else if (type_name == _("Cylinder")) - mesh = make_cylinder(params.cyl_r, params.cyl_h); - else if (type_name == _("Sphere")) - mesh = make_sphere(params.sph_rho); - else if (type_name == _("Slab")){ - const auto& size = (*m_objects)[m_selected_object_id]->bounding_box().size(); - mesh = make_cube(size(0)*1.5, size(1)*1.5, params.slab_h); - // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z - mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, params.slab_z); - } - mesh.repair(); - - auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh); - new_volume->set_type(ModelVolume::PARAMETER_MODIFIER); - - new_volume->name = name; - // set a default extruder value, since user can't add it manually - new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); - - m_parts_changed = true; - parts_changed(m_selected_object_id); - - m_objects_ctrl->Select(m_objects_model->AddChild(m_objects_ctrl->GetSelection(), - name, m_icon_modifiermesh)); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME - object_ctrl_selection_changed(); -#endif //no __WXOSX__ //__WXMSW__ -} - -void on_btn_load(bool is_modifier /*= false*/, bool is_lambda/* = false*/) -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item) - return; - int obj_idx = -1; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) - obj_idx = m_objects_model->GetIdByItem(item); - else - return; - - if (obj_idx < 0) return; - wxArrayString part_names; - if (is_lambda) - load_lambda((*m_objects)[obj_idx], part_names, is_modifier); - else - load_part((*m_objects)[obj_idx], part_names, is_modifier); - - parts_changed(obj_idx); - - for (int i = 0; i < part_names.size(); ++i) - m_objects_ctrl->Select( m_objects_model->AddChild(item, part_names.Item(i), - is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME - object_ctrl_selection_changed(); -#endif //no __WXOSX__//__WXMSW__ -} - -void remove_settings_from_config() -{ - auto opt_keys = (*m_config)->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") - return; - int extruder = -1; - if ((*m_config)->has("extruder")) - extruder = (*m_config)->option("extruder")->value; - - (*m_config)->clear(); - - if (extruder >=0 ) - (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); -} - -bool remove_subobject_from_object(const int volume_id) -{ - const auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - - // if user is deleting the last solid part, throw error - int solid_cnt = 0; - for (auto vol : (*m_objects)[m_selected_object_id]->volumes) - if (vol->is_model_part()) - ++solid_cnt; - if (volume->is_model_part() && solid_cnt == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); - return false; - } - - (*m_objects)[m_selected_object_id]->delete_volume(volume_id); - m_parts_changed = true; - - parts_changed(m_selected_object_id); - return true; -} - -void on_btn_del() -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - - const auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id ==-1) - return; - - if (volume_id ==-2) - remove_settings_from_config(); - else if (!remove_subobject_from_object(volume_id)) - return; - - m_objects_ctrl->Select(m_objects_model->Delete(item)); - part_selection_changed(); -} - -bool get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume) -{ - if (!item || m_selected_object_id < 0) - return false; - const auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0) { - if (split_part) return false; - volume = (*m_objects)[m_selected_object_id]->volumes[0]; - } - else - volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - if (volume) - return true; - return false; -} - -bool is_splittable_object(const bool split_part) -{ - const wxDataViewItem item = m_objects_ctrl->GetSelection(); - if (!item) return false; - - wxDataViewItemArray children; - if (!split_part && m_objects_model->GetChildren(item, children) > 0) - return false; - - ModelVolume* volume; - if (!get_volume_by_item(split_part, item, volume) || !volume) - return false; - - TriangleMeshPtrs meshptrs = volume->mesh.split(); - if (meshptrs.size() <= 1) { - delete meshptrs.front(); - return false; - } - - return true; -} - -void on_btn_split(const bool split_part) -{ - const auto item = m_objects_ctrl->GetSelection(); - if (!item || m_selected_object_id<0) - return; - ModelVolume* volume; - if (!get_volume_by_item(split_part, item, volume)) return; - DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; - const auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); - if (volume->split(nozzle_dmrs_cnt) == 1) { - wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); - return; - } - - auto model_object = (*m_objects)[m_selected_object_id]; - - if (split_part) { - auto parent = m_objects_model->GetParent(item); - m_objects_model->DeleteChildren(parent); - - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddChild(parent, model_object->volumes[id]->name, - model_object->volumes[id]->is_modifier() ? m_icon_modifiermesh : m_icon_solidmesh, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, - false); - - m_objects_ctrl->Expand(parent); - } - else { - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddChild(item, model_object->volumes[id]->name, - m_icon_solidmesh, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, - false); - m_objects_ctrl->Expand(item); - } - - m_parts_changed = true; - parts_changed(m_selected_object_id); -} - -void on_btn_move_up(){ - auto item = m_objects_ctrl->GetSelection(); - if (!item) - return; - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0) - return; - auto& volumes = (*m_objects)[m_selected_object_id]->volumes; - if (0 < volume_id && volume_id < volumes.size()) { - std::swap(volumes[volume_id - 1], volumes[volume_id]); - m_parts_changed = true; - m_objects_ctrl->Select(m_objects_model->MoveChildUp(item)); - part_selection_changed(); -// #ifdef __WXMSW__ -// object_ctrl_selection_changed(); -// #endif //__WXMSW__ - } -} - -void on_btn_move_down(){ - auto item = m_objects_ctrl->GetSelection(); - if (!item) - return; - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0) - return; - auto& volumes = (*m_objects)[m_selected_object_id]->volumes; - if (0 <= volume_id && volume_id+1 < volumes.size()) { - std::swap(volumes[volume_id + 1], volumes[volume_id]); - m_parts_changed = true; - m_objects_ctrl->Select(m_objects_model->MoveChildDown(item)); - part_selection_changed(); -// #ifdef __WXMSW__ -// object_ctrl_selection_changed(); -// #endif //__WXMSW__ - } -} - -void parts_changed(int obj_idx) -{ - if (m_event_object_settings_changed <= 0) return; - - wxCommandEvent e(m_event_object_settings_changed); - auto event_str = wxString::Format("%d %d %d", obj_idx, - is_parts_changed() ? 1 : 0, - is_part_settings_changed() ? 1 : 0); - e.SetString(event_str); - get_main_frame()->ProcessWindowEvent(e); -} - -void update_settings_value() -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { - og->set_value("position_x", 0); - og->set_value("position_y", 0); - og->set_value("position_z", 0); - og->set_value("scale_x", 0); - og->set_value("scale_y", 0); - og->set_value("scale_z", 0); - og->set_value("rotation_x", 0); - og->set_value("rotation_y", 0); - og->set_value("rotation_z", 0); - og->disable(); - return; - } - g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%"); - update_position_values(); - update_scale_values(); - update_rotation_values(); - og->enable(); -} - -void part_selection_changed() -{ - auto item = m_objects_ctrl->GetSelection(); - int obj_idx = -1; - auto og = get_optgroup(ogFrequentlyObjectSettings); - m_config = nullptr; - wxString object_name = wxEmptyString; - if (item) - { - const bool is_settings_item = m_objects_model->IsSettingsItem(item); - bool is_part = false; - wxString og_name = wxEmptyString; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - obj_idx = m_objects_model->GetIdByItem(item); - og_name = _(L("Object manipulation")); - m_config = std::make_shared(&(*m_objects)[obj_idx]->config); - } - else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); - if (is_settings_item) { - if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { - og_name = _(L("Object Settings to modify")); - m_config = std::make_shared(&(*m_objects)[obj_idx]->config); - } - else { - og_name = _(L("Part Settings to modify")); - is_part = true; - auto main_parent = m_objects_model->GetParent(parent); - obj_idx = m_objects_model->GetIdByItem(main_parent); - const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); - m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); - } - } - else { - og_name = _(L("Part manipulation")); - is_part = true; - const auto volume_id = m_objects_model->GetVolumeIdByItem(item); - m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); - } - } - - og->set_name(" " + og_name + " "); - object_name = m_objects_model->GetName(item); - m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); - } - og->set_value("object_name", object_name); - - update_settings_list(); - - m_selected_object_id = obj_idx; - - update_settings_value(); - -/* wxWindowUpdateLocker noUpdates(get_right_panel()); - - m_move_options = Point3(0, 0, 0); - m_last_coords = Point3(0, 0, 0); - // reset move sliders - std::vector opt_keys = {"x", "y", "z"}; - auto og = get_optgroup(ogObjectMovers); - for (auto opt_key: opt_keys) - og->set_value(opt_key, int(0)); - -// if (!item || m_selected_object_id < 0){ - if (m_selected_object_id < 0){ - m_sizer_object_buttons->Show(false); - m_sizer_part_buttons->Show(false); - m_sizer_object_movers->Show(false); - m_collpane_settings->Show(false); - return; - } - - m_collpane_settings->Show(true); - - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0){ - m_sizer_object_buttons->Show(true); - m_sizer_part_buttons->Show(false); - m_sizer_object_movers->Show(false); - m_collpane_settings->SetLabelText(_(L("Object Settings")) + ":"); - -// elsif($itemData->{type} eq 'object') { -// # select nothing in 3D preview -// -// # attach object config to settings panel -// $self->{optgroup_movers}->disable; -// $self->{staticbox}->SetLabel('Object Settings'); -// @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new); -// $config = $self->{model_object}->config; -// } - - return; - } - - m_collpane_settings->SetLabelText(_(L("Part Settings")) + ":"); - - m_sizer_object_buttons->Show(false); - m_sizer_part_buttons->Show(true); - m_sizer_object_movers->Show(true); - - auto bb_size = m_objects[m_selected_object_id]->bounding_box().size(); - int scale = 10; //?? - - m_mover_x->SetMin(-bb_size.x * 4 * scale); - m_mover_x->SetMax(bb_size.x * 4 * scale); - - m_mover_y->SetMin(-bb_size.y * 4 * scale); - m_mover_y->SetMax(bb_size.y * 4 * scale); - - m_mover_z->SetMin(-bb_size.z * 4 * scale); - m_mover_z->SetMax(bb_size.z * 4 * scale); - - - -// my ($config, @opt_keys); - m_btn_move_up->Enable(volume_id > 0); - m_btn_move_down->Enable(volume_id + 1 < m_objects[m_selected_object_id]->volumes.size()); - - // attach volume config to settings panel - auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; - - if (volume->modifier) - og->enable(); - else - og->disable(); - -// auto config = volume->config; - - // get default values -// @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; -// } -/* - # get default values - my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); - - # append default extruder - push @opt_keys, 'extruder'; - $default_config->set('extruder', 0); - $config->set_ifndef('extruder', 0); - $self->{settings_panel}->set_default_config($default_config); - $self->{settings_panel}->set_config($config); - $self->{settings_panel}->set_opt_keys(\@opt_keys); - $self->{settings_panel}->set_fixed_options([qw(extruder)]); - $self->{settings_panel}->enable; - } - */ -} - -void set_extruder_column_hidden(bool hide) -{ - m_objects_ctrl->GetColumn(2)->SetHidden(hide); -} - -void update_extruder_in_config(const wxString& selection) -{ - if (!m_config || selection.empty()) - return; - - int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); - (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); - - if (m_event_update_scene > 0) { - wxCommandEvent e(m_event_update_scene); - get_main_frame()->ProcessWindowEvent(e); - } -} - -void update_scale_values() -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - auto instance = (*m_objects)[m_selected_object_id]->instances.front(); - auto size = (*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(); - - if (g_is_percent_scale) { - auto scale = instance->scaling_factor * 100.0; - og->set_value("scale_x", int(scale)); - og->set_value("scale_y", int(scale)); - og->set_value("scale_z", int(scale)); - } - else { - og->set_value("scale_x", int(instance->scaling_factor * size(0) + 0.5)); - og->set_value("scale_y", int(instance->scaling_factor * size(1) + 0.5)); - og->set_value("scale_z", int(instance->scaling_factor * size(2) + 0.5)); - } -} - -void update_position_values() -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - auto instance = (*m_objects)[m_selected_object_id]->instances.front(); - -#if ENABLE_MODELINSTANCE_3D_OFFSET - og->set_value("position_x", int(instance->get_offset(X))); - og->set_value("position_y", int(instance->get_offset(Y))); - og->set_value("position_z", int(instance->get_offset(Z))); -#else - og->set_value("position_x", int(instance->offset(0))); - og->set_value("position_y", int(instance->offset(1))); - og->set_value("position_z", 0); -#endif // ENABLE_MODELINSTANCE_3D_OFFSET -} - -void update_position_values(const Vec3d& position) -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - - og->set_value("position_x", int(position(0))); - og->set_value("position_y", int(position(1))); - og->set_value("position_z", int(position(2))); -} - -void update_scale_values(double scaling_factor) -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - - // this is temporary - // to be able to update the values as size - // we need to store somewhere the original size - // or have it passed as parameter - if (!g_is_percent_scale) - og->set_value("scale_unit", _("%")); - - auto scale = scaling_factor * 100.0; - og->set_value("scale_x", int(scale)); - og->set_value("scale_y", int(scale)); - og->set_value("scale_z", int(scale)); -} - -void update_rotation_values() -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - auto instance = (*m_objects)[m_selected_object_id]->instances.front(); - og->set_value("rotation_x", 0); - og->set_value("rotation_y", 0); - og->set_value("rotation_z", int(Geometry::rad2deg(instance->rotation))); -} - -void update_rotation_value(double angle, Axis axis) -{ - auto og = get_optgroup(ogFrequentlyObjectSettings); - - std::string axis_str; - switch (axis) - { - case X: - { - axis_str = "rotation_x"; - break; - } - case Y: - { - axis_str = "rotation_y"; - break; - } - case Z: - { - axis_str = "rotation_z"; - break; - } - } - - og->set_value(axis_str, int(Geometry::rad2deg(angle))); -} - -void set_uniform_scaling(const bool uniform_scale) -{ - g_is_uniform_scale = uniform_scale; -} - -void on_begin_drag(wxDataViewEvent &event) -{ - wxDataViewItem item(event.GetItem()); - - // only allow drags for item, not containers - if (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->IsSettingsItem(item)) { - event.Veto(); - return; - } - - /* Under MSW or OSX, DnD moves an item to the place of another selected item - * But under GTK, DnD moves an item between another two items. - * And as a result - call EVT_CHANGE_SELECTION to unselect all items. - * To prevent such behavior use g_prevent_list_events - **/ - g_prevent_list_events = true;//it's needed for GTK - - wxTextDataObject *obj = new wxTextDataObject; - obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); - event.SetDataObject(obj); - event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; -} - -void on_drop_possible(wxDataViewEvent &event) -{ - wxDataViewItem item(event.GetItem()); - - // only allow drags for item or background, not containers - if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item)) - event.Veto(); -} - -void on_drop(wxDataViewEvent &event) -{ - wxDataViewItem item(event.GetItem()); - - // only allow drops for item, not containers - if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item)) { - event.Veto(); - return; - } - - wxTextDataObject obj; - obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); - - int from_volume_id = std::stoi(obj.GetText().ToStdString()); - int to_volume_id = m_objects_model->GetVolumeIdByItem(item); - -#ifdef __WXGTK__ - /* Under GTK, DnD moves an item between another two items. - * And event.GetItem() return item, which is under "insertion line" - * So, if we move item down we should to decrease the to_volume_id value - **/ - if (to_volume_id > from_volume_id) to_volume_id--; -#endif // __WXGTK__ - - m_objects_ctrl->Select(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, - m_objects_model->GetParent(item))); - - auto& volumes = (*m_objects)[m_selected_object_id]->volumes; - auto delta = to_volume_id < from_volume_id ? -1 : 1; - int cnt = 0; - for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++) - std::swap(volumes[id], volumes[id +delta]); - - m_parts_changed = true; - parts_changed(m_selected_object_id); - - g_prevent_list_events = false; -} - -void update_objects_list_extruder_column(int extruders_count) -{ - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) - extruders_count = 1; - - // delete old 3rd column - m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumn(2)); - // insert new created 3rd column - m_objects_ctrl->InsertColumn(2, object_ctrl_create_extruder_column(extruders_count)); - // set show/hide for this column - set_extruder_column_hidden(extruders_count <= 1); -} - -void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas) -{ - m_slider = new PrusaDoubleSlider(parent, wxID_ANY, 0, 0, 0, 100); - sizer->Add(m_slider, 0, wxEXPAND, 0); - - m_preview_canvas = canvas; - m_preview_canvas->Bind(wxEVT_KEY_DOWN, update_double_slider_from_canvas); - - m_slider->Bind(wxEVT_SCROLL_CHANGED, [parent](wxEvent& event) { - _3DScene::set_toolpaths_range(m_preview_canvas, m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); - if (parent->IsShown()) - m_preview_canvas->Refresh(); - }); -} - -void fill_slider_values(std::vector> &values, - const std::vector &layers_z) -{ - std::vector layers_all_z = _3DScene::get_current_print_zs(m_preview_canvas, false); - if (layers_all_z.size() == layers_z.size()) - for (int i = 0; i < layers_z.size(); i++) - values.push_back(std::pair(i+1, layers_z[i])); - else if (layers_all_z.size() > layers_z.size()) { - int cur_id = 0; - for (int i = 0; i < layers_z.size(); i++) - for (int j = cur_id; j < layers_all_z.size(); j++) - if (layers_z[i] - 1e-6 < layers_all_z[j] && layers_all_z[j] < layers_z[i] + 1e-6) { - values.push_back(std::pair(j+1, layers_z[i])); - cur_id = j; - break; - } - } -} - -void set_double_slider_thumbs( const bool force_sliders_full_range, - const std::vector &layers_z, - const double z_low, const double z_high) -{ - // Force slider full range only when slider is created. - // Support selected diapason on the all next steps - if (/*force_sliders_full_range*/z_high == 0.0) { - m_slider->SetLowerValue(0); - m_slider->SetHigherValue(layers_z.size() - 1); - return; - } - - for (int i = layers_z.size() - 1; i >= 0; i--) - if (z_low >= layers_z[i]) { - m_slider->SetLowerValue(i); - break; - } - for (int i = layers_z.size() - 1; i >= 0 ; i--) - if (z_high >= layers_z[i]) { - m_slider->SetHigherValue(i); - break; - } -} - -void update_double_slider(bool force_sliders_full_range) -{ - std::vector> values; - std::vector layers_z = _3DScene::get_current_print_zs(m_preview_canvas, true); - fill_slider_values(values, layers_z); - - const double z_low = m_slider->GetLowerValueD(); - const double z_high = m_slider->GetHigherValueD(); - m_slider->SetMaxValue(layers_z.size() - 1); - m_slider->SetSliderValues(values); - - set_double_slider_thumbs(force_sliders_full_range, layers_z, z_low, z_high); -} - -void reset_double_slider() -{ - m_slider->SetHigherValue(0); - m_slider->SetLowerValue(0); -} - -void update_double_slider_from_canvas(wxKeyEvent& event) -{ - if (event.HasModifiers()) { - event.Skip(); - return; - } - - const auto key = event.GetKeyCode(); - - if (key == 'U' || key == 'D') { - const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1; - m_slider->SetHigherValue(new_pos); - if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue()); - } - else if (key == 'S') - m_slider->ChangeOneLayerLock(); - else - event.Skip(); -} - -void show_manipulation_sizer(const bool is_simple_mode) -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item || !is_simple_mode) - return; - - if (m_objects_model->IsSettingsItem(item)) { - m_objects_ctrl->Select(m_objects_model->GetParent(item)); - part_selection_changed(); - } -} - -} //namespace GUI -} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectParts.hpp b/src/slic3r/GUI/GUI_ObjectParts.hpp deleted file mode 100644 index e66b4d1db..000000000 --- a/src/slic3r/GUI/GUI_ObjectParts.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef slic3r_GUI_ObjectParts_hpp_ -#define slic3r_GUI_ObjectParts_hpp_ - -class wxWindow; -class wxSizer; -class wxBoxSizer; -class wxString; -class wxArrayString; -class wxMenu; -class wxDataViewEvent; -class wxKeyEvent; -class wxGLCanvas; -class wxBitmap; - -namespace Slic3r { -class ModelObject; -class Model; - -namespace GUI { -//class wxGLCanvas; - -enum ogGroup{ - ogFrequentlyChangingParameters, - ogFrequentlyObjectSettings, - ogCurrentSettings -// ogObjectSettings, -// ogObjectMovers, -// ogPartSettings -}; - -enum LambdaTypeIDs{ - LambdaTypeBox, - LambdaTypeCylinder, - LambdaTypeSphere, - LambdaTypeSlab -}; - -struct OBJECT_PARAMETERS -{ - LambdaTypeIDs type = LambdaTypeBox; - double dim[3];// = { 1.0, 1.0, 1.0 }; - int cyl_r = 1; - int cyl_h = 1; - double sph_rho = 1.0; - double slab_h = 1.0; - double slab_z = 0.0; -}; - -typedef std::map t_category_icon; -inline t_category_icon& get_category_icon(); - -void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); -void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); -void add_object_settings(wxWindow* parent, wxBoxSizer* sizer); -void show_collpane_settings(bool expert_mode); - -wxMenu *create_add_settings_popupmenu(bool is_part); -wxMenu *create_add_part_popupmenu(); -wxMenu *create_part_settings_popupmenu(); - -// Add object to the list -//void add_object(const std::string &name); -void add_object_to_list(const std::string &name, ModelObject* model_object); -// Delete object from the list -void delete_object_from_list(); -// Delete all objects from the list -void delete_all_objects_from_list(); -// Set count of object on c++ side -void set_object_count(int idx, int count); -// Unselect all objects in the list on c++ side -void unselect_objects(); -// Select current object in the list on c++ side -void select_current_object(int idx); -// Select current volume in the list on c++ side -void select_current_volume(int idx, int vol_idx); -// Remove objects/sub-object from the list -void remove(); - -void object_ctrl_selection_changed(); -void object_ctrl_context_menu(); -void object_ctrl_key_event(wxKeyEvent& event); -void object_ctrl_item_value_change(wxDataViewEvent& event); -void show_context_menu(); -bool is_splittable_object(const bool split_part); - -void init_mesh_icons(); -void set_event_object_selection_changed(const int& event); -void set_event_object_settings_changed(const int& event); -void set_event_remove_object(const int& event); -void set_event_update_scene(const int& event); -void set_objects_from_model(Model &model); - -bool is_parts_changed(); -bool is_part_settings_changed(); - -void load_part( ModelObject* model_object, - wxArrayString& part_names, const bool is_modifier); - -void load_lambda( ModelObject* model_object, - wxArrayString& part_names, const bool is_modifier); -void load_lambda( const std::string& type_name); - -void on_btn_load(bool is_modifier = false, bool is_lambda = false); -void on_btn_del(); -void on_btn_split(const bool split_part); -void on_btn_move_up(); -void on_btn_move_down(); - -void parts_changed(int obj_idx); -void part_selection_changed(); - -void update_settings_value(); -// show/hide "Extruder" column for Objects List -void set_extruder_column_hidden(bool hide); -// update extruder in current config -void update_extruder_in_config(const wxString& selection); -// update position values displacements or "gizmos" -void update_position_values(); -void update_position_values(const Vec3d& position); -// update scale values after scale unit changing or "gizmos" -void update_scale_values(); -void update_scale_values(double scaling_factor); -// update rotation values object selection changing -void update_rotation_values(); -// update rotation value after "gizmos" -void update_rotation_value(double angle, Axis axis); -void set_uniform_scaling(const bool uniform_scale); - -void on_begin_drag(wxDataViewEvent &event); -void on_drop_possible(wxDataViewEvent &event); -void on_drop(wxDataViewEvent &event); - -// update extruder column for objects_ctrl according to extruders count -void update_objects_list_extruder_column(int extruders_count); - -// Create/Update/Reset double slider on 3dPreview -void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas); -void update_double_slider(bool force_sliders_full_range); -void reset_double_slider(); -// update DoubleSlider after keyDown in canvas -void update_double_slider_from_canvas(wxKeyEvent& event); - -void show_manipulation_sizer(const bool is_simple_mode); - -} //namespace GUI -} //namespace Slic3r -#endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp new file mode 100644 index 000000000..477b0530b --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -0,0 +1,163 @@ +#include "GUI_ObjectSettings.hpp" +#include "GUI_ObjectList.hpp" + +#include "OptionsGroup.hpp" +#include "wxExtensions.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" + +#include + +#include "I18N.hpp" + +namespace Slic3r +{ +namespace GUI +{ + +OG_Settings::OG_Settings(wxWindow* parent, const bool staticbox) : + m_parent(parent) +{ + wxString title = staticbox ? " " : ""; // temporary workaround - #ys_FIXME + m_og = std::make_shared(parent, title); +} + +bool OG_Settings::IsShown() +{ + return m_og->sizer->IsEmpty() ? false : m_og->sizer->IsShown(size_t(0)); +} + +void OG_Settings::Show(const bool show) +{ + m_og->Show(show); +} + +void OG_Settings::Hide() +{ + Show(false); +} + +void OG_Settings::UpdateAndShow(const bool show) +{ + Show(show); +// m_parent->Layout(); +} + +wxSizer* OG_Settings::get_sizer() +{ + return m_og->sizer; +} + + + +ObjectSettings::ObjectSettings(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_og->set_name(_(L("Additional Settings"))); + + m_settings_list_sizer = new wxBoxSizer(wxVERTICAL); + m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5); +} + +void ObjectSettings::update_settings_list() +{ + m_settings_list_sizer->Clear(true); + + auto objects_ctrl = wxGetApp().obj_list(); + auto objects_model = wxGetApp().obj_list()->m_objects_model; + auto config = wxGetApp().obj_list()->m_config; + + const auto item = objects_ctrl->GetSelection(); + if (item && !objects_ctrl->multiple_selection() && + config && objects_model->IsSettingsItem(item)) + { + auto extra_column = [config, this](wxWindow* parent, const Line& line) + { + auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line + + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); +#ifdef __WXMSW__ + btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // __WXMSW__ + btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { + config->erase(opt_key); + wxTheApp->CallAfter([this]() { + update_settings_list(); + m_parent->Layout(); + }); + }); + return btn; + }; + + std::map> cat_options; + auto opt_keys = config->keys(); + objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology + + m_og_settings.resize(0); + std::vector categories; + if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; + { + auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + + for (auto& opt_key : opt_keys) { + auto category = config->def()->get(opt_key)->category; + if (category.empty() || + (category == "Extruders" && extruders_cnt == 1)) continue; + + std::vector< std::string > new_category; + + auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + cat_options[category] = cat_opt; + } + + for (auto& cat : cat_options) { + if (cat.second.size() == 1 && cat.second[0] == "extruder") + continue; + + auto optgroup = std::make_shared(m_parent, cat.first, config, false, extra_column); + optgroup->label_width = 150; + optgroup->sidetext_width = 70; + + optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { + wxGetApp().obj_list()->part_settings_changed(); }; + + for (auto& opt : cat.second) + { + if (opt == "extruder") + continue; + Option option = optgroup->get_option(opt); + option.opt.width = 70; + optgroup->append_single_option_line(option); + } + optgroup->reload_config(); + m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); + + categories.push_back(cat.first); + } + } + + if (m_og_settings.empty()) { + objects_ctrl->select_item(objects_model->Delete(item)); + } + else { + if (!categories.empty()) + objects_model->UpdateSettingsDigest(item, categories); + } + } +} + +void ObjectSettings::UpdateAndShow(const bool show) +{ + if (show) + update_settings_list(); + + OG_Settings::UpdateAndShow(show); +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp new file mode 100644 index 000000000..19140efe3 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -0,0 +1,49 @@ +#ifndef slic3r_GUI_ObjectSettings_hpp_ +#define slic3r_GUI_ObjectSettings_hpp_ + +#include +#include + +class wxBoxSizer; + +namespace Slic3r { +namespace GUI { +class ConfigOptionsGroup; + +class OG_Settings +{ +protected: + std::shared_ptr m_og; + wxWindow* m_parent; +public: + OG_Settings(wxWindow* parent, const bool staticbox); + virtual ~OG_Settings() {} + + virtual bool IsShown(); + virtual void Show(const bool show); + virtual void Hide(); + virtual void UpdateAndShow(const bool show); + + wxSizer* get_sizer(); + ConfigOptionsGroup* get_og() { return m_og.get(); } +}; + + +class ObjectSettings : public OG_Settings +{ + // sizer for extra Object/Part's settings + wxBoxSizer* m_settings_list_sizer{ nullptr }; + // option groups for settings + std::vector > m_og_settings; + +public: + ObjectSettings(wxWindow* parent); + ~ObjectSettings() {} + + void update_settings_list(); + void UpdateAndShow(const bool show) override; +}; + +}} + +#endif // slic3r_GUI_ObjectSettings_hpp_ diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp new file mode 100644 index 000000000..4e14aefe8 --- /dev/null +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -0,0 +1,889 @@ +#include "libslic3r/libslic3r.h" +#include "libslic3r/GCode/PreviewData.hpp" +#include "GUI_Preview.hpp" +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "AppConfig.hpp" +#include "3DScene.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "GLCanvas3DManager.hpp" +#include "GLCanvas3D.hpp" +#include "PresetBundle.hpp" +#include "wxExtensions.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421 +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { +namespace GUI { + +#if ENABLE_REMOVE_TABS_FROM_PLATER + View3D::View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) + : m_canvas_widget(nullptr) + , m_canvas(nullptr) +#if !ENABLE_IMGUI + , m_gizmo_widget(nullptr) +#endif // !ENABLE_IMGUI + , m_model(nullptr) + , m_config(nullptr) + , m_process(nullptr) +{ + init(parent, model, config, process); +} + +View3D::~View3D() +{ + if (m_canvas_widget != nullptr) + { + _3DScene::remove_canvas(m_canvas_widget); + delete m_canvas_widget; + m_canvas = nullptr; + } +} + +bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +{ + if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)) + return false; + + m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); + _3DScene::add_canvas(m_canvas_widget); + m_canvas = _3DScene::get_canvas(this->m_canvas_widget); + + m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); + // XXX: If have OpenGL + m_canvas->enable_picking(true); + m_canvas->enable_moving(true); + // XXX: more config from 3D.pm + m_canvas->set_model(model); + m_canvas->set_process(process); + m_canvas->set_config(config); + m_canvas->enable_gizmos(true); + m_canvas->enable_toolbar(true); + m_canvas->enable_shader(true); + m_canvas->enable_force_zoom_to_bed(true); + +#if !ENABLE_IMGUI + m_gizmo_widget = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + m_gizmo_widget->SetSizer(new wxBoxSizer(wxVERTICAL)); + m_canvas->set_external_gizmo_widgets_parent(m_gizmo_widget); +#endif // !ENABLE_IMGUI + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); +#if !ENABLE_IMGUI + main_sizer->Add(m_gizmo_widget, 0, wxALL | wxEXPAND, 0); +#endif // !ENABLE_IMGUI + + SetSizer(main_sizer); + SetMinSize(GetSize()); + GetSizer()->SetSizeHints(this); + + return true; +} + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void View3D::set_view_toolbar(GLToolbar* toolbar) +#else +void View3D::set_view_toolbar(GLRadioToolbar* toolbar) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +{ + if (m_canvas != nullptr) + m_canvas->set_view_toolbar(toolbar); +} + +void View3D::set_as_dirty() +{ + if (m_canvas != nullptr) + m_canvas->set_as_dirty(); +} + +void View3D::set_bed_shape(const Pointfs& shape) +{ + if (m_canvas != nullptr) + { + m_canvas->set_bed_shape(shape); + m_canvas->zoom_to_bed(); + } +} + +void View3D::select_view(const std::string& direction) +{ + if (m_canvas != nullptr) + m_canvas->select_view(direction); +} + +void View3D::select_all() +{ + if (m_canvas != nullptr) + m_canvas->select_all(); +} + +void View3D::delete_selected() +{ + if (m_canvas != nullptr) + m_canvas->delete_selected(); +} + +void View3D::mirror_selection(Axis axis) +{ + if (m_canvas != nullptr) + m_canvas->mirror_selection(axis); +} + +void View3D::enable_toolbar_item(const std::string& name, bool enable) +{ + if (m_canvas != nullptr) + m_canvas->enable_toolbar_item(name, enable); +} + +int View3D::check_volumes_outside_state() const +{ + return (m_canvas != nullptr) ? m_canvas->check_volumes_outside_state() : false; +} + +bool View3D::is_layers_editing_enabled() const +{ + return (m_canvas != nullptr) ? m_canvas->is_layers_editing_enabled() : false; +} + +bool View3D::is_layers_editing_allowed() const +{ + return (m_canvas != nullptr) ? m_canvas->is_layers_editing_allowed() : false; +} + +void View3D::enable_layers_editing(bool enable) +{ + if (m_canvas != nullptr) + m_canvas->enable_layers_editing(enable); +} + +bool View3D::is_dragging() const +{ + return (m_canvas != nullptr) ? m_canvas->is_dragging() : false; +} + +bool View3D::is_reload_delayed() const +{ + return (m_canvas != nullptr) ? m_canvas->is_reload_delayed() : false; +} + +void View3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh) +{ + if (m_canvas != nullptr) + m_canvas->reload_scene(refresh_immediately, force_full_scene_refresh); +} + +void View3D::render() +{ + if (m_canvas != nullptr) + m_canvas->render(); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +#if ENABLE_REMOVE_TABS_FROM_PLATER +Preview::Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) +#else +Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + : m_canvas_widget(nullptr) + , m_canvas(nullptr) + , m_double_slider_sizer(nullptr) + , m_label_view_type(nullptr) + , m_choice_view_type(nullptr) + , m_label_show_features(nullptr) + , m_combochecklist_features(nullptr) + , m_checkbox_travel(nullptr) + , m_checkbox_retractions(nullptr) + , m_checkbox_unretractions(nullptr) + , m_checkbox_shells(nullptr) + , m_config(config) + , m_process(process) + , m_gcode_preview_data(gcode_preview_data) + , m_number_extruders(1) + , m_preferred_color_mode("feature") + , m_loaded(false) + , m_enabled(false) + , m_force_sliders_full_range(false) + , m_schedule_background_process(schedule_background_process_func) +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (init(parent, config, process, gcode_preview_data)) + { + show_hide_ui_elements("none"); + load_print(); + } +#else + if (init(notebook, config, process, gcode_preview_data)) + { + notebook->AddPage(this, _(L("Preview"))); + show_hide_ui_elements("none"); + load_print(); + } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +bool Preview::init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) +#else +bool Preview::init(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) + return false; + + if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)) + return false; +#else + if ((notebook == nullptr) || (config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) + return false; + + // creates this panel add append it to the given notebook as a new page + if (!Create(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize)) + return false; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); + _3DScene::add_canvas(m_canvas_widget); + m_canvas = _3DScene::get_canvas(this->m_canvas_widget); + m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); + m_canvas->enable_shader(true); + m_canvas->set_config(m_config); + m_canvas->set_process(process); + m_canvas->enable_legend_texture(true); + m_canvas->enable_dynamic_background(true); + + m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); + create_double_slider(); + + m_label_view_type = new wxStaticText(this, wxID_ANY, _(L("View"))); + + m_choice_view_type = new wxChoice(this, wxID_ANY); + m_choice_view_type->Append(_(L("Feature type"))); + m_choice_view_type->Append(_(L("Height"))); + m_choice_view_type->Append(_(L("Width"))); + m_choice_view_type->Append(_(L("Speed"))); + m_choice_view_type->Append(_(L("Volumetric flow rate"))); + m_choice_view_type->Append(_(L("Tool"))); + m_choice_view_type->Append(_(L("Color Print"))); + m_choice_view_type->SetSelection(0); + + m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show"))); + + m_combochecklist_features = new wxComboCtrl(); + m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(200, -1), wxCB_READONLY); + std::string feature_text = GUI::into_u8(_(L("Feature types"))); + std::string feature_items = GUI::into_u8( + _(L("Perimeter")) + "|" + + _(L("External perimeter")) + "|" + + _(L("Overhang perimeter")) + "|" + + _(L("Internal infill")) + "|" + + _(L("Solid infill")) + "|" + + _(L("Top solid infill")) + "|" + + _(L("Bridge infill")) + "|" + + _(L("Gap fill")) + "|" + + _(L("Skirt")) + "|" + + _(L("Support material")) + "|" + + _(L("Support material interface")) + "|" + + _(L("Wipe tower")) + "|" + + _(L("Custom")) + ); + Slic3r::GUI::create_combochecklist(m_combochecklist_features, feature_text, feature_items, true); + + m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); + m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); + m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); + m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); + + wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); + top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); + + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); + bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); + bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5); + bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(20); + bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_retractions, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); + main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0); + + SetSizer(main_sizer); + SetMinSize(GetSize()); + GetSizer()->SetSizeHints(this); + + bind_event_handlers(); + + // sets colors for gcode preview extrusion roles + std::vector extrusion_roles_colors = { + "Perimeter", "FFFF66", + "External perimeter", "FFA500", + "Overhang perimeter", "0000FF", + "Internal infill", "B1302A", + "Solid infill", "D732D7", + "Top solid infill", "FF1A1A", + "Bridge infill", "9999FF", + "Gap fill", "FFFFFF", + "Skirt", "845321", + "Support material", "00FF00", + "Support material interface", "008000", + "Wipe tower", "B3E3AB", + "Custom", "28CC94" + }; + m_gcode_preview_data->set_extrusion_paths_colors(extrusion_roles_colors); + + return true; +} + +Preview::~Preview() +{ + unbind_event_handlers(); + + if (m_canvas_widget != nullptr) + { + _3DScene::remove_canvas(m_canvas_widget); + delete m_canvas_widget; + m_canvas = nullptr; + } +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void Preview::set_view_toolbar(GLToolbar* toolbar) +#else +void Preview::set_view_toolbar(GLRadioToolbar* toolbar) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +{ + if (m_canvas != nullptr) + m_canvas->set_view_toolbar(toolbar); +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +void Preview::set_number_extruders(unsigned int number_extruders) +{ + if (m_number_extruders != number_extruders) + { + m_number_extruders = number_extruders; + int type = 0; // color by a feature type + if (number_extruders > 1) + { + int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); + int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type + m_choice_view_type->SetSelection(type); + if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + + m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature"; + } + } +} + +void Preview::reset_gcode_preview_data() +{ + m_gcode_preview_data->reset(); + m_canvas->reset_legend_texture(); +} + +void Preview::set_canvas_as_dirty() +{ + m_canvas->set_as_dirty(); +} + +void Preview::set_enabled(bool enabled) +{ + m_enabled = enabled; +} + +void Preview::set_bed_shape(const Pointfs& shape) +{ + m_canvas->set_bed_shape(shape); +} + +void Preview::select_view(const std::string& direction) +{ + m_canvas->select_view(direction); +} + +void Preview::set_viewport_from_scene(GLCanvas3D* canvas) +{ + if (canvas != nullptr) + m_canvas->set_viewport_from_scene(*canvas); +} + +void Preview::set_viewport_into_scene(GLCanvas3D* canvas) +{ + if (canvas != nullptr) + canvas->set_viewport_from_scene(*m_canvas); +} + +void Preview::set_drop_target(wxDropTarget* target) +{ + if (target != nullptr) + SetDropTarget(target); +} + +void Preview::load_print() +{ + PrinterTechnology tech = m_process->current_printer_technology(); + if (tech == ptFFF) + load_print_as_fff(); + else if (tech == ptSLA) + load_print_as_sla(); +} + +void Preview::reload_print(bool force) +{ + m_canvas->reset_volumes(); + m_loaded = false; + + if (!IsShown() && !force) + return; + + load_print(); +} + +void Preview::refresh_print() +{ + m_loaded = false; + + if (!IsShown()) + return; + + load_print(); +} + +void Preview::bind_event_handlers() +{ + this->Bind(wxEVT_SIZE, &Preview::on_size, this); + m_choice_view_type->Bind(wxEVT_CHOICE, &Preview::on_choice_view_type, this); + m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); + m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); + m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); + m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); + m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); +} + +void Preview::unbind_event_handlers() +{ + this->Unbind(wxEVT_SIZE, &Preview::on_size, this); + m_choice_view_type->Unbind(wxEVT_CHOICE, &Preview::on_choice_view_type, this); + m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); + m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); + m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); + m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); + m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); +} + +void Preview::show_hide_ui_elements(const std::string& what) +{ + bool enable = (what == "full"); + m_label_show_features->Enable(enable); + m_combochecklist_features->Enable(enable); + m_checkbox_travel->Enable(enable); + m_checkbox_retractions->Enable(enable); + m_checkbox_unretractions->Enable(enable); + m_checkbox_shells->Enable(enable); + + enable = (what != "none"); + m_label_view_type->Enable(enable); + m_choice_view_type->Enable(enable); + + bool visible = (what != "none"); + m_label_show_features->Show(visible); + m_combochecklist_features->Show(visible); + m_checkbox_travel->Show(visible); + m_checkbox_retractions->Show(visible); + m_checkbox_unretractions->Show(visible); + m_checkbox_shells->Show(visible); + m_label_view_type->Show(visible); + m_choice_view_type->Show(visible); +} + +void Preview::reset_sliders() +{ + m_enabled = false; + reset_double_slider(); + m_double_slider_sizer->Hide((size_t)0); +} + +void Preview::update_sliders(const std::vector& layers_z) +{ + m_enabled = true; + update_double_slider(layers_z, m_force_sliders_full_range); + m_double_slider_sizer->Show((size_t)0); + Layout(); +} + +void Preview::on_size(wxSizeEvent& evt) +{ + evt.Skip(); + Refresh(); +} + +void Preview::on_choice_view_type(wxCommandEvent& evt) +{ + m_preferred_color_mode = (m_choice_view_type->GetStringSelection() == L("Tool")) ? "tool" : "feature"; + int selection = m_choice_view_type->GetCurrentSelection(); + if ((0 <= selection) && (selection < (int)GCodePreviewData::Extrusion::Num_View_Types)) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)selection; + + reload_print(); +} + +void Preview::on_combochecklist_features(wxCommandEvent& evt) +{ + int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features); + m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags; + refresh_print(); +} + +void Preview::on_checkbox_travel(wxCommandEvent& evt) +{ + m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked(); + refresh_print(); +} + +void Preview::on_checkbox_retractions(wxCommandEvent& evt) +{ + m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked(); + refresh_print(); +} + +void Preview::on_checkbox_unretractions(wxCommandEvent& evt) +{ + m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked(); + refresh_print(); +} + +void Preview::on_checkbox_shells(wxCommandEvent& evt) +{ + m_gcode_preview_data->shell.is_visible = m_checkbox_shells->IsChecked(); + refresh_print(); +} + +void Preview::create_double_slider() +{ + m_slider = new PrusaDoubleSlider(this, wxID_ANY, 0, 0, 0, 100); + m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0); + + // sizer, m_canvas_widget + m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_double_slider_from_canvas, this); + + m_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_sliders_scroll_changed, this); + + Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { + auto& config = wxGetApp().preset_bundle->project_config; + ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); + m_schedule_background_process(); + int type = m_choice_view_type->FindString(_(L("Color Print"))); + if (m_choice_view_type->GetSelection() != type) { + m_choice_view_type->SetSelection(type); + if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + m_preferred_color_mode = "feature"; + } + }); +} + +void Preview::update_double_slider(const std::vector& layers_z, bool force_sliders_full_range) +{ + std::vector> values; + fill_slider_values(values, layers_z); + + m_slider->SetMaxValue(layers_z.size() - 1); + if (force_sliders_full_range) + m_slider->SetHigherValue(layers_z.size() - 1); + + m_slider->SetSliderValues(values); + const double z_low = m_slider->GetLowerValueD(); + const double z_high = m_slider->GetHigherValueD(); + + const auto& config = wxGetApp().preset_bundle->project_config; + const std::vector &ticks_from_config = (config.option("colorprint_heights"))->values; + + m_slider->SetTicksValues(ticks_from_config); + + set_double_slider_thumbs(layers_z, z_low, z_high); + + bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF); + if (color_print_enable) { + const auto& config = wxGetApp().preset_bundle->full_config(); + if (config.opt("nozzle_diameter")->values.size() > 1) + color_print_enable = false; + } + m_slider->EnableTickManipulation(color_print_enable); +} + +void Preview::fill_slider_values(std::vector> &values, + const std::vector &layers_z) +{ + values.clear(); + for (int i = 0; i < layers_z.size(); ++i) + { + values.push_back(std::pair(i + 1, layers_z[i])); + } + + // All ticks that would end up outside the slider range should be erased. + // TODO: this should be placed into more appropriate part of code, + // this function is e.g. not called when the last object is deleted + auto& config = wxGetApp().preset_bundle->project_config; + std::vector &ticks_from_config = (config.option("colorprint_heights"))->values; + unsigned int old_size = ticks_from_config.size(); + ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(), + [values](double val) { return values.back().second < val; }), + ticks_from_config.end()); + if (ticks_from_config.size() != old_size) + m_schedule_background_process(); +} + +void Preview::set_double_slider_thumbs(const std::vector &layers_z, + const double z_low, + const double z_high) +{ + // Force slider full range only when slider is created. + // Support selected diapason on the all next steps + if (z_high == 0.0) { + m_slider->SetLowerValue(0); + m_slider->SetHigherValue(layers_z.size() - 1); + return; + } + + for (int i = layers_z.size() - 1; i >= 0; i--) +// if (z_low >= layers_z[i]) { + if (fabs(z_low - layers_z[i]) <= 1e-6) { + m_slider->SetLowerValue(i); + break; + } + for (int i = layers_z.size() - 1; i >= 0; i--) +// if (z_high >= layers_z[i]) { + if (fabs(z_high-layers_z[i]) <= 1e-6) { + m_slider->SetHigherValue(i); + break; + } +} + +void Preview::reset_double_slider() +{ + m_slider->SetHigherValue(0); + m_slider->SetLowerValue(0); +} + +void Preview::update_double_slider_from_canvas(wxKeyEvent& event) +{ + if (event.HasModifiers()) { + event.Skip(); + return; + } + + const auto key = event.GetKeyCode(); + + if (key == 'U' || key == 'D') { + const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1; + m_slider->SetHigherValue(new_pos); + if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue()); + } + else if (key == 'S') + m_slider->ChangeOneLayerLock(); + else + event.Skip(); +} + +void Preview::load_print_as_fff() +{ + if (m_loaded || m_process->current_printer_technology() != ptFFF) + return; + + // we require that there's at least one object and the posSlice step + // is performed on all of them(this ensures that _shifted_copies was + // populated and we know the number of layers) + unsigned int n_layers = 0; + const Print *print = m_process->fff_print(); + if (print->is_step_done(posSlice)) + { + std::set zs; + for (const PrintObject* print_object : print->objects()) + { + const LayerPtrs& layers = print_object->layers(); + const SupportLayerPtrs& support_layers = print_object->support_layers(); + for (const Layer* layer : layers) + { + zs.insert(layer->print_z); + } + for (const SupportLayer* layer : support_layers) + { + zs.insert(layer->print_z); + } + } + + n_layers = (unsigned int)zs.size(); + } + + if (n_layers == 0) + { + reset_sliders(); + m_canvas->reset_legend_texture(); + m_canvas_widget->Refresh(); + return; + } + + if (m_preferred_color_mode == "tool_or_feature") + { + // It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature. + // Color by feature if it is a single extruder print. + unsigned int number_extruders = (unsigned int)print->extruders().size(); + int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); + int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type + m_choice_view_type->SetSelection(type); + if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + // If the->SetSelection changed the following line, revert it to "decide yourself". + m_preferred_color_mode = "tool_or_feature"; + } + + // Collect colors per extruder. + std::vector colors; + if (!m_gcode_preview_data->empty() || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool)) + { + const ConfigOptionStrings* extruders_opt = dynamic_cast(m_config->option("extruder_colour")); + const ConfigOptionStrings* filamemts_opt = dynamic_cast(m_config->option("filament_colour")); + unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()); + + unsigned char rgb[3]; + for (unsigned int i = 0; i < colors_count; ++i) + { + std::string color = m_config->opt_string("extruder_colour", i); + if (!PresetBundle::parse_color(color, rgb)) + { + color = m_config->opt_string("filament_colour", i); + if (!PresetBundle::parse_color(color, rgb)) + color = "#FFFFFF"; + } + + colors.push_back(color); + } + } + + if (IsShown()) + { + // used to set the sliders to the extremes of the current zs range + m_force_sliders_full_range = false; + + if (m_gcode_preview_data->empty()) + { + // load skirt and brim + m_canvas->load_preview(colors); + show_hide_ui_elements("simple"); + } + else + { + m_force_sliders_full_range = (m_canvas->get_volumes_count() == 0); + m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); + show_hide_ui_elements("full"); + + // recalculates zs and update sliders accordingly + n_layers = (unsigned int)m_canvas->get_current_print_zs(true).size(); + if (n_layers == 0) + { + // all layers filtered out + reset_sliders(); + m_canvas_widget->Refresh(); + } + } + + if (n_layers > 0) + update_sliders(m_canvas->get_current_print_zs(true)); + + m_loaded = true; + } +} + +void Preview::load_print_as_sla() +{ + if (m_loaded || (m_process->current_printer_technology() != ptSLA)) + return; + + unsigned int n_layers = 0; + const SLAPrint* print = m_process->sla_print(); + + std::set zs; + for (const SLAPrintObject* obj : print->objects()) + { + double shift_z = obj->get_current_elevation(); + if (obj->is_step_done(slaposIndexSlices)) + { + const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); + for (const SLAPrintObject::SliceIndex::value_type& id : index) + { + zs.insert(shift_z + id.first); + } + } + } + + n_layers = (unsigned int)zs.size(); + if (n_layers == 0) + { + reset_sliders(); + m_canvas_widget->Refresh(); + } + + if (IsShown()) + { + m_canvas->load_sla_preview(); + show_hide_ui_elements("none"); + + if (n_layers > 0) + { + std::vector layer_zs; + std::copy(zs.begin(), zs.end(), std::back_inserter(layer_zs)); + m_force_sliders_full_range = true; + update_sliders(layer_zs); + } + + m_loaded = true; + } +} + +void Preview::on_sliders_scroll_changed(wxEvent& event) +{ + if (IsShown()) + { + PrinterTechnology tech = m_process->current_printer_technology(); + if (tech == ptFFF) + { + m_canvas->set_toolpaths_range(m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); + m_canvas_widget->Refresh(); + m_canvas->set_use_clipping_planes(false); + } + else if (tech == ptSLA) + { + m_canvas->set_clipping_plane(0, GLCanvas3D::ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); + m_canvas->set_clipping_plane(1, GLCanvas3D::ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); + m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0); + m_canvas_widget->Refresh(); + } + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp new file mode 100644 index 000000000..23e6a682f --- /dev/null +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -0,0 +1,201 @@ +#ifndef slic3r_GUI_Preview_hpp_ +#define slic3r_GUI_Preview_hpp_ + +#include +#include "libslic3r/Point.hpp" + +#include + +class wxNotebook; +class wxGLCanvas; +class wxBoxSizer; +class wxStaticText; +class wxChoice; +class wxComboCtrl; +class wxCheckBox; +class PrusaDoubleSlider; + +namespace Slic3r { + +class DynamicPrintConfig; +class Print; +class BackgroundSlicingProcess; +class GCodePreviewData; +#if ENABLE_REMOVE_TABS_FROM_PLATER +class Model; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +namespace GUI { + +class GLCanvas3D; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_REMOVE_TABS_FROM_PLATER +class GLToolbar; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#else +#if ENABLE_REMOVE_TABS_FROM_PLATER +class GLRadioToolbar; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if ENABLE_REMOVE_TABS_FROM_PLATER +class View3D : public wxPanel +{ + wxGLCanvas* m_canvas_widget; + GLCanvas3D* m_canvas; + +#if !ENABLE_IMGUI + wxPanel* m_gizmo_widget; +#endif // !ENABLE_IMGUI + + Model* m_model; + DynamicPrintConfig* m_config; + BackgroundSlicingProcess* m_process; + +public: + View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + virtual ~View3D(); + + wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } + GLCanvas3D* get_canvas3d() { return m_canvas; } + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar); +#else + void set_view_toolbar(GLRadioToolbar* toolbar); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + + void set_as_dirty(); + void set_bed_shape(const Pointfs& shape); + + void select_view(const std::string& direction); + void select_all(); + void delete_selected(); + void mirror_selection(Axis axis); + + void enable_toolbar_item(const std::string& name, bool enable); + int check_volumes_outside_state() const; + + bool is_layers_editing_enabled() const; + bool is_layers_editing_allowed() const; + void enable_layers_editing(bool enable); + + bool is_dragging() const; + bool is_reload_delayed() const; + + void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); + void render(); + +private: + bool init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); +}; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +class Preview : public wxPanel +{ + wxGLCanvas* m_canvas_widget; + GLCanvas3D* m_canvas; + wxBoxSizer* m_double_slider_sizer; + wxStaticText* m_label_view_type; + wxChoice* m_choice_view_type; + wxStaticText* m_label_show_features; + wxComboCtrl* m_combochecklist_features; + wxCheckBox* m_checkbox_travel; + wxCheckBox* m_checkbox_retractions; + wxCheckBox* m_checkbox_unretractions; + wxCheckBox* m_checkbox_shells; + + DynamicPrintConfig* m_config; + BackgroundSlicingProcess* m_process; + GCodePreviewData* m_gcode_preview_data; + + // Calling this function object forces Plater::schedule_background_process. + std::function m_schedule_background_process; + + unsigned int m_number_extruders; + std::string m_preferred_color_mode; + + bool m_loaded; + bool m_enabled; + bool m_force_sliders_full_range; + + PrusaDoubleSlider* m_slider {nullptr}; + +public: +#if ENABLE_REMOVE_TABS_FROM_PLATER + Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); +#else + Preview(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + virtual ~Preview(); + + wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } + +#if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar); +#else + void set_view_toolbar(GLRadioToolbar* toolbar); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + void set_number_extruders(unsigned int number_extruders); + void reset_gcode_preview_data(); + void set_canvas_as_dirty(); + void set_enabled(bool enabled); + void set_bed_shape(const Pointfs& shape); + void select_view(const std::string& direction); + void set_viewport_from_scene(GLCanvas3D* canvas); + void set_viewport_into_scene(GLCanvas3D* canvas); + void set_drop_target(wxDropTarget* target); + + void load_print(); + void reload_print(bool force = false); + void refresh_print(); + +private: +#if ENABLE_REMOVE_TABS_FROM_PLATER + bool init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); +#else + bool init(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + void bind_event_handlers(); + void unbind_event_handlers(); + + void show_hide_ui_elements(const std::string& what); + + void reset_sliders(); + void update_sliders(const std::vector& layers_z); + + void on_size(wxSizeEvent& evt); + void on_choice_view_type(wxCommandEvent& evt); + void on_combochecklist_features(wxCommandEvent& evt); + void on_checkbox_travel(wxCommandEvent& evt); + void on_checkbox_retractions(wxCommandEvent& evt); + void on_checkbox_unretractions(wxCommandEvent& evt); + void on_checkbox_shells(wxCommandEvent& evt); + + // Create/Update/Reset double slider on 3dPreview + void create_double_slider(); + void update_double_slider(const std::vector& layers_z, bool force_sliders_full_range); + void fill_slider_values(std::vector> &values, + const std::vector &layers_z); + void set_double_slider_thumbs( const std::vector &layers_z, + const double z_low, + const double z_high); + void reset_double_slider(); + // update DoubleSlider after keyDown in canvas + void update_double_slider_from_canvas(wxKeyEvent& event); + + void load_print_as_fff(); + void load_print_as_sla(); + + void on_sliders_scroll_changed(wxEvent& event); + +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GUI_Preview_hpp_ diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp new file mode 100644 index 000000000..43b3c38f7 --- /dev/null +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -0,0 +1,139 @@ +#include "GUI_Utils.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "libslic3r/Config.hpp" + + +namespace Slic3r { +namespace GUI { + + +wxTopLevelWindow* find_toplevel_parent(wxWindow *window) +{ + for (; window != nullptr; window = window->GetParent()) { + if (window->IsTopLevel()) { + return dynamic_cast(window); + } + } + + return nullptr; +} + + +CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent) + : wxPanel(parent, wxID_ANY) +{ + // WARN: wxMSW does some extra shenanigans to calc the extra control size. + // It first calls the create function with a dummy empty wxDialog parent and saves its size. + // Afterwards, the create function is called again with the real parent. + // Additionally there's no way to pass any extra data to the create function (no closure), + // which is why we have to this stuff here. Grrr! + auto *dlg = dynamic_cast(parent); + const wxString checkbox_label(dlg != nullptr ? dlg->checkbox_label : wxString("String long enough to contain dlg->checkbox_label")); + + auto* sizer = new wxBoxSizer(wxHORIZONTAL); + cbox = new wxCheckBox(this, wxID_ANY, checkbox_label); + cbox->SetValue(true); + sizer->AddSpacer(5); + sizer->Add(this->cbox, 0, wxEXPAND | wxALL, 5); + SetSizer(sizer); + sizer->SetSizeHints(this); +} + +wxWindow* CheckboxFileDialog::ExtraPanel::ctor(wxWindow *parent) { + return new ExtraPanel(parent); +} + +CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent, + const wxString &checkbox_label, + bool checkbox_value, + const wxString &message, + const wxString &default_dir, + const wxString &default_file, + const wxString &wildcard, + long style, + const wxPoint &pos, + const wxSize &size, + const wxString &name +) + : wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name) + , checkbox_label(checkbox_label) +{ + if (checkbox_label.IsEmpty()) { + return; + } + + SetExtraControlCreator(ExtraPanel::ctor); +} + +bool CheckboxFileDialog::get_checkbox_value() const +{ + auto *extra_panel = dynamic_cast(GetExtraControl()); + return extra_panel != nullptr ? extra_panel->cbox->GetValue() : false; +} + + +WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window) +{ + WindowMetrics res; + res.rect = window->GetScreenRect(); + res.maximized = window->IsMaximized(); + return res; +} + +boost::optional WindowMetrics::deserialize(const std::string &str) +{ + std::vector metrics_str; + metrics_str.reserve(5); + + if (!unescape_strings_cstyle(str, metrics_str) || metrics_str.size() != 5) { + return boost::none; + } + + int metrics[5]; + try { + for (size_t i = 0; i < 5; i++) { + metrics[i] = boost::lexical_cast(metrics_str[i]); + } + } catch(const boost::bad_lexical_cast &) { + return boost::none; + } + + if ((metrics[4] & ~1) != 0) { // Checks if the maximized flag is 1 or 0 + metrics[4] = 0; + } + + WindowMetrics res; + res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]); + res.maximized = metrics[4] != 0; + + return res; +} + +void WindowMetrics::sanitize_for_display(const wxRect &screen_rect) +{ + rect = rect.Intersect(screen_rect); +} + +std::string WindowMetrics::serialize() +{ + return (boost::format("%1%; %2%; %3%; %4%; %5%") + % rect.GetX() + % rect.GetY() + % rect.GetWidth() + % rect.GetHeight() + % static_cast(maximized) + ).str(); +} + + + +} +} diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp new file mode 100644 index 000000000..256d47253 --- /dev/null +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -0,0 +1,158 @@ +#ifndef slic3r_GUI_Utils_hpp_ +#define slic3r_GUI_Utils_hpp_ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +class wxCheckBox; +class wxTopLevelWindow; +class wxRect; + + +namespace Slic3r { +namespace GUI { + + +wxTopLevelWindow* find_toplevel_parent(wxWindow *window); + + +class EventGuard +{ + // This is a RAII-style smart-ptr-like guard that will bind any event to any event handler + // and unbind it as soon as it goes out of scope or unbind() is called. + // This can be used to solve the annoying problem of wx events being delivered to freed objects. + +private: + // This is a way to type-erase both the event type as well as the handler: + + struct EventStorageBase { + virtual ~EventStorageBase() {} + }; + + template + struct EventStorageFun : EventStorageBase { + wxEvtHandler *emitter; + EvTag tag; + Fun fun; + + EventStorageFun(wxEvtHandler *emitter, const EvTag &tag, Fun fun) + : emitter(emitter) + , tag(tag) + , fun(std::move(fun)) + { + emitter->Bind(this->tag, this->fun); + } + + virtual ~EventStorageFun() { emitter->Unbind(tag, fun); } + }; + + template + struct EventStorageMethod : EventStorageBase { + typedef void(Class::* MethodPtr)(EvArg &); + + wxEvtHandler *emitter; + EvTag tag; + MethodPtr method; + EvHandler *handler; + + EventStorageMethod(wxEvtHandler *emitter, const EvTag &tag, MethodPtr method, EvHandler *handler) + : emitter(emitter) + , tag(tag) + , method(method) + , handler(handler) + { + emitter->Bind(tag, method, handler); + } + + virtual ~EventStorageMethod() { emitter->Unbind(tag, method, handler); } + }; + + std::unique_ptr event_storage; +public: + EventGuard() {} + EventGuard(const EventGuard&) = delete; + EventGuard(EventGuard &&other) : event_storage(std::move(other.event_storage)) {} + + template + EventGuard(wxEvtHandler *emitter, const EvTag &tag, Fun fun) + :event_storage(new EventStorageFun(emitter, tag, std::move(fun))) + {} + + template + EventGuard(wxEvtHandler *emitter, const EvTag &tag, void(Class::* method)(EvArg &), EvHandler *handler) + :event_storage(new EventStorageMethod(emitter, tag, method, handler)) + {} + + EventGuard& operator=(const EventGuard&) = delete; + EventGuard& operator=(EventGuard &&other) + { + event_storage = std::move(other.event_storage); + return *this; + } + + void unbind() { event_storage.reset(nullptr); } + explicit operator bool() const noexcept { return !!event_storage; } +}; + + +class CheckboxFileDialog : public wxFileDialog +{ +public: + CheckboxFileDialog(wxWindow *parent, + const wxString &checkbox_label, + bool checkbox_value, + const wxString &message = wxFileSelectorPromptStr, + const wxString &default_dir = wxEmptyString, + const wxString &default_file = wxEmptyString, + const wxString &wildcard = wxFileSelectorDefaultWildcardStr, + long style = wxFD_DEFAULT_STYLE, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + const wxString &name = wxFileDialogNameStr + ); + + bool get_checkbox_value() const; + +private: + struct ExtraPanel : public wxPanel + { + wxCheckBox *cbox; + + ExtraPanel(wxWindow *parent); + static wxWindow* ctor(wxWindow *parent); + }; + + wxString checkbox_label; +}; + + +class WindowMetrics +{ +private: + wxRect rect; + bool maximized; + + WindowMetrics() : maximized(false) {} +public: + static WindowMetrics from_window(wxTopLevelWindow *window); + static boost::optional deserialize(const std::string &str); + + wxRect get_rect() const { return rect; } + bool get_maximized() const { return maximized; } + + void sanitize_for_display(const wxRect &screen_rect); + std::string serialize(); +}; + + +}} + +#endif diff --git a/src/slic3r/GUI/I18N.cpp b/src/slic3r/GUI/I18N.cpp new file mode 100644 index 000000000..530a32143 --- /dev/null +++ b/src/slic3r/GUI/I18N.cpp @@ -0,0 +1,11 @@ +#include "I18N.hpp" + +namespace Slic3r { namespace GUI { + +wxString L_str(const std::string &str) +{ + //! Explicitly specify that the source string is already in UTF-8 encoding + return wxGetTranslation(wxString(str.c_str(), wxConvUTF8)); +} + +} } diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp new file mode 100644 index 000000000..c86ff7501 --- /dev/null +++ b/src/slic3r/GUI/I18N.hpp @@ -0,0 +1,39 @@ +#ifndef _ +#define _(s) Slic3r::GUI::I18N::translate((s)) +#endif /* _ */ + +#ifndef L +// !!! If you needed to translate some wxString, +// !!! please use _(L(string)) +// !!! _() - is a standard wxWidgets macro to translate +// !!! L() is used only for marking localizable string +// !!! It will be used in "xgettext" to create a Locating Message Catalog. +#define L(s) s +#endif /* L */ + +#ifndef _CHB +//! macro used to localization, return wxScopedCharBuffer +//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding +#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str() +#endif /* _CHB */ + +#ifndef slic3r_GUI_I18N_hpp_ +#define slic3r_GUI_I18N_hpp_ + +#include + +namespace Slic3r { namespace GUI { + +namespace I18N { + inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); } + inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); } + inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); } + inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); } +} + +// Return translated std::string as a wxString +wxString L_str(const std::string &str); + +} } + +#endif /* slic3r_GUI_I18N_hpp_ */ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp new file mode 100644 index 000000000..2cc22d2b5 --- /dev/null +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -0,0 +1,323 @@ +#include "ImGuiWrapper.hpp" + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" + +namespace Slic3r { +namespace GUI { + + +ImGuiWrapper::ImGuiWrapper() + : m_font_texture(0) + , m_mouse_buttons(0) +{ +} + +ImGuiWrapper::~ImGuiWrapper() +{ + destroy_device_objects(); + ImGui::DestroyContext(); +} + +bool ImGuiWrapper::init() +{ + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), 18.0f); + if (font == nullptr) { + font = io.Fonts->AddFontDefault(); + if (font == nullptr) + return false; + } + else { + m_fonts.insert(FontsMap::value_type("Noto Sans Regular 18", font)); + } + + io.IniFilename = nullptr; + + return true; +} + +void ImGuiWrapper::set_display_size(float w, float h) +{ + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(w, h); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); +} + +bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); + io.MouseDown[0] = evt.LeftDown(); + io.MouseDown[1] = evt.RightDown(); + io.MouseDown[2] = evt.MiddleDown(); + + unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); + bool res = buttons != m_mouse_buttons; + m_mouse_buttons = buttons; + return res; +} + +void ImGuiWrapper::new_frame() +{ + if (m_font_texture == 0) + create_device_objects(); + + ImGui::NewFrame(); +} + +void ImGuiWrapper::render() +{ + ImGui::Render(); + render_draw_data(ImGui::GetDrawData()); +} + +void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) +{ + ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); +} + +void ImGuiWrapper::set_next_window_bg_alpha(float alpha) +{ + ImGui::SetNextWindowBgAlpha(alpha); +} + +bool ImGuiWrapper::begin(const std::string &name, int flags) +{ + return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); +} + +bool ImGuiWrapper::begin(const wxString &name, int flags) +{ + return begin(into_u8(name), flags); +} + +void ImGuiWrapper::end() +{ + ImGui::End(); +} + +bool ImGuiWrapper::button(const wxString &label) +{ + auto label_utf8 = into_u8(label); + return ImGui::Button(label_utf8.c_str()); +} + +bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) +{ + return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); +} + +bool ImGuiWrapper::input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format) +{ + bool value_changed = false; + + ImGui::BeginGroup(); + + for (int i = 0; i < 3; ++i) + { + std::string item_label = (i == 0) ? "X" : ((i == 1) ? "Y" : "Z"); + ImGui::PushID(i); + ImGui::PushItemWidth(width); + value_changed |= ImGui::InputDouble(item_label.c_str(), const_cast(&value(i)), 0.0f, 0.0f, format.c_str()); + ImGui::PopID(); + } + ImGui::EndGroup(); + + return value_changed; +} + +bool ImGuiWrapper::checkbox(const wxString &label, bool &value) +{ + auto label_utf8 = into_u8(label); + return ImGui::Checkbox(label_utf8.c_str(), &value); +} + +void ImGuiWrapper::text(const wxString &label) +{ + auto label_utf8 = into_u8(label); + ImGui::Text(label_utf8.c_str(), NULL); +} + +bool ImGuiWrapper::want_mouse() const +{ + return ImGui::GetIO().WantCaptureMouse; +} + +bool ImGuiWrapper::want_keyboard() const +{ + return ImGui::GetIO().WantCaptureKeyboard; +} + +bool ImGuiWrapper::want_text_input() const +{ + return ImGui::GetIO().WantTextInput; +} + +bool ImGuiWrapper::want_any_input() const +{ + const auto io = ImGui::GetIO(); + return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; +} + +void ImGuiWrapper::create_device_objects() +{ + create_fonts_texture(); +} + +void ImGuiWrapper::create_fonts_texture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &m_font_texture); + glBindTexture(GL_TEXTURE_2D, m_font_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (ImTextureID)(intptr_t)m_font_texture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); +} + +void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GLint texture_env_mode = GL_MODULATE; + glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texture_env_mode); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + ImVec2 pos = draw_data->DisplayPos; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + // User callback (registered via ImDrawList::AddCallback) + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y); + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Apply scissor/clipping rectangle + glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + } + idx_buffer += pcmd->ElemCount; + } + } + + // Restore modified state + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texture_env_mode); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +void ImGuiWrapper::destroy_device_objects() +{ + destroy_fonts_texture(); +} + +void ImGuiWrapper::destroy_fonts_texture() +{ + if (m_font_texture) { + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->TexID = 0; + glDeleteTextures(1, &m_font_texture); + m_font_texture = 0; + } +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp new file mode 100644 index 000000000..cd8508aef --- /dev/null +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -0,0 +1,70 @@ +#ifndef slic3r_ImGuiWrapper_hpp_ +#define slic3r_ImGuiWrapper_hpp_ + +#include +#include + +#include + +#include "libslic3r/Point.hpp" + +class wxString; +class wxMouseEvent; + + +namespace Slic3r { +namespace GUI { + +class ImGuiWrapper +{ + typedef std::map FontsMap; + + FontsMap m_fonts; + unsigned m_font_texture; + + unsigned m_mouse_buttons; + +public: + ImGuiWrapper(); + ~ImGuiWrapper(); + + bool init(); + void read_glsl_version(); + + void set_display_size(float w, float h); + bool update_mouse_data(wxMouseEvent &evt); + + void new_frame(); + void render(); + + void set_next_window_pos(float x, float y, int flag); + void set_next_window_bg_alpha(float alpha); + + bool begin(const std::string &name, int flags = 0); + bool begin(const wxString &name, int flags = 0); + void end(); + + bool button(const wxString &label); + bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); + bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); + bool checkbox(const wxString &label, bool &value); + void text(const wxString &label); + + bool want_mouse() const; + bool want_keyboard() const; + bool want_text_input() const; + bool want_any_input() const; +private: + void create_device_objects(); + void create_fonts_texture(); + void render_draw_data(ImDrawData *draw_data); + void destroy_device_objects(); + void destroy_fonts_texture(); +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_ImGuiWrapper_hpp_ + diff --git a/src/slic3r/GUI/LambdaObjectDialog.cpp b/src/slic3r/GUI/LambdaObjectDialog.cpp index 7d741be7f..0146db5f6 100644 --- a/src/slic3r/GUI/LambdaObjectDialog.cpp +++ b/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -3,12 +3,12 @@ #include #include #include "OptionsGroup.hpp" +#include "I18N.hpp" namespace Slic3r { namespace GUI { -static wxString dots("…", wxConvUTF8); LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, const wxString type_name): @@ -39,8 +39,8 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, ConfigOptionDef def; def.width = 70; auto optgroup = init_modificator_options_page(_(L("Box"))); - if (optgroup){ - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (optgroup) { + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { int opt_id = opt_key == "l" ? 0 : opt_key == "w" ? 1 : opt_key == "h" ? 2 : -1; @@ -50,22 +50,22 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, def.type = coFloat; def.default_value = new ConfigOptionFloat{ 1.0 }; - def.label = L("L"); + def.label = L("Length"); Option option(def, "l"); optgroup->append_single_option_line(option); - def.label = L("W"); + def.label = L("Width"); option = Option(def, "w"); optgroup->append_single_option_line(option); - def.label = L("H"); + def.label = L("Height"); option = Option(def, "h"); optgroup->append_single_option_line(option); } optgroup = init_modificator_options_page(_(L("Cylinder"))); - if (optgroup){ - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (optgroup) { + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { int val = boost::any_cast(value); if (opt_key == "cyl_r") object_parameters.cyl_r = val; @@ -86,8 +86,8 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, } optgroup = init_modificator_options_page(_(L("Sphere"))); - if (optgroup){ - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (optgroup) { + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "sph_rho") object_parameters.sph_rho = boost::any_cast(value); else return; @@ -101,8 +101,8 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, } optgroup = init_modificator_options_page(_(L("Slab"))); - if (optgroup){ - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (optgroup) { + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { double val = boost::any_cast(value); if (opt_key == "slab_z") object_parameters.slab_z = val; @@ -113,7 +113,7 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, def.type = coFloat; def.default_value = new ConfigOptionFloat{ 1.0 }; - def.label = L("H"); + def.label = L("Height"); auto option = Option(def, "slab_h"); optgroup->append_single_option_line(option); @@ -172,7 +172,8 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){ +ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title) +{ if (!m_type_name.IsEmpty() && m_type_name != title) return nullptr; diff --git a/src/slic3r/GUI/LambdaObjectDialog.hpp b/src/slic3r/GUI/LambdaObjectDialog.hpp index 8f3e8cd80..6cc99c8a7 100644 --- a/src/slic3r/GUI/LambdaObjectDialog.hpp +++ b/src/slic3r/GUI/LambdaObjectDialog.hpp @@ -13,6 +13,24 @@ namespace Slic3r { namespace GUI { +enum LambdaTypeIDs{ + LambdaTypeBox, + LambdaTypeCylinder, + LambdaTypeSphere, + LambdaTypeSlab +}; + +struct OBJECT_PARAMETERS +{ + LambdaTypeIDs type = LambdaTypeBox; + double dim[3];// = { 1.0, 1.0, 1.0 }; + int cyl_r = 1; + int cyl_h = 1; + double sph_rho = 1.0; + double slab_h = 1.0; + double slab_z = 0.0; +}; +class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; class LambdaObjectDialog : public wxDialog { @@ -23,10 +41,10 @@ class LambdaObjectDialog : public wxDialog public: LambdaObjectDialog(wxWindow* parent, const wxString type_name = wxEmptyString); - ~LambdaObjectDialog(){} + ~LambdaObjectDialog() {} bool CanClose() { return true; } // ??? - OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; } + OBJECT_PARAMETERS& ObjectParameters() { return object_parameters; } ConfigOptionsGroupShp init_modificator_options_page(const wxString& title); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp new file mode 100644 index 000000000..a74d5f72a --- /dev/null +++ b/src/slic3r/GUI/MainFrame.cpp @@ -0,0 +1,817 @@ +#include "MainFrame.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Print.hpp" +#include "libslic3r/Polygon.hpp" + +#include "Tab.hpp" +#include "PresetBundle.hpp" +#include "ProgressStatusBar.hpp" +#include "3DScene.hpp" +#include "AppConfig.hpp" +#include "wxExtensions.hpp" +#include "I18N.hpp" + +#include +#include "GUI_App.hpp" + +namespace Slic3r { +namespace GUI { + +MainFrame::MainFrame(const bool no_plater, const bool loaded) : +wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE), + m_no_plater(no_plater), + m_loaded(loaded) +{ + // Load the icon either from the exe, or from the ico file. +#if _WIN32 + { + TCHAR szExeFileName[MAX_PATH]; + GetModuleFileName(nullptr, szExeFileName, MAX_PATH); + SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); + } +#else + SetIcon(wxIcon(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); +#endif // _WIN32 + + // initialize status bar + m_statusbar = new ProgressStatusBar(this); + m_statusbar->embed(this); + m_statusbar->set_status_text(_(L("Version ")) + + SLIC3R_VERSION + + _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); + + // initialize tabpanel and menubar + init_tabpanel(); + init_menubar(); + + // set default tooltip timer in msec + // SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values + // (SetAutoPop is not available on GTK.) + wxToolTip::SetAutoPop(32767); + + m_loaded = true; + + // initialize layout + auto sizer = new wxBoxSizer(wxVERTICAL); + if (m_tabpanel) + sizer->Add(m_tabpanel, 1, wxEXPAND); + sizer->SetSizeHints(this); + SetSizer(sizer); + Fit(); + SetMinSize(wxSize(760, 490)); + SetSize(GetMinSize()); + Layout(); + + // declare events + Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { + if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { + event.Veto(); + return; + } + // save window size + wxGetApp().window_pos_save(this, "mainframe"); + // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, + // but in rare cases it may not have been called yet. + wxGetApp().app_config->save(); +// if (m_plater) +// m_plater->print = undef; + _3DScene::remove_all_canvases(); +// Slic3r::GUI::deregister_on_request_update_callback(); + // propagate event + event.Skip(); + }); + + // NB: Restoring the window position is done in a two-phase manner here, + // first the saved position is restored as-is and validation is done after the window is shown + // and initial round of events is complete, because on some platforms that is the only way + // to get an accurate window position & size. + wxGetApp().window_pos_restore(this, "mainframe"); + Bind(wxEVT_SHOW, [this](wxShowEvent&) { + CallAfter([this]() { + wxGetApp().window_pos_sanitize(this); + }); + }); + + update_ui_from_settings(); +} + +void MainFrame::init_tabpanel() +{ + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + + m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { + auto panel = m_tabpanel->GetCurrentPage(); + + if (panel == nullptr) + return; + + auto& tabs_list = wxGetApp().tabs_list; + if (find(tabs_list.begin(), tabs_list.end(), panel) != tabs_list.end()) { + // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered + // before the MainFrame is fully set up. + static_cast(panel)->OnActivate(); + } + }); + + if (!m_no_plater) { + m_plater = new Slic3r::GUI::Plater(m_tabpanel, this); + wxGetApp().plater_ = m_plater; + m_tabpanel->AddPage(m_plater, _(L("Plater"))); + } + + // The following event is emited by Tab implementation on config value change. + Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this); + + // The following event is emited by Tab on preset selection, + // or when the preset's "modified" status changes. + Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); + + create_preset_tabs(); + + if (m_plater) { + // load initial config + auto full_config = wxGetApp().preset_bundle->full_config(); + m_plater->on_config_change(full_config); + + // Show a correct number of filament fields. + // nozzle_diameter is undefined when SLA printer is selected + if (full_config.has("nozzle_diameter")) { + m_plater->on_extruders_change(full_config.option("nozzle_diameter")->values.size()); + } + } +} + +void MainFrame::create_preset_tabs() +{ + wxGetApp().update_label_colours_from_appconfig(); + add_created_tab(new TabPrint(m_tabpanel)); + add_created_tab(new TabFilament(m_tabpanel)); + add_created_tab(new TabSLAPrint(m_tabpanel)); + add_created_tab(new TabSLAMaterial(m_tabpanel)); + add_created_tab(new TabPrinter(m_tabpanel)); +} + +void MainFrame::add_created_tab(Tab* panel) +{ + panel->create_preset_tab(); + + const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + + if (panel->supports_printer_technology(printer_tech)) + m_tabpanel->AddPage(panel, panel->title()); +} + +bool MainFrame::can_save() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +bool MainFrame::can_export_model() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +bool MainFrame::can_export_gcode() const +{ + if (m_plater == nullptr) + return false; + + if (m_plater->model().objects.empty()) + return false; + + if (m_plater->is_export_gcode_scheduled()) + return false; + + // TODO:: add other filters + + return true; +} + +bool MainFrame::can_change_view() const +{ + int page_id = m_tabpanel->GetSelection(); + return (page_id != wxNOT_FOUND) ? m_tabpanel->GetPageText((size_t)page_id).Lower() == "plater" : false; +} + +bool MainFrame::can_select() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +bool MainFrame::can_delete() const +{ + return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false; +} + +bool MainFrame::can_delete_all() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} + +void MainFrame::init_menubar() +{ + // File menu + wxMenu* fileMenu = new wxMenu; + { + wxMenuItem* item_open = append_menu_item(fileMenu, wxID_ANY, _(L("Open…\tCtrl+O")), _(L("Open a project file")), + [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "brick_add.png"); + wxMenuItem* item_save = append_menu_item(fileMenu, wxID_ANY, _(L("Save\tCtrl+S")), _(L("Save current project file")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(m_plater->get_project_filename().wx_str()); }, "disk.png"); + wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save as…\tCtrl+Alt+S")), _(L("Save current project file as")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "disk.png"); + + fileMenu->AppendSeparator(); + + wxMenu* import_menu = new wxMenu(); + wxMenuItem* item_import_model = append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AMF/3MF…\tCtrl+I")), _(L("Load a model")), + [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "brick_add.png"); + import_menu->AppendSeparator(); + append_menu_item(import_menu, wxID_ANY, _(L("Import Config…\tCtrl+L")), _(L("Load exported configuration file")), + [this](wxCommandEvent&) { load_config_file(); }, "plugin_add.png"); + append_menu_item(import_menu, wxID_ANY, _(L("Import Config from project…\tCtrl+Alt+L")), _(L("Load configuration from project file")), + [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "plugin_add.png"); + import_menu->AppendSeparator(); + append_menu_item(import_menu, wxID_ANY, _(L("Import Config Bundle…")), _(L("Load presets from a bundle")), + [this](wxCommandEvent&) { load_configbundle(); }, "lorry_add.png"); + append_submenu(fileMenu, import_menu, wxID_ANY, _(L("Import")), _(L(""))); + + wxMenu* export_menu = new wxMenu(); + wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export G-code…\tCtrl+G")), _(L("Export current plate as G-code")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "cog_go.png"); + export_menu->AppendSeparator(); + wxMenuItem* item_export_stl = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL…")), _(L("Export current plate as STL")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "brick_go.png"); + wxMenuItem* item_export_amf = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as AMF…")), _(L("Export current plate as AMF")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "brick_go.png"); + export_menu->AppendSeparator(); + append_menu_item(export_menu, wxID_ANY, _(L("Export Config…\tCtrl+E")), _(L("Export current configuration to file")), + [this](wxCommandEvent&) { export_config(); }, "plugin_go.png"); + append_menu_item(export_menu, wxID_ANY, _(L("Export Config Bundle…")), _(L("Export all presets to file")), + [this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png"); + append_submenu(fileMenu, export_menu, wxID_ANY, _(L("Export")), _(L(""))); + + fileMenu->AppendSeparator(); + +#if 0 + m_menu_item_repeat = nullptr; + append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice…\tCtrl+U")), _(L("Slice a file into a G-code")), + [this](wxCommandEvent&) { + wxTheApp->CallAfter([this]() { + quick_slice(); + m_menu_item_repeat->Enable(is_last_input_file()); + }); }, "cog_go.png"); + append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save As…\tCtrl+Alt+U")), _(L("Slice a file into a G-code, save as")), + [this](wxCommandEvent&) { + wxTheApp->CallAfter([this]() { + quick_slice(qsSaveAs); + m_menu_item_repeat->Enable(is_last_input_file()); + }); }, "cog_go.png"); + m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _(L("Repeat Last Quick Slice\tCtrl+Shift+U")), _(L("Repeat last quick slice")), + [this](wxCommandEvent&) { + wxTheApp->CallAfter([this]() { + quick_slice(qsReslice); + }); }, "cog_go.png"); + m_menu_item_repeat->Enable(false); + fileMenu->AppendSeparator(); +#endif + m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice Now\tCtrl+R")), _(L("Start new slicing process")), + [this](wxCommandEvent&) { reslice_now(); }, "shape_handles.png"); + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_ANY, _(L("Repair STL file…")), _(L("Automatically repair an STL file")), + [this](wxCommandEvent&) { repair_stl(); }, "wrench.png"); + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_EXIT, _(L("Quit")), _(L("Quit Slic3r")), + [this](wxCommandEvent&) { Close(false); }); + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater != nullptr); }, item_open->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save_as->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater != nullptr); }, item_import_model->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_gcode()); }, item_export_gcode->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_stl->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_amf->GetId()); + } + + // Edit menu + wxMenu* editMenu = nullptr; + if (m_plater != nullptr) + { + editMenu = new wxMenu(); + wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, L("Select all\tCtrl+A"), L("Selects all objects"), + [this](wxCommandEvent&) { m_plater->select_all(); }, ""); + editMenu->AppendSeparator(); + wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, L("Delete selected\tDel"), L("Deletes the current selection"), + [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); + wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, L("Delete all\tCtrl+Del"), L("Deletes all objects"), + [this](wxCommandEvent&) { m_plater->reset(); }, ""); + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); + } + + // Window menu + auto windowMenu = new wxMenu(); + { + size_t tab_offset = 0; + if (m_plater) { +#if ENABLE_REMOVE_TABS_FROM_PLATER + append_menu_item(windowMenu, wxID_ANY, L("Plater Tab\tCtrl+1"), L("Show the plater"), + [this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png"); +#else + append_menu_item(windowMenu, wxID_ANY, L("Select Plater Tab\tCtrl+1"), L("Show the plater"), + [this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png"); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + tab_offset += 1; + } + if (tab_offset > 0) { + windowMenu->AppendSeparator(); + } +#if ENABLE_REMOVE_TABS_FROM_PLATER + append_menu_item(windowMenu, wxID_ANY, L("Print Settings Tab\tCtrl+2"), L("Show the print settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png"); + append_menu_item(windowMenu, wxID_ANY, L("Filament Settings Tab\tCtrl+3"), L("Show the filament settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool.png"); + append_menu_item(windowMenu, wxID_ANY, L("Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png"); + if (m_plater) { + windowMenu->AppendSeparator(); + wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_ANY, L("3D\tCtrl+5"), L("Show the 3D editing view"), + [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, ""); + wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_ANY, L("Preview\tCtrl+6"), L("Show the 3D slices preview"), + [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, ""); + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId()); + } +#else + append_menu_item(windowMenu, wxID_ANY, L("Select Print Settings Tab\tCtrl+2"), L("Show the print settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png"); + append_menu_item(windowMenu, wxID_ANY, L("Select Filament Settings Tab\tCtrl+3"), L("Show the filament settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool.png"); + append_menu_item(windowMenu, wxID_ANY, L("Select Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), + [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png"); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + } + + // View menu + wxMenu* viewMenu = nullptr; + if (m_plater) { + viewMenu = new wxMenu(); + // \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, + // as the simple numeric accelerators spoil all numeric data entry. + // The camera control accelerators are captured by GLCanvas3D::on_char(). + wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); }); + viewMenu->AppendSeparator(); + wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); }); + wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); }); + wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); }); + wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); }); + wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); }); + wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); }); + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_iso->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_top->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_bottom->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_front->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_rear->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_left->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_right->GetId()); + } + + // Help menu + auto helpMenu = new wxMenu(); + { + append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://www.prusa3d.com/drivers/"); }); + append_menu_item(helpMenu, wxID_ANY, _(L("Prusa Edition Releases")), _(L("Open the Prusa Edition releases page in your browser")), + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/releases"); }); +//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ +//# wxTheApp->check_version(1); +//# }); +//# $versioncheck->Enable(wxTheApp->have_version_check); + append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r Website")), _(L("Open the Slic3r website in your browser")), + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://slic3r.org/"); }); + append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r Manual")), _(L("Open the Slic3r manual in your browser")), + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); }); + helpMenu->AppendSeparator(); + append_menu_item(helpMenu, wxID_ANY, _(L("System Info")), _(L("Show system information")), + [this](wxCommandEvent&) { wxGetApp().system_info(); }); + append_menu_item(helpMenu, wxID_ANY, _(L("Show Configuration Folder")), _(L("Show user configuration folder (datadir)")), + [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); + append_menu_item(helpMenu, wxID_ANY, _(L("Report an Issue")), _(L("Report an issue on the Slic3r Prusa Edition")), + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + append_menu_item(helpMenu, wxID_ANY, _(L("About Slic3r")), _(L("Show about dialog")), + [this](wxCommandEvent&) { Slic3r::GUI::about(); }); + } + + // menubar + // assign menubar to frame after appending items, otherwise special items + // will not be handled correctly + { + auto menubar = new wxMenuBar(); + menubar->Append(fileMenu, L("&File")); + if (editMenu) menubar->Append(editMenu, L("&Edit")); + menubar->Append(windowMenu, L("&Window")); + if (viewMenu) menubar->Append(viewMenu, L("&View")); + // Add additional menus from C++ + wxGetApp().add_config_menu(menubar); + menubar->Append(helpMenu, L("&Help")); + SetMenuBar(menubar); + } +} + +// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". +void MainFrame::quick_slice(const int qs) +{ +// my $progress_dialog; + wxString input_file; +// eval +// { + // validate configuration + auto config = wxGetApp().preset_bundle->full_config(); + config.validate(); + + // select input file + if (!(qs & qsReslice)) { + auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), + wxGetApp().app_config->get_last_dir(), "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dlg->ShowModal() != wxID_OK) { + dlg->Destroy(); + return; + } + input_file = dlg->GetPath(); + dlg->Destroy(); + if (!(qs & qsExportSVG)) + m_qs_last_input_file = input_file; + } + else { + if (m_qs_last_input_file.IsEmpty()) { + auto dlg = new wxMessageDialog(this, _(L("No previously sliced file.")), + _(L("Error")), wxICON_ERROR | wxOK); + dlg->ShowModal(); + return; + } + if (std::ifstream(m_qs_last_input_file.char_str())) { + auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), + _(L("File Not Found")), wxICON_ERROR | wxOK); + dlg->ShowModal(); + return; + } + input_file = m_qs_last_input_file; + } + auto input_file_basename = get_base_name(input_file); + wxGetApp().app_config->update_skein_dir(get_dir_name(input_file)); + + auto bed_shape = Slic3r::Polygon::new_scale(config.option("bed_shape")->values); +// auto print_center = Slic3r::Pointf->new_unscale(bed_shape.bounding_box().center()); +// +// auto sprint = new Slic3r::Print::Simple( +// print_center = > print_center, +// status_cb = > [](int percent, const wxString& msg) { +// m_progress_dialog->Update(percent, msg+"…"); +// }); + + // keep model around + auto model = Slic3r::Model::read_from_file(input_file.ToStdString()); + +// sprint->apply_config(config); +// sprint->set_model(model); + + // Copy the names of active presets into the placeholder parser. +// wxGetApp().preset_bundle->export_selections(sprint->placeholder_parser); + + // select output file + wxString output_file; + if (qs & qsReslice) { + if (!m_qs_last_output_file.IsEmpty()) + output_file = m_qs_last_output_file; + } + else if (qs & qsSaveAs) { + // The following line may die if the output_filename_format template substitution fails. +// output_file = sprint->output_filepath; +// if (export_svg) +// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .svg /; + auto dlg = new wxFileDialog(this, _(L("Save ")) + (qs & qsExportSVG ? _(L("SVG")) : _(L("G-code"))) + _(L(" file as:")), + wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file), + qs & qsExportSVG ? file_wildcards(FT_SVG) : file_wildcards(FT_GCODE), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dlg->ShowModal() != wxID_OK) { + dlg->Destroy(); + return; + } + output_file = dlg->GetPath(); + dlg->Destroy(); + if (!(qs & qsExportSVG)) + m_qs_last_output_file = output_file; + wxGetApp().app_config->update_last_output_dir(get_dir_name(output_file)); + } + else if (qs & qsExportPNG) { +// output_file = sprint->output_filepath; +// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .zip / ; + auto dlg = new wxFileDialog(this, _(L("Save zip file as:")), + wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), + get_base_name(output_file), "*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dlg->ShowModal() != wxID_OK) { + dlg->Destroy(); + return; + } + output_file = dlg->GetPath(); + dlg->Destroy(); + } + + // show processbar dialog + m_progress_dialog = new wxProgressDialog(_(L("Slicing…")), _(L("Processing ")) + input_file_basename + "…", + 100, this, 4); + m_progress_dialog->Pulse(); + { +// my @warnings = (); +// local $SIG{ __WARN__ } = sub{ push @warnings, $_[0] }; + +// sprint->output_file(output_file); +// if (export_svg) { +// sprint->export_svg(); +// } +// else if(export_png) { +// sprint->export_png(); +// } +// else { +// sprint->export_gcode(); +// } +// sprint->status_cb(undef); +// Slic3r::GUI::warning_catcher($self)->($_) for @warnings; + } + m_progress_dialog->Destroy(); + m_progress_dialog = nullptr; + + auto message = input_file_basename + _(L(" was successfully sliced.")); +// wxTheApp->notify(message); + wxMessageDialog(this, message, _(L("Slicing Done!")), wxOK | wxICON_INFORMATION).ShowModal(); +// }; +// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); }); +} + +void MainFrame::reslice_now() +{ + if (m_plater) + m_plater->reslice(); +} + +void MainFrame::repair_stl() +{ + wxString input_file; + { + auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")), + wxGetApp().app_config->get_last_dir(), "", + file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dlg->ShowModal() != wxID_OK) { + dlg->Destroy(); + return; + } + input_file = dlg->GetPath(); + dlg->Destroy(); + } + + auto output_file = input_file; + { +// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ; + auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), + get_dir_name(output_file), get_base_name(output_file), + file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dlg->ShowModal() != wxID_OK) { + dlg->Destroy(); + return /*undef*/; + } + output_file = dlg->GetPath(); + dlg->Destroy(); + } + + auto tmesh = new Slic3r::TriangleMesh(); + tmesh->ReadSTLFile(input_file.char_str()); + tmesh->repair(); + tmesh->WriteOBJFile(output_file.char_str()); + Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair")); +} + +void MainFrame::export_config() +{ + // Generate a cummulative configuration for the selected print, filaments and printer. + auto config = wxGetApp().preset_bundle->full_config(); + // Validate the cummulative configuration. + auto valid = config.validate(); + if (!valid.empty()) { +// Slic3r::GUI::catch_error(this); + return; + } + // Ask user for the file name for the config file. + auto dlg = new wxFileDialog(this, _(L("Save configuration as:")), + !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), + !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini", + file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + wxString file; + if (dlg->ShowModal() == wxID_OK) + file = dlg->GetPath(); + dlg->Destroy(); + if (!file.IsEmpty()) { + wxGetApp().app_config->update_config_dir(get_dir_name(file)); + m_last_config = file; + config.save(file.ToStdString()); + } +} + +// Load a config file containing a Print, Filament & Printer preset. +void MainFrame::load_config_file(wxString file/* = wxEmptyString*/) +{ + if (file.IsEmpty()) { + if (!wxGetApp().check_unsaved_changes()) + return; + auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), + "config.ini", "INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dlg->ShowModal() != wxID_OK) + return; + file = dlg->GetPath(); + dlg->Destroy(); + } + try { + wxGetApp().preset_bundle->load_config_file(file.ToStdString()); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + wxGetApp().load_current_presets(); + wxGetApp().app_config->update_config_dir(get_dir_name(file)); + m_last_config = file; +} + +void MainFrame::export_configbundle() +{ + if (!wxGetApp().check_unsaved_changes()) + return; + // validate current configuration in case it's dirty + auto err = wxGetApp().preset_bundle->full_config().validate(); + if (! err.empty()) { + show_error(this, err); + return; + } + // Ask user for a file name. + auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")), + !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), + "Slic3r_config_bundle.ini", + file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + wxString file; + if (dlg->ShowModal() == wxID_OK) + file = dlg->GetPath(); + dlg->Destroy(); + if (!file.IsEmpty()) { + // Export the config bundle. + wxGetApp().app_config->update_config_dir(get_dir_name(file)); + try { + wxGetApp().preset_bundle->export_configbundle(file.ToStdString()); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + } + } +} + +// Loading a config bundle with an external file name used to be used +// to auto - install a config bundle on a fresh user account, +// but that behavior was not documented and likely buggy. +void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) +{ + if (!wxGetApp().check_unsaved_changes()) + return; + if (file.IsEmpty()) { + auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), + "config.ini", file_wildcards(FT_INI), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dlg->ShowModal() != wxID_OK) + return; + file = dlg->GetPath(); + dlg->Destroy(); + } + + wxGetApp().app_config->update_config_dir(get_dir_name(file)); + + auto presets_imported = 0; + try { + presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToStdString()); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + + // Load the currently selected preset into the GUI, update the preset selection box. + wxGetApp().load_current_presets(); + + const auto message = wxString::Format(_(L("%d presets successfully imported.")), presets_imported); + Slic3r::GUI::show_info(this, message, "Info"); +} + +// Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. +// Also update the platter with the new presets. +void MainFrame::load_config(const DynamicPrintConfig& config) +{ + for (auto tab : wxGetApp().tabs_list) + tab->load_config(config); + if (m_plater) + m_plater->on_config_change(config); +} + +void MainFrame::select_tab(size_t tab) const +{ + m_tabpanel->SetSelection(tab); +} + +// Set a camera direction, zoom to all objects. +void MainFrame::select_view(const std::string& direction) +{ + if (m_plater) + m_plater->select_view(direction); +} + +void MainFrame::on_presets_changed(SimpleEvent &event) +{ + auto *tab = dynamic_cast(event.GetEventObject()); + wxASSERT(tab != nullptr); + if (tab == nullptr) { + return; + } + + // Update preset combo boxes(Print settings, Filament, Material, Printer) from their respective tabs. + auto presets = tab->get_presets(); + if (m_plater != nullptr && presets != nullptr) { + + // FIXME: The preset type really should be a property of Tab instead + Slic3r::Preset::Type preset_type = tab->type(); + if (preset_type == Slic3r::Preset::TYPE_INVALID) { + wxASSERT(false); + return; + } + + m_plater->on_config_change(*tab->get_config()); + m_plater->sidebar().update_presets(preset_type); + } +} + +void MainFrame::on_value_changed(wxCommandEvent& event) +{ + auto *tab = dynamic_cast(event.GetEventObject()); + wxASSERT(tab != nullptr); + if (tab == nullptr) + return; + + auto opt_key = event.GetString(); + if (m_plater) { + m_plater->on_config_change(*tab->get_config()); // propagate config change events to the plater + if (opt_key == "extruders_count") { + auto value = event.GetInt(); + m_plater->on_extruders_change(value); + } + } + // Don't save while loading for the first time. + if (m_loaded) { + AppConfig &cfg = *wxGetApp().app_config; + if (cfg.get("autosave") == "1") + cfg.save(); + } +} + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void MainFrame::update_ui_from_settings() +{ + bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; + m_menu_item_reslice_now->Enable(bp_on); + m_plater->sidebar().show_reslice(!bp_on); + m_plater->sidebar().Layout(); + if (m_plater) + m_plater->update_ui_from_settings(); + for (auto tab: wxGetApp().tabs_list) + tab->update_ui_from_settings(); +} + +std::string MainFrame::get_base_name(const wxString full_name) const +{ + return boost::filesystem::path(full_name).filename().string(); +} + +std::string MainFrame::get_dir_name(const wxString full_name) const +{ + return boost::filesystem::path(full_name).parent_path().string(); +} + + +} // GUI +} // Slic3r diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp new file mode 100644 index 000000000..2559b5ed1 --- /dev/null +++ b/src/slic3r/GUI/MainFrame.hpp @@ -0,0 +1,105 @@ +#ifndef slic3r_MainFrame_hpp_ +#define slic3r_MainFrame_hpp_ + +#include "libslic3r/PrintConfig.hpp" + +#include +#include + +#include +#include + +#include "Plater.hpp" +#include "Event.hpp" + +class wxNotebook; +class wxProgressDialog; + +namespace Slic3r { + +class ProgressStatusBar; + +namespace GUI +{ +class Tab; + +enum QuickSlice +{ + qsUndef = 0, + qsReslice = 1, + qsSaveAs = 2, + qsExportSVG = 4, + qsExportPNG = 8 +}; + +struct PresetTab { + std::string name; + Tab* panel; + PrinterTechnology technology; +}; + +class MainFrame : public wxFrame +{ + bool m_no_plater; + bool m_loaded; + int m_lang_ch_event; + int m_preferences_event; + + wxString m_qs_last_input_file = wxEmptyString; + wxString m_qs_last_output_file = wxEmptyString; + wxString m_last_config = wxEmptyString; + + wxMenuItem* m_menu_item_repeat { nullptr }; + wxMenuItem* m_menu_item_reslice_now { nullptr }; + + std::string get_base_name(const wxString full_name) const ; + std::string get_dir_name(const wxString full_name) const ; + + void on_presets_changed(SimpleEvent&); + void on_value_changed(wxCommandEvent&); + + bool can_save() const; + bool can_export_model() const; + bool can_export_gcode() const; + bool can_change_view() const; + bool can_select() const; + bool can_delete() const; + bool can_delete_all() const; + +public: + MainFrame() {} + MainFrame(const bool no_plater, const bool loaded); + ~MainFrame() {} + + Plater* plater() { return m_plater; } + + void init_tabpanel(); + void create_preset_tabs(); + void add_created_tab(Tab* panel); + void init_menubar(); + + void update_ui_from_settings(); + bool is_loaded() const { return m_loaded; } + bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); } + + void quick_slice(const int qs = qsUndef); + void reslice_now(); + void repair_stl(); + void export_config(); + void load_config_file(wxString file = wxEmptyString); + void export_configbundle(); + void load_configbundle(wxString file = wxEmptyString); + void load_config(const DynamicPrintConfig& config); + void select_tab(size_t tab) const; + void select_view(const std::string& direction); + + Plater* m_plater { nullptr }; + wxNotebook* m_tabpanel { nullptr }; + wxProgressDialog* m_progress_dialog { nullptr }; + ProgressStatusBar* m_statusbar { nullptr }; +}; + +} // GUI +} //Slic3r + +#endif // slic3r_MainFrame_hpp_ diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 58679ed9e..ae7b40484 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -10,6 +10,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "I18N.hpp" #include "ConfigWizard.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index b831ed050..c5a85e3a6 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -5,7 +5,8 @@ #include #include #include -#include "Utils.hpp" +#include "libslic3r/Utils.hpp" +#include "I18N.hpp" namespace Slic3r { namespace GUI { @@ -22,18 +23,18 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co // is the normal type. if (opt.gui_type.compare("select") == 0) { } else if (opt.gui_type.compare("select_open") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("color") == 0) { - m_fields.emplace(id, STDMOVE(ColourPicker::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(ColourPicker::Create(parent(), opt, id))); } else if (opt.gui_type.compare("f_enum_open") == 0 || opt.gui_type.compare("i_enum_open") == 0 || opt.gui_type.compare("i_enum_closed") == 0) { - m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { - m_fields.emplace(id, STDMOVE(SliderCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(SliderCtrl::Create(parent(), opt, id))); } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else if (opt.gui_type.compare("legend") == 0) { // StaticText - m_fields.emplace(id, STDMOVE(StaticText::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(StaticText::Create(parent(), opt, id))); } else { switch (opt.type) { case coFloatOrPercent: @@ -43,21 +44,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, STDMOVE(TextCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(TextCtrl::Create(parent(), opt, id))); break; case coBool: case coBools: - m_fields.emplace(id, STDMOVE(CheckBox::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(CheckBox::Create(parent(), opt, id))); break; case coInt: case coInts: - m_fields.emplace(id, STDMOVE(SpinCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(SpinCtrl::Create(parent(), opt, id))); break; case coEnum: - m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); break; case coPoints: - m_fields.emplace(id, STDMOVE(PointCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(PointCtrl::Create(parent(), opt, id))); break; case coNone: break; default: @@ -66,26 +67,31 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co } // Grab a reference to fields for convenience const t_field& field = m_fields[id]; - field->m_on_change = [this](std::string opt_id, boost::any value){ + field->m_on_change = [this](const std::string& opt_id, const boost::any& value) { //! This function will be called from Field. //! Call OptionGroup._on_change(...) if (!m_disabled) this->on_change_OG(opt_id, value); }; - field->m_on_kill_focus = [this](){ + field->m_on_kill_focus = [this](const std::string& opt_id) { //! This function will be called from Field. if (!m_disabled) - this->on_kill_focus(); + this->on_kill_focus(opt_id); + }; + field->m_on_set_focus = [this](const std::string& opt_id) { + //! This function will be called from Field. + if (!m_disabled) + this->on_set_focus(opt_id); }; field->m_parent = parent(); //! Label to change background color, when option is modified field->m_Label = label; - field->m_back_to_initial_value = [this](std::string opt_id){ + field->m_back_to_initial_value = [this](std::string opt_id) { if (!m_disabled) this->back_to_initial_value(opt_id); }; - field->m_back_to_sys_value = [this](std::string opt_id){ + field->m_back_to_sys_value = [this](std::string opt_id) { if (!this->m_disabled) this->back_to_sys_value(opt_id); }; @@ -97,8 +103,8 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) { if (!m_show_modified_btns) { - field->m_Undo_btn->Hide(); - field->m_Undo_to_sys_btn->Hide(); + field->m_Undo_btn->set_as_hidden(); + field->m_Undo_to_sys_btn->set_as_hidden(); return; } @@ -106,9 +112,9 @@ void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& fiel sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); } -void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) { -//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){ - if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){ +void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = nullptr*/) { +//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)) { + if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width) { if (line.sizer != nullptr) { sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); return; @@ -123,6 +129,10 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* for (auto opt : option_set) m_options.emplace(opt.opt_id, opt); + // add mode value for current line to m_options_mode + if (!option_set.empty()) + m_options_mode.push_back(option_set[0].opt.mode); + // if we have a single option with no label, no sidetext just add it directly to sizer if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && @@ -156,16 +166,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* #endif /* __WXGTK__ */ // if we have an extra column, build it - if (extra_column) { - if (extra_column) { - grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); - } - else { - // if the callback provides no sizer for the extra cell, put a spacer - grid_sizer->AddSpacer(1); - } - } - + if (extra_column) + grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); // Build a label if we have it wxStaticText* label=nullptr; @@ -182,33 +184,32 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug if (!line.near_label_widget) - grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | - (m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); else { // If we're here, we have some widget near the label // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); sizer->Add(line.near_label_widget(parent()), 0, wxRIGHT, 7); - sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | - (m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5); + sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } if (line.label_tooltip.compare("") != 0) label->SetToolTip(line.label_tooltip); } + if (full_Label != nullptr) + *full_Label = label; // Initiate the pointer to the control of the full label, if we need this one. // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { auto wgt = line.widget(parent()); // If widget doesn't have label, don't use border grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); - if (colored_Label != nullptr) *colored_Label = label; return; } // If we're here, we have more than one option or a single option with sidetext // so we need a horizontal sizer to arrange these things - auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL); + auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && @@ -218,23 +219,16 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* add_undo_buttuns_to_sizer(sizer, field); if (is_window_field(field)) - sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | - wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2); + sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) | + wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2); if (is_sizer_field(field)) - sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0); return; } for (auto opt : option_set) { ConfigOptionDef option = opt.opt; - wxSizer* sizer_tmp; - if (m_flag == ogSIDE_OPTIONS_VERTICAL){ - auto sz = new wxFlexGridSizer(1, 3, 2, 2); - sz->RemoveGrowableCol(2); - sizer_tmp = sz; - } - else - sizer_tmp = sizer; + wxSizer* sizer_tmp = sizer; // add label if any if (option.label != "") { wxString str_label = _(option.label); @@ -244,7 +238,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // L_str(option.label); label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); - sizer_tmp->Add(label, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0); } // add field @@ -260,7 +254,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetFont(sidetext_font); - sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4); + sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); field->set_side_text_ptr(sidetext); } @@ -269,13 +263,10 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* sizer_tmp->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification } - if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back()) + if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back()) { sizer_tmp->AddSpacer(6); } - - if (m_flag == ogSIDE_OPTIONS_VERTICAL) - sizer->Add(sizer_tmp, 0, wxALIGN_RIGHT|wxALL, 0); } // add extra sizers if any for (auto extra_widget : line.get_extra_widgets()) { @@ -291,6 +282,12 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::on_set_focus(const std::string& opt_key) +{ + if (m_set_focus != nullptr) + m_set_focus(opt_key); +} + void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) { if (m_on_change != nullptr) m_on_change(opt_id, value); @@ -329,7 +326,7 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b // get value //! auto field_value = get_value(opt_id); if (option.gui_flags.compare("serialized")==0) { - if (opt_index != -1){ + if (opt_index != -1) { // die "Can't set serialized option indexed value" ; } change_opt_value(*m_config, opt_key, value); @@ -371,7 +368,7 @@ void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key) void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key) { boost::any value; - if (opt_key == "extruders_count"){ + if (opt_key == "extruders_count") { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); value = int(nozzle_diameter->values.size()); } @@ -392,7 +389,16 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, on_change_OG(opt_key, get_value(opt_key)); } -void ConfigOptionsGroup::reload_config(){ +void ConfigOptionsGroup::on_kill_focus(const std::string& opt_key) +{ + if (m_fill_empty_value) { + m_fill_empty_value(opt_key); + return; + } + reload_config(); +} + +void ConfigOptionsGroup::reload_config() { for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { auto opt_id = it->first; std::string opt_key = m_opt_map.at(opt_id).first; @@ -403,7 +409,50 @@ void ConfigOptionsGroup::reload_config(){ } -boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize){ +void ConfigOptionsGroup::Hide() +{ + Show(false); +} + +void ConfigOptionsGroup::Show(const bool show) +{ + sizer->ShowItems(show); +#ifdef __WXGTK__ + m_panel->Show(show); + m_grid_sizer->Show(show); +#endif /* __WXGTK__ */ +} + +bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) { + if (m_options_mode.empty()) + return true; + if (m_grid_sizer->GetEffectiveRowsCount() != m_options_mode.size() && + m_options_mode.size() == 1) + return m_options_mode[0] <= mode; + + Show(true); + + int coef = 0; + int hidden_row_cnt = 0; + const int cols = m_grid_sizer->GetCols(); + for (auto opt_mode : m_options_mode) { + const bool show = opt_mode <= mode; + if (!show) { + hidden_row_cnt++; + for (int i = 0; i < cols; ++i) + m_grid_sizer->Show(coef + i, show); + } + coef+= cols; + } + + if (hidden_row_cnt == m_options_mode.size()) { + sizer->ShowItems(false); + return false; + } + return true; +} + +boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize) { if (deserialize) { // Want to edit a vector value(currently only multi - strings) in a single edit box. @@ -427,7 +476,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config boost::any ret; wxString text_value = wxString(""); const ConfigOptionDef* opt = config.def()->get(opt_key); - switch (opt->type){ + switch (opt->type) { case coFloatOrPercent:{ const auto &value = *config.option(opt_key); if (value.percent) @@ -460,13 +509,13 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = static_cast(config.opt_string(opt_key)); break; case coStrings: - if (opt_key.compare("compatible_printers") == 0){ + if (opt_key.compare("compatible_printers") == 0) { ret = config.option(opt_key)->values; break; } if (config.option(opt_key)->values.empty()) ret = text_value; - else if (opt->gui_flags.compare("serialized") == 0){ + else if (opt->gui_flags.compare("serialized") == 0) { std::vector values = config.option(opt_key)->values; if (!values.empty() && values[0].compare("") != 0) for (auto el : values) @@ -490,24 +539,27 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config break; case coEnum:{ if (opt_key.compare("top_fill_pattern") == 0 || opt_key.compare("bottom_fill_pattern") == 0 || - opt_key.compare("fill_pattern") == 0 ){ + opt_key.compare("fill_pattern") == 0 ) { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("gcode_flavor") == 0 ){ + else if (opt_key.compare("gcode_flavor") == 0 ) { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("support_material_pattern") == 0){ + else if (opt_key.compare("support_material_pattern") == 0) { ret = static_cast(config.option>(opt_key)->value); } else if (opt_key.compare("seam_position") == 0 || opt_key.compare("perimeter_loop_seam") == 0) { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("host_type") == 0){ - ret = static_cast(config.option>(opt_key)->value); - } - else if (opt_key.compare("infill_dense_algo") == 0){ + else if (opt_key.compare("host_type") == 0) { + ret = static_cast(config.option>(opt_key)->value); + } + else if (opt_key.compare("infill_dense_algo") == 0){ ret = static_cast(config.option>(opt_key)->value); - } + } + else if (opt_key.compare("display_orientation") == 0) { + ret = static_cast(config.option>(opt_key)->value); + } } break; case coPoints: @@ -523,13 +575,14 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config return ret; } -Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index){ +Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index) +{ Field* field = get_field(opt_key); if (field != nullptr) return field; std::string opt_id = ""; for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { - if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){ + if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second) { opt_id = it->first; break; } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 4941e5453..9097dcab6 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -14,6 +14,7 @@ #include "libslic3r/libslic3r.h" #include "Field.hpp" +#include "GUI_App.hpp" // Translate the ifdef #ifdef __WXOSX__ @@ -26,11 +27,6 @@ namespace Slic3r { namespace GUI { -enum ogDrawFlag{ - ogDEFAULT, - ogSIDE_OPTIONS_VERTICAL -}; - /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window). using widget_t = std::function;//!std::function; @@ -89,7 +85,9 @@ public: size_t label_width {200}; wxSizer* sizer {nullptr}; column_t extra_column {nullptr}; - t_change m_on_change {nullptr}; + t_change m_on_change { nullptr }; + t_kill_focus m_fill_empty_value { nullptr }; + t_kill_focus m_set_focus { nullptr }; std::function m_get_initial_config{ nullptr }; std::function m_get_sys_config{ nullptr }; std::function have_sys_config{ nullptr }; @@ -114,7 +112,7 @@ public: } #endif /* __WXGTK__ */ - void append_line(const Line& line, wxStaticText** colored_Label = nullptr); + void append_line(const Line& line, wxStaticText** full_Label = nullptr); Line create_single_option_line(const Option& option) const; void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } @@ -150,20 +148,20 @@ public: inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } - void set_flag(ogDrawFlag flag) { m_flag = flag; } void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); } - void set_show_modified_btns_val(bool show) { - m_show_modified_btns = show; + void set_show_modified_btns_val(bool show) { + m_show_modified_btns = show; } OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, - ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : - m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), - staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ + column_t extra_clmn = nullptr) : + m_parent(_parent), title(title), + m_show_modified_btns(is_tab_opt), + staticbox(title!=""), extra_column(extra_clmn) { if (staticbox) { stb = new wxStaticBox(_parent, wxID_ANY, title); - stb->SetFont(bold_font()); + stb->SetFont(wxGetApp().bold_font()); } sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; @@ -171,7 +169,7 @@ public: if (extra_column != nullptr) num_columns++; m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); - static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); + static_cast(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 ); #ifdef __WXGTK__ m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); sizer->Fit(m_panel); @@ -181,11 +179,12 @@ public: #endif /* __WXGTK__ */ } - wxGridSizer* get_grid_sizer(){ return m_grid_sizer; } + wxGridSizer* get_grid_sizer() { return m_grid_sizer; } protected: std::map m_options; wxWindow* m_parent {nullptr}; + std::vector m_options_mode; /// Field list, contains unique_ptrs of the derived type. /// using types that need to know what it is beyond the public interface @@ -196,8 +195,6 @@ protected: // "true" if option is created in preset tabs bool m_show_modified_btns{ false }; - ogDrawFlag m_flag{ ogDEFAULT }; - // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel // inside it before you insert the other controls. @@ -213,23 +210,25 @@ protected: const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); - virtual void on_kill_focus (){}; + virtual void on_kill_focus(const std::string& opt_key) {}; + virtual void on_set_focus(const std::string& opt_key); virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); - virtual void back_to_initial_value(const std::string& opt_key){} - virtual void back_to_sys_value(const std::string& opt_key){} + virtual void back_to_initial_value(const std::string& opt_key) {} + virtual void back_to_sys_value(const std::string& opt_key) {} }; class ConfigOptionsGroup: public OptionsGroup { public: ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, - bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : - OptionsGroup(parent, title, is_tab_opt, flag, extra_clmn), m_config(_config) {} + bool is_tab_opt = false, column_t extra_clmn = nullptr) : + OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(_config) {} /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; bool m_full_labels {0}; t_opt_map m_opt_map; + void set_config(DynamicPrintConfig* config) { m_config = config; } Option get_option(const std::string& opt_key, int opt_index = -1); Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{ Option option = get_option(title, idx); @@ -248,8 +247,12 @@ public: void back_to_initial_value(const std::string& opt_key) override; void back_to_sys_value(const std::string& opt_key) override; void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key); - void on_kill_focus() override{ reload_config();} + void on_kill_focus(const std::string& opt_key) override;// { reload_config(); } void reload_config(); + // return value shows visibility : false => all options are hidden + void Hide(); + void Show(const bool show); + bool update_visibility(ConfigOptionMode mode); boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize); // return option value from config boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); @@ -260,8 +263,8 @@ public: class ogStaticText :public wxStaticText{ public: ogStaticText() {} - ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){} - ~ogStaticText(){} + ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize) {} + ~ogStaticText() {} void SetText(const wxString& value, bool wrap = true); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp new file mode 100644 index 000000000..8db804c12 --- /dev/null +++ b/src/slic3r/GUI/Plater.cpp @@ -0,0 +1,3340 @@ +#include "Plater.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Format/STL.hpp" +#include "libslic3r/Format/AMF.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/SLA/SLARotfinder.hpp" +#include "libslic3r/Utils.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_Utils.hpp" +#include "wxExtensions.hpp" +#include "MainFrame.hpp" +#include "3DScene.hpp" +#include "GLCanvas3D.hpp" +#include "GLToolbar.hpp" +#include "GUI_Preview.hpp" +#include "Tab.hpp" +#include "PresetBundle.hpp" +#include "BackgroundSlicingProcess.hpp" +#include "ProgressStatusBar.hpp" +#include "PrintHostDialogs.hpp" +#include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/FixModelByWin10.hpp" + +#include // Needs to be last because reasons :-/ +#include "WipeTowerDialog.hpp" + +using boost::optional; +namespace fs = boost::filesystem; +using Slic3r::_3DScene; +using Slic3r::Preset; +using Slic3r::PrintHostJob; + + +namespace Slic3r { +namespace GUI { + +wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); +wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); +wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); + +// Sidebar widgets + +// struct InfoBox : public wxStaticBox +// { +// InfoBox(wxWindow *parent, const wxString &label) : +// wxStaticBox(parent, wxID_ANY, label) +// { +// SetFont(GUI::small_font().Bold()); +// } +// }; + +class ObjectInfo : public wxStaticBoxSizer +{ +public: + ObjectInfo(wxWindow *parent); + + wxStaticBitmap *manifold_warning_icon; + wxStaticText *info_size; + wxStaticText *info_volume; + wxStaticText *info_facets; + wxStaticText *info_materials; + wxStaticText *info_manifold; + bool showing_manifold_warning_icon; + void show_sizer(bool show); +}; + +ObjectInfo::ObjectInfo(wxWindow *parent) : + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Info"))), wxVERTICAL) +{ + GetStaticBox()->SetFont(wxGetApp().bold_font()); + + auto *grid_sizer = new wxFlexGridSizer(4, 5, 5); + grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + grid_sizer->AddGrowableCol(1, 1); + grid_sizer->AddGrowableCol(3, 1); + + auto init_info_label = [parent, grid_sizer](wxStaticText **info_label, wxString text_label) { + auto *text = new wxStaticText(parent, wxID_ANY, text_label); + text->SetFont(wxGetApp().small_font()); + *info_label = new wxStaticText(parent, wxID_ANY, ""); + (*info_label)->SetFont(wxGetApp().small_font()); + grid_sizer->Add(text, 0); + grid_sizer->Add(*info_label, 0); + }; + + init_info_label(&info_size, _(L("Size:"))); + init_info_label(&info_volume, _(L("Volume:"))); + init_info_label(&info_facets, _(L("Facets:"))); + init_info_label(&info_materials, _(L("Materials:"))); + Add(grid_sizer, 0, wxEXPAND); + + auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _(L("Manifold:"))); + info_manifold_text->SetFont(wxGetApp().small_font()); + info_manifold = new wxStaticText(parent, wxID_ANY, ""); + info_manifold->SetFont(wxGetApp().small_font()); + wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap); + auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); + sizer_manifold->Add(info_manifold_text, 0); + sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); + sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); + Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4); +} + +void ObjectInfo::show_sizer(bool show) +{ + Show(show); + if (show) + manifold_warning_icon->Show(showing_manifold_warning_icon && show); +} + +enum SlisedInfoIdx +{ + siFilament_m, + siFilament_mm3, + siFilament_g, + siCost, + siEstimatedTime, + siWTNumbetOfToolchanges, + + siCount +}; + +class SlicedInfo : public wxStaticBoxSizer +{ +public: + SlicedInfo(wxWindow *parent); + void SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label=""); + +private: + std::vector> info_vec; +}; + +SlicedInfo::SlicedInfo(wxWindow *parent) : + wxStaticBoxSizer(new wxStaticBox(parent, wxID_ANY, _(L("Sliced Info"))), wxVERTICAL) +{ + GetStaticBox()->SetFont(wxGetApp().bold_font()); + + auto *grid_sizer = new wxFlexGridSizer(2, 5, 15); + grid_sizer->SetFlexibleDirection(wxVERTICAL); + + info_vec.reserve(siCount); + + auto init_info_label = [this, parent, grid_sizer](wxString text_label) { + auto *text = new wxStaticText(parent, wxID_ANY, text_label); + text->SetFont(wxGetApp().small_font()); + auto info_label = new wxStaticText(parent, wxID_ANY, "N/A"); + info_label->SetFont(wxGetApp().small_font()); + grid_sizer->Add(text, 0); + grid_sizer->Add(info_label, 0); + info_vec.push_back(std::pair(text, info_label)); + }; + + init_info_label(_(L("Used Filament (m)"))); + init_info_label(_(L("Used Filament (mm³)"))); + init_info_label(_(L("Used Filament (g)"))); + init_info_label(_(L("Cost"))); + init_info_label(_(L("Estimated printing time"))); + init_info_label(_(L("Number of tool changes"))); + + Add(grid_sizer, 0, wxEXPAND); + this->Show(false); +} + +void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/) +{ + const bool show = text != "N/A"; + if (show) + info_vec[idx].second->SetLabelText(text); + if (!new_label.IsEmpty()) + info_vec[idx].first->SetLabelText(new_label); + info_vec[idx].first->Show(show); + info_vec[idx].second->Show(show); +} + +PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,-1), 0, nullptr, wxCB_READONLY), + preset_type(preset_type), + last_selected(wxNOT_FOUND) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + auto selected_item = this->GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker == LABEL_ITEM_MARKER) { + this->SetSelection(this->last_selected); + evt.StopPropagation(); + } else if (this->last_selected != selected_item) { + this->last_selected = selected_item; + evt.SetInt(this->preset_type); + evt.Skip(); + } else { + evt.StopPropagation(); + } + }); + + if (preset_type == Slic3r::Preset::TYPE_FILAMENT) + { + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { + if (extruder_idx < 0 || event.GetLogicalPosition(wxClientDC(this)).x > 24) { + // Let the combo box process the mouse click. + event.Skip(); + return; + } + + // Swallow the mouse click and open the color picker. + auto data = new wxColourData(); + data->SetChooseFull(1); + auto dialog = new wxColourDialog(wxGetApp().mainframe, data); + if (dialog->ShowModal() == wxID_OK) { + DynamicPrintConfig cfg = *wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); + + //FIXME this is too expensive to call full_config to get just the extruder color! + auto colors = static_cast(wxGetApp().preset_bundle->full_config().option("extruder_colour")->clone()); + colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); + + cfg.set_key_value("extruder_colour", colors); + + wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg); + wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this); + wxGetApp().plater()->on_config_change(cfg); + } + dialog->Destroy(); + }); + } +} + +PresetComboBox::~PresetComboBox() {} + + +void PresetComboBox::set_label_marker(int item) +{ + this->SetClientData(item, (void*)LABEL_ITEM_MARKER); +} + +void PresetComboBox::check_selection() +{ + if (this->last_selected != GetSelection()) + this->last_selected = GetSelection(); +} + +// Frequently changed parameters + +class FreqChangedParams : public OG_Settings +{ + double m_brim_width = 0.0; + wxButton* m_wiping_dialog_button{ nullptr }; +public: + FreqChangedParams(wxWindow* parent, const int label_width); + ~FreqChangedParams() {} + + wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; } + void Show(const bool show); +}; + +FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : + OG_Settings(parent, false) +{ + DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; + + m_og->set_config(config); + m_og->label_width = label_width; + + m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { + TabPrint* tab_print = nullptr; + for (size_t i = 0; i < wxGetApp().tab_panel()->GetPageCount(); ++i) { + Tab *tab = dynamic_cast(wxGetApp().tab_panel()->GetPage(i)); + if (!tab) + continue; + if (tab->name() == "print") { + tab_print = static_cast(tab); + break; + } + } + if (tab_print == nullptr) + return; + + if (opt_key == "fill_density") { + value = m_og->get_config_value(*config, opt_key); + tab_print->set_value(opt_key, value); + tab_print->update(); + } + else{ + DynamicPrintConfig new_conf = *config; + if (opt_key == "brim") { + double new_val; + double brim_width = config->opt_float("brim_width"); + if (boost::any_cast(value) == true) + { + new_val = m_brim_width == 0.0 ? 5 : + m_brim_width < 0.0 ? m_brim_width * (-1) : + m_brim_width; + } + else { + m_brim_width = brim_width * (-1); + new_val = 0; + } + new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); + } + else { //(opt_key == "support") + const wxString& selection = boost::any_cast(value); + + auto support_material = selection == _("None") ? false : true; + new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); + + if (selection == _("Everywhere")) + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); + else if (selection == _("Support on build plate only")) + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); + } + tab_print->load_config(new_conf); + } + + tab_print->update_dirty(); + }; + + Option option = m_og->get_option("fill_density"); + option.opt.sidetext = ""; + option.opt.full_width = true; + m_og->append_single_option_line(option); + + ConfigOptionDef def; + + def.label = L("Support"); + def.type = coStrings; + def.gui_type = "select_open"; + def.tooltip = L("Select what kind of support do you need"); + def.enum_labels.push_back(L("None")); + def.enum_labels.push_back(L("Support on build plate only")); + def.enum_labels.push_back(L("Everywhere")); + std::string selection = !config->opt_bool("support_material") ? + "None" : + config->opt_bool("support_material_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + def.default_value = new ConfigOptionStrings{ selection }; + option = Option(def, "support"); + option.opt.full_width = true; + m_og->append_single_option_line(option); + + m_brim_width = config->opt_float("brim_width"); + def.label = L("Brim"); + def.type = coBool; + def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); + def.gui_type = ""; + def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; + option = Option(def, "brim"); + m_og->append_single_option_line(option); + + + Line line = { "", "" }; + line.widget = [config, this](wxWindow* parent) { + m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_wiping_dialog_button); + m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) + { + auto &config = wxGetApp().preset_bundle->project_config; + const std::vector &init_matrix = (config.option("wiping_volumes_matrix"))->values; + const std::vector &init_extruders = (config.option("wiping_volumes_extruders"))->values; + + WipingDialog dlg(parent, cast(init_matrix), cast(init_extruders)); + + if (dlg.ShowModal() == wxID_OK) { + std::vector matrix = dlg.get_matrix(); + std::vector extruders = dlg.get_extruders(); + (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(), matrix.end()); + (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(), extruders.end()); + wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); + } + })); + return sizer; + }; + m_og->append_line(line); +} + + +void FreqChangedParams::Show(const bool show) +{ + bool is_wdb_shown = m_wiping_dialog_button->IsShown(); + m_og->Show(show); + + // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden + if (show && !is_wdb_shown) + m_wiping_dialog_button->Hide(); +} + +// Sidebar / private + +struct Sidebar::priv +{ + Plater *plater; + + wxScrolledWindow *scrolled; + + wxFlexGridSizer *sizer_presets; + PresetComboBox *combo_print; + std::vector combos_filament; + wxBoxSizer *sizer_filaments; + PresetComboBox *combo_sla_print; + PresetComboBox *combo_sla_material; + PresetComboBox *combo_printer; + + wxBoxSizer *sizer_params; + FreqChangedParams *frequently_changed_parameters; + ObjectList *object_list; + ObjectManipulation *object_manipulation; + ObjectSettings *object_settings; + ObjectInfo *object_info; + SlicedInfo *sliced_info; + + wxButton *btn_export_gcode; + wxButton *btn_reslice; + wxButton *btn_send_gcode; + + priv(Plater *plater) : plater(plater) {} + + void show_preset_comboboxes(); +}; + +void Sidebar::priv::show_preset_comboboxes() +{ + const bool showSLA = plater->printer_technology() == ptSLA; + + wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); + + for (size_t i = 0; i < 4; ++i) + sizer_presets->Show(i, !showSLA); + + for (size_t i = 4; i < 8; ++i) { + if (sizer_presets->IsShown(i) != showSLA) + sizer_presets->Show(i, showSLA); + } + + frequently_changed_parameters->Show(!showSLA); + + scrolled->GetParent()->Layout(); + scrolled->Refresh(); +} + + +// Sidebar / public + +Sidebar::Sidebar(Plater *parent) + : wxPanel(parent), p(new priv(parent)) +{ + p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(400, -1)); + p->scrolled->SetScrollbars(0, 1, 1, 1); + + // Sizer in the scrolled area + auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); + p->scrolled->SetSizer(scrolled_sizer); + + // The preset chooser + p->sizer_presets = new wxFlexGridSizer(5, 2, 1, 2); + p->sizer_presets->AddGrowableCol(1, 1); + p->sizer_presets->SetFlexibleDirection(wxBOTH); + p->sizer_filaments = new wxBoxSizer(wxVERTICAL); + + auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { + auto *text = new wxStaticText(p->scrolled, wxID_ANY, label); + text->SetFont(wxGetApp().small_font()); + *combo = new PresetComboBox(p->scrolled, preset_type); + + auto *sizer_presets = this->p->sizer_presets; + auto *sizer_filaments = this->p->sizer_filaments; + sizer_presets->Add(text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + if (! filament) { + sizer_presets->Add(*combo, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 1); + } else { + sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1); + (*combo)->set_extruder_idx(0); + sizer_presets->Add(sizer_filaments, 1, wxEXPAND); + } + }; + + p->combos_filament.push_back(nullptr); + init_combo(&p->combo_print, _(L("Print settings")), Preset::TYPE_PRINT, false); + init_combo(&p->combos_filament[0], _(L("Filament")), Preset::TYPE_FILAMENT, true); + init_combo(&p->combo_sla_print, _(L("SLA print")), Preset::TYPE_SLA_PRINT, false); + init_combo(&p->combo_sla_material, _(L("SLA material")), Preset::TYPE_SLA_MATERIAL, false); + init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); + + // calculate width of the preset labels + p->sizer_presets->Layout(); + const wxArrayInt& ar = p->sizer_presets->GetColWidths(); + int label_width = ar.IsEmpty() ? 100 : ar.front()-4; + + p->sizer_params = new wxBoxSizer(wxVERTICAL); + + // Frequently changed parameters + p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, label_width); + p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxBOTTOM | wxLEFT, 2); + + // Object List + p->object_list = new ObjectList(p->scrolled); + p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND | wxTOP, 20); + + // Object Manipulations + p->object_manipulation = new ObjectManipulation(p->scrolled); + p->object_manipulation->Hide(); + p->sizer_params->Add(p->object_manipulation->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); + + // Frequently Object Settings + p->object_settings = new ObjectSettings(p->scrolled); + p->object_settings->Hide(); + p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); + + wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); + p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); + p->btn_send_gcode->SetBitmap(arrow_up); + p->btn_send_gcode->SetFont(wxGetApp().bold_font()); + p->btn_send_gcode->Hide(); + + // Info boxes + p->object_info = new ObjectInfo(p->scrolled); + p->sliced_info = new SlicedInfo(p->scrolled); + + // Sizer in the scrolled area + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2); + scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND); + scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); + scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); + + // Buttons underneath the scrolled area + p->btn_export_gcode = new wxButton(this, wxID_ANY, _(L("Export G-code…"))); + p->btn_export_gcode->SetFont(wxGetApp().bold_font()); + p->btn_reslice = new wxButton(this, wxID_ANY, _(L("Slice now"))); + p->btn_reslice->SetFont(wxGetApp().bold_font()); + enable_buttons(false); + + auto *btns_sizer = new wxBoxSizer(wxVERTICAL); + btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5); + btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, 5); + btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5); + + auto *sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(p->scrolled, 1, wxEXPAND | wxTOP, 5); + sizer->Add(btns_sizer, 0, wxEXPAND | wxLEFT, 20); + SetSizer(sizer); + + // Events + p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); }); + p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); }); + p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); +} + +Sidebar::~Sidebar() {} + +void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { + *combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT); +// # copy icons from first choice +// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; + + (*combo)->set_extruder_idx(extr_idx); + + auto /***/sizer_filaments = this->p->sizer_filaments; + sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1); +} + +void Sidebar::remove_unused_filament_combos(const int current_extruder_count) +{ + if (current_extruder_count >= p->combos_filament.size()) + return; + auto sizer_filaments = this->p->sizer_filaments; + while (p->combos_filament.size() > current_extruder_count) { + const int last = p->combos_filament.size() - 1; + sizer_filaments->Remove(last); + (*p->combos_filament[last]).Destroy(); + p->combos_filament.pop_back(); + } +} + +void Sidebar::update_presets(Preset::Type preset_type) +{ + PresetBundle &preset_bundle = *wxGetApp().preset_bundle; + + switch (preset_type) { + case Preset::TYPE_FILAMENT: + if (p->combos_filament.size() == 1) { + // Single filament printer, synchronize the filament presets. + const std::string &name = preset_bundle.filaments.get_selected_preset().name; + preset_bundle.set_filament_preset(0, name); + } + + for (size_t i = 0; i < p->combos_filament.size(); i++) { + preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]); + } + + break; + + case Preset::TYPE_PRINT: + preset_bundle.prints.update_platter_ui(p->combo_print); + break; + + case Preset::TYPE_SLA_PRINT: + preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); + break; + + case Preset::TYPE_SLA_MATERIAL: + preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + break; + + case Preset::TYPE_PRINTER: + { + // Update the print choosers to only contain the compatible presets, update the dirty flags. + if (p->plater->printer_technology() == ptFFF) + preset_bundle.prints.update_platter_ui(p->combo_print); + else { + preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); + preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + } + // Update the printer choosers, update the dirty flags. + auto prev_selection = p->combo_printer->GetSelection(); + preset_bundle.printers.update_platter_ui(p->combo_printer); + if (prev_selection != p->combo_printer->GetSelection()) + p->combo_printer->check_selection(); + // Update the filament choosers to only contain the compatible presets, update the color preview, + // update the dirty flags. + if (p->plater->printer_technology() == ptFFF) { + for (size_t i = 0; i < p->combos_filament.size(); ++ i) + preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]); + } + p->show_preset_comboboxes(); + break; + } + + default: break; + } + + // Synchronize config.ini with the current selections. + wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); +} + +ObjectManipulation* Sidebar::obj_manipul() +{ + return p->object_manipulation; +} + +ObjectList* Sidebar::obj_list() +{ + return p->object_list; +} + +ObjectSettings* Sidebar::obj_settings() +{ + return p->object_settings; +} + +wxScrolledWindow* Sidebar::scrolled_panel() +{ + return p->scrolled; +} + +ConfigOptionsGroup* Sidebar::og_freq_chng_params() +{ + return p->frequently_changed_parameters->get_og(); +} + +wxButton* Sidebar::get_wiping_dialog_button() +{ + return p->frequently_changed_parameters->get_wiping_dialog_button(); +} + +void Sidebar::update_objects_list_extruder_column(int extruders_count) +{ + p->object_list->update_objects_list_extruder_column(extruders_count); +} + +void Sidebar::show_info_sizer() +{ + if (!p->plater->is_single_full_object_selection() || + m_mode < ConfigMenuModeExpert || + p->plater->model().objects.empty()) { + p->object_info->Show(false); + return; + } + + int obj_idx = p->plater->get_selected_object_idx(); + + const ModelObject* model_object = p->plater->model().objects[obj_idx]; + // hack to avoid crash when deleting the last object on the bed + if (model_object->volumes.empty()) + { + p->object_info->Show(false); + return; + } + + const ModelInstance* model_instance = !model_object->instances.empty() ? model_object->instances.front() : nullptr; + + auto size = model_object->bounding_box().size(); + p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2))); + p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); + + auto& stats = model_object->volumes.front()->mesh.stl.stats; + auto sf = model_instance->get_scaling_factor(); + p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2) * sf(0) * sf(1) * sf(2))); + p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast(model_object->facets_count()), stats.number_of_parts)); + + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + if (errors > 0) { + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors)")), errors); + p->object_info->info_manifold->SetLabel(tooltip); + + tooltip += wxString::Format(_(L(":\n%d degenerate facets, %d edges fixed, %d facets removed, " + "%d facets added, %d facets reversed, %d backwards edges")), + stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, + stats.facets_added, stats.facets_reversed, stats.backwards_edges); + + p->object_info->showing_manifold_warning_icon = true; + p->object_info->info_manifold->SetToolTip(tooltip); + p->object_info->manifold_warning_icon->SetToolTip(tooltip); + } + else { + p->object_info->info_manifold->SetLabel(L("Yes")); + p->object_info->showing_manifold_warning_icon = false; + p->object_info->info_manifold->SetToolTip(""); + p->object_info->manifold_warning_icon->SetToolTip(""); + } + + p->object_info->show_sizer(true); +} + +void Sidebar::show_sliced_info_sizer(const bool show) +{ + wxWindowUpdateLocker freeze_guard(this); + + p->sliced_info->Show(show); + if (show) { + const PrintStatistics& ps = p->plater->fff_print().print_statistics(); + const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; + + wxString new_label = _(L("Used Filament (m)")); + if (is_wipe_tower) + new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); + + wxString info_text = is_wipe_tower ? + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, + (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, + ps.total_wipe_tower_filament / 1000) : + wxString::Format("%.2f", ps.total_used_filament / 1000); + p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); + + p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume)); + p->sliced_info->SetTextAndShow(siFilament_g, wxString::Format("%.2f", ps.total_weight)); + + + new_label = _(L("Cost")); + if (is_wipe_tower) + new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); + + info_text = is_wipe_tower ? + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, + (ps.total_cost - ps.total_wipe_tower_cost), + ps.total_wipe_tower_cost) : + wxString::Format("%.2f", ps.total_cost); + p->sliced_info->SetTextAndShow(siCost, info_text, new_label); + + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + else { + new_label = "Estimated printing time :"; + info_text = ""; + if (ps.estimated_normal_print_time != "N/A") { + new_label += wxString::Format("\n - %s", _(L("normal mode"))); + info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); + } + if (ps.estimated_silent_print_time != "N/A") { + new_label += wxString::Format("\n - %s", _(L("silent mode"))); + info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); + } + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + } + + // if there is a wipe tower, insert number of toolchanges info into the array: + p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A"); + } + + Layout(); + p->scrolled->Refresh(); +} + +void Sidebar::enable_buttons(bool enable) +{ + p->btn_reslice->Enable(enable); + p->btn_export_gcode->Enable(enable); + p->btn_send_gcode->Enable(enable); +} + +void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); } +void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); } + +bool Sidebar::is_multifilament() +{ + return p->combos_filament.size() > 1; +} + + +std::vector& Sidebar::combos_filament() +{ + return p->combos_filament; +} + +// Plater::DropTarget + +class PlaterDropTarget : public wxFileDropTarget +{ +public: + PlaterDropTarget(Plater *plater) : plater(plater) {} + + virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); + +private: + Plater *plater; + + static const std::regex pattern_drop; +}; + +const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase); + +bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) +{ + std::vector paths; + + for (const auto &filename : filenames) { + fs::path path(filename); + + if (std::regex_match(path.string(), pattern_drop)) { + paths.push_back(std::move(path)); + } else { + return false; + } + } + + plater->load_files(paths); + return true; +} + +// Plater / private +struct Plater::priv +{ + // PIMPL back pointer ("Q-Pointer") + Plater *q; + MainFrame *main_frame; + + // Object popup menu + wxMenu object_menu; + // Part popup menu + wxMenu part_menu; + // SLA-Object popup menu + wxMenu sla_object_menu; + + // Data + Slic3r::DynamicPrintConfig *config; // FIXME: leak? + Slic3r::Print fff_print; + Slic3r::SLAPrint sla_print; + Slic3r::Model model; + PrinterTechnology printer_technology = ptFFF; + Slic3r::GCodePreviewData gcode_preview_data; + + // GUI elements +#if ENABLE_REMOVE_TABS_FROM_PLATER + wxSizer* panel_sizer; + wxPanel* current_panel; + std::vector panels; +#else + wxNotebook *notebook; + EventGuard guard_on_notebook_changed; + // Note: ^ The on_notebook_changed is guarded here because the wxNotebook d-tor tends to generate + // wxEVT_NOTEBOOK_PAGE_CHANGED events on some platforms, which causes them to be received by a freed Plater. + // EventGuard unbinds the handler in its d-tor. +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + Sidebar *sidebar; +#if ENABLE_REMOVE_TABS_FROM_PLATER + View3D* view3D; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar view_toolbar; +#else + GLRadioToolbar view_toolbar; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if !ENABLE_IMGUI + wxPanel *panel3d; +#endif // not ENABLE_IMGUI + wxGLCanvas *canvas3Dwidget; // TODO: Use GLCanvas3D when we can + GLCanvas3D *canvas3D; +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER + Preview *preview; + + wxString project_filename; + + BackgroundSlicingProcess background_process; + std::atomic arranging; + std::atomic rotoptimizing; + bool delayed_scene_refresh; + + wxTimer background_process_timer; + + static const std::regex pattern_bundle; + static const std::regex pattern_3mf; + static const std::regex pattern_zip_amf; + + priv(Plater *q, MainFrame *main_frame); + + void update(bool force_full_scene_refresh = false); + void select_view(const std::string& direction); +#if ENABLE_REMOVE_TABS_FROM_PLATER + void select_view_3D(const std::string& name); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + void update_ui_from_settings(); + ProgressStatusBar* statusbar(); + std::string get_config(const std::string &key) const; + BoundingBoxf bed_shape_bb() const; + BoundingBox scaled_bed_shape_bb() const; + std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); + std::vector load_model_objects(const ModelObjectPtrs &model_objects); + std::unique_ptr get_export_file(GUI::FileType file_type); + + const GLCanvas3D::Selection& get_selection() const; + GLCanvas3D::Selection& get_selection(); + int get_selected_object_idx() const; + int get_selected_volume_idx() const; + void selection_changed(); + void object_list_changed(); + + void select_all(); + void remove(size_t obj_idx); + void delete_object_from_model(size_t obj_idx); + void reset(); + void mirror(Axis axis); + void arrange(); + void sla_optimize_rotation(); + void split_object(); + void split_volume(); + bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } + void update_print_volume_state(); + void schedule_background_process(); + // Update background processing thread from the current config and Model. + enum UpdateBackgroundProcessReturnState { + // update_background_process() reports, that the Print / SLAPrint was updated in a way, + // that the background process was invalidated and it needs to be re-run. + UPDATE_BACKGROUND_PROCESS_RESTART = 1, + // update_background_process() reports, that the Print / SLAPrint was updated in a way, + // that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false)) + UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2, + // update_background_process() reports, that the Print / SLAPrint is invalid, and the error message + // was sent to the status line. + UPDATE_BACKGROUND_PROCESS_INVALID = 4, + }; + // returns bit mask of UpdateBackgroundProcessReturnState + unsigned int update_background_process(); + void export_gcode(fs::path output_path, PrintHostJob upload_job); + void async_apply_config(); + void reload_from_disk(); + void fix_through_netfabb(const int obj_idx); + +#if ENABLE_REMOVE_TABS_FROM_PLATER + void set_current_panel(wxPanel* panel); +#else + void on_notebook_changed(wxBookCtrlEvent&); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + void on_select_preset(wxCommandEvent&); + void on_slicing_update(SlicingStatusEvent&); + void on_slicing_completed(wxCommandEvent&); + void on_process_completed(wxCommandEvent&); + void on_layer_editing_toggled(bool enable); + + void on_action_add(SimpleEvent&); + void on_action_split_objects(SimpleEvent&); + void on_action_split_volumes(SimpleEvent&); + void on_action_layersediting(SimpleEvent&); + + void on_object_select(SimpleEvent&); + void on_viewport_changed(SimpleEvent&); + void on_right_click(Vec2dEvent&); + void on_wipetower_moved(Vec3dEvent&); + void on_update_geometry(Vec3dsEvent<2>&); + void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); + + void update_object_menu(); + +private: + bool init_object_menu(); + bool init_common_menu(wxMenu* menu, const bool is_part = false); + bool complit_init_object_menu(); + bool complit_init_sla_object_menu(); + bool complit_init_part_menu(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + void init_view_toolbar(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + bool can_delete_object() const; + bool can_increase_instances() const; + bool can_decrease_instances() const; + bool can_split_to_objects() const; + bool can_split_to_volumes() const; + bool layers_height_allowed() const; + bool can_delete_all() const; + bool can_arrange() const; + bool can_mirror() const; + + void update_fff_scene(); + void update_sla_scene(); +}; + +const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); +const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); +const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase); +Plater::priv::priv(Plater *q, MainFrame *main_frame) + : q(q) + , main_frame(main_frame) + , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ + "bed_shape", "complete_objects", "extruder_clearance_radius", "skirts", "skirt_distance", + "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", + "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology" + })) +#if !ENABLE_REMOVE_TABS_FROM_PLATER + , notebook(new wxNotebook(q, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_BOTTOM)) + , guard_on_notebook_changed(notebook, wxEVT_NOTEBOOK_PAGE_CHANGED, &priv::on_notebook_changed, this) +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER + , sidebar(new Sidebar(q)) +#if !ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_IMGUI + , canvas3Dwidget(GLCanvas3DManager::create_wxglcanvas(notebook)) +#else + , panel3d(new wxPanel(notebook, wxID_ANY)) + , canvas3Dwidget(GLCanvas3DManager::create_wxglcanvas(panel3d)) +#endif // ENABLE_IMGUI + , canvas3D(nullptr) +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER + , delayed_scene_refresh(false) + , project_filename(wxEmptyString) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , view_toolbar(GLToolbar::Radio) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +{ + arranging.store(false); + rotoptimizing.store(false); + background_process.set_fff_print(&fff_print); + background_process.set_sla_print(&sla_print); + background_process.set_gcode_preview_data(&gcode_preview_data); + background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); + background_process.set_finished_event(EVT_PROCESS_COMPLETED); + // Default printer technology for default config. + background_process.select_technology(this->printer_technology); + // Register progress callback from the Print class to the Platter. + + auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) { + wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status)); + }; + fff_print.set_status_callback(statuscb); + sla_print.set_status_callback(statuscb); + this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); + +#if !ENABLE_REMOVE_TABS_FROM_PLATER + _3DScene::add_canvas(canvas3Dwidget); + this->canvas3D = _3DScene::get_canvas(this->canvas3Dwidget); + this->canvas3D->allow_multisample(GLCanvas3DManager::can_multisample()); +#if ENABLE_IMGUI + notebook->AddPage(canvas3Dwidget, _(L("3D"))); +#else + auto *panel3dsizer = new wxBoxSizer(wxVERTICAL); + panel3dsizer->Add(canvas3Dwidget, 1, wxEXPAND); + auto *panel_gizmo_widgets = new wxPanel(panel3d, wxID_ANY); + panel_gizmo_widgets->SetSizer(new wxBoxSizer(wxVERTICAL)); + panel3dsizer->Add(panel_gizmo_widgets, 0, wxEXPAND); + + panel3d->SetSizer(panel3dsizer); + notebook->AddPage(panel3d, _(L("3D"))); + + canvas3D->set_external_gizmo_widgets_parent(panel_gizmo_widgets); +#endif // ENABLE_IMGUI +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER + +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D = new View3D(q, &model, config, &background_process); + preview = new Preview(q, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); + + panels.push_back(view3D); + panels.push_back(preview); +#else + preview = new GUI::Preview(notebook, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); + + // XXX: If have OpenGL + this->canvas3D->enable_picking(true); + this->canvas3D->enable_moving(true); + // XXX: more config from 3D.pm + this->canvas3D->set_model(&model); + this->canvas3D->set_process(&background_process); + this->canvas3D->set_config(config); + this->canvas3D->enable_gizmos(true); + this->canvas3D->enable_toolbar(true); + this->canvas3D->enable_shader(true); + this->canvas3D->enable_force_zoom_to_bed(true); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + this->background_process_timer.SetOwner(this->q, 0); + this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->async_apply_config(); }); + + auto *bed_shape = config->opt("bed_shape"); +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->set_bed_shape(bed_shape->values); +#else + this->canvas3D->set_bed_shape(bed_shape->values); + this->canvas3D->zoom_to_bed(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + preview->set_bed_shape(bed_shape->values); + + update(); + + auto *hsizer = new wxBoxSizer(wxHORIZONTAL); +#if ENABLE_REMOVE_TABS_FROM_PLATER + panel_sizer = new wxBoxSizer(wxHORIZONTAL); + panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0); + panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0); + hsizer->Add(panel_sizer, 1, wxEXPAND | wxALL, 0); +#else + hsizer->Add(notebook, 1, wxEXPAND | wxTOP, 1); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0); + q->SetSizer(hsizer); + +#if ENABLE_REMOVE_TABS_FROM_PLATER + set_current_panel(view3D); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + init_object_menu(); + + // Events: + + // Preset change event + sidebar->Bind(wxEVT_COMBOBOX, &priv::on_select_preset, this); + + sidebar->Bind(EVT_OBJ_LIST_OBJECT_SELECT, [this](wxEvent&) { priv::selection_changed(); }); + sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + +#if ENABLE_REMOVE_TABS_FROM_PLATER + wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas(); + // 3DScene events: + view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); + view3D_canvas->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); + view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); + view3D_canvas->Bind(EVT_GLCANVAS_MODEL_UPDATE, [this](SimpleEvent&) { this->schedule_background_process(); }); + view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); + view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); + view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); + // 3DScene/Toolbar: + view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); + view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); + view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); +#else + // 3DScene events: + canvas3Dwidget->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); + canvas3Dwidget->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); + canvas3Dwidget->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); + canvas3Dwidget->Bind(EVT_GLCANVAS_MODEL_UPDATE, [this](SimpleEvent&) { this->schedule_background_process(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); + canvas3Dwidget->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); + canvas3Dwidget->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); + // 3DScene/Toolbar: + canvas3Dwidget->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); } ); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); }); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); + canvas3Dwidget->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + // Preview events: + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); + q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); +#if ENABLE_REMOVE_TABS_FROM_PLATER + q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); + q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + // Drop target: + q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership + + update_ui_from_settings(); + q->Layout(); +} + +void Plater::priv::update(bool force_full_scene_refresh) +{ + wxWindowUpdateLocker freeze_guard(q); + if (get_config("autocenter") == "1") { + // auto *bed_shape_opt = config->opt("bed_shape"); + // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); + // const BoundingBox bed_shape_bb = bed_shape.bounding_box(); + const Vec2d& bed_center = bed_shape_bb().center(); + model.center_instances_around_point(bed_center); + } + + if (this->printer_technology == ptSLA) { + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data. + this->update_background_process(); + } +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(false, force_full_scene_refresh); +#else + this->canvas3D->reload_scene(false, force_full_scene_refresh); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + preview->reset_gcode_preview_data(); + preview->reload_print(); + + this->schedule_background_process(); +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Plater::priv::select_view(const std::string& direction) +{ + if (current_panel == view3D) + view3D->select_view(direction); + else if (current_panel == preview) + preview->select_view(direction); +} + +void Plater::priv::select_view_3D(const std::string& name) +{ + if (name == "3D") + set_current_panel(view3D); + else if (name == "Preview") + set_current_panel(preview); + +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE + view_toolbar.set_selection(name); +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE +} +#else +void Plater::priv::select_view(const std::string& direction) +{ + int page_id = notebook->GetSelection(); + if (page_id != wxNOT_FOUND) + { + const wxString& page_text = notebook->GetPageText(page_id); + if (page_text == _(L("3D"))) + this->canvas3D->select_view(direction); + else if (page_text == _(L("Preview"))) + preview->select_view(direction); + } +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void Plater::priv::update_ui_from_settings() +{ + // TODO: (?) + // my ($self) = @_; + // if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { + // $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); + // $self->{buttons_sizer}->Layout; + // } +} + +ProgressStatusBar* Plater::priv::statusbar() +{ + return main_frame->m_statusbar; +} + +std::string Plater::priv::get_config(const std::string &key) const +{ + return wxGetApp().app_config->get(key); +} + +BoundingBoxf Plater::priv::bed_shape_bb() const +{ + BoundingBox bb = scaled_bed_shape_bb(); + return BoundingBoxf(unscale(bb.min), unscale(bb.max)); +} + +BoundingBox Plater::priv::scaled_bed_shape_bb() const +{ + const auto *bed_shape_opt = config->opt("bed_shape"); + const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); + return bed_shape.bounding_box(); +} + +std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config) +{ + if (input_files.empty()) { return std::vector(); } + + auto *nozzle_dmrs = config->opt("nozzle_diameter"); + + bool one_by_one = input_files.size() == 1 || nozzle_dmrs->values.size() <= 1; + if (! one_by_one) { + for (const auto &path : input_files) { + if (std::regex_match(path.string(), pattern_bundle)) { + one_by_one = true; + break; + } + } + } + + const auto loading = _(L("Loading…")); + wxProgressDialog dlg(loading, loading); + dlg.Pulse(); + + auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); + std::vector obj_idxs; + + for (size_t i = 0; i < input_files.size(); i++) { + const auto &path = input_files[i]; + const auto filename = path.filename(); + const auto dlg_info = wxString::Format(_(L("Processing input file %s\n")), filename.string()); + dlg.Update(100 * i / input_files.size(), dlg_info); + + const bool type_3mf = std::regex_match(path.string(), pattern_3mf); + const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); + + Slic3r::Model model; + try { + if (type_3mf || type_zip_amf) { + DynamicPrintConfig config; + { + DynamicPrintConfig config_loaded; + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false); + if (load_config && !config_loaded.empty()) { + // Based on the printer technology field found in the loaded config, select the base for the config, + PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); + config.apply(printer_technology == ptFFF ? + static_cast(FullPrintConfig::defaults()) : + static_cast(SLAFullPrintConfig::defaults())); + // and place the loaded config over the base. + config += std::move(config_loaded); + } + } + + if (load_config) + { + if (!config.empty()) { + Preset::normalize(config); + wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); + wxGetApp().load_current_presets(); + } + wxGetApp().app_config->update_config_dir(path.parent_path().string()); + } + } + else { + model = Slic3r::Model::read_from_file(path.string(), nullptr, false); + for (auto obj : model.objects) + if (obj->name.empty()) + obj->name = fs::path(obj->input_file).filename().string(); + } + } catch (const std::exception &e) { + GUI::show_error(q, e.what()); + continue; + } + + if (load_model) + { + // The model should now be initialized + + if (model.looks_like_multipart_object()) { + wxMessageDialog dlg(q, _(L( + "This file contains several objects positioned at multiple heights. " + "Instead of considering them as multiple objects, should I consider\n" + "this file as a single object having multiple parts?\n" + )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { + model.convert_multipart_object(nozzle_dmrs->values.size()); + } + } + + if (type_3mf) { + for (ModelObject* model_object : model.objects) { + model_object->center_around_origin(); + model_object->ensure_on_bed(); + } + } + + // check multi-part object adding for the SLA-printing + if (printer_technology == ptSLA) + { + for (auto obj : model.objects) + if ( obj->volumes.size()>1 ) { + Slic3r::GUI::show_error(nullptr, + wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), + filename.string())); + return std::vector(); + } + } + + if (one_by_one) { + auto loaded_idxs = load_model_objects(model.objects); + obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + } else { + // This must be an .stl or .obj file, which may contain a maximum of one volume. + for (const ModelObject* model_object : model.objects) { + new_model->add_object(*model_object); + } + } + } + } + + if (new_model != nullptr) { + wxMessageDialog dlg(q, _(L( + "Multiple objects were loaded for a multi-material printer.\n" + "Instead of considering them as multiple objects, should I consider\n" + "these files to represent a single object having multiple parts?\n" + )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { + new_model->convert_multipart_object(nozzle_dmrs->values.size()); + } + + auto loaded_idxs = load_model_objects(new_model->objects); + obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + } + + if (load_model) + { + wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string()); + // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... + statusbar()->set_status_text(_(L("Loaded"))); + } + + return obj_idxs; +} + +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) +{ + const BoundingBoxf bed_shape = bed_shape_bb(); +#if !ENABLE_MODELVOLUME_TRANSFORM + const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast(), 0.0); +#endif // !ENABLE_MODELVOLUME_TRANSFORM + const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0); + + bool need_arrange = false; + bool scaled_down = false; + std::vector obj_idxs; + unsigned int obj_count = model.objects.size(); + + for (ModelObject *model_object : model_objects) { + auto *object = model.add_object(*model_object); + std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name; + obj_idxs.push_back(obj_count++); + + if (model_object->instances.empty()) { + // if object has no defined position(s) we need to rearrange everything after loading + need_arrange = true; + + // add a default instance and center object around origin + object->center_around_origin(); // also aligns object to Z = 0 + ModelInstance* instance = object->add_instance(); +#if ENABLE_MODELVOLUME_TRANSFORM + instance->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -object->origin_translation(2))); +#else + instance->set_offset(bed_center); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + + const Vec3d size = object->bounding_box().size(); + const Vec3d ratio = size.cwiseQuotient(bed_size); + const double max_ratio = std::max(ratio(0), ratio(1)); + if (max_ratio > 10000) { + // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, + // so scale down the mesh + // const Vec3d inverse = ratio.cwiseInverse(); + // object->scale(inverse); + object->scale(ratio.cwiseInverse()); + scaled_down = true; + } else if (max_ratio > 5) { + const Vec3d inverse = ratio.cwiseInverse(); + for (ModelInstance *instance : model_object->instances) { + instance->set_scaling_factor(inverse); + } + } + + object->ensure_on_bed(); + + // print.auto_assign_extruders(object); + // print.add_model_object(object); + } + + if (scaled_down) { + GUI::show_info(q, + _(L("Your object appears to be too large, so it was automatically scaled down to fit your print bed.")), + _(L("Object too large?"))); + } + + for (const size_t idx : obj_idxs) { + wxGetApp().obj_list()->add_object_to_list(idx); + } + + update(); +#if !ENABLE_MODIFIED_CAMERA_TARGET + this->canvas3D->zoom_to_volumes(); +#endif // !ENABLE_MODIFIED_CAMERA_TARGET + object_list_changed(); + + this->schedule_background_process(); + + return obj_idxs; +} + +std::unique_ptr Plater::priv::get_export_file(GUI::FileType file_type) +{ + wxString wildcard; + switch (file_type) { + case FT_STL: + case FT_AMF: + case FT_3MF: + case FT_GCODE: + wildcard = file_wildcards(file_type); + break; + default: + wildcard = file_wildcards(FT_MODEL); + break; + } + + // Update printbility state of each of the ModelInstances. + this->update_print_volume_state(); + // Find the file name of the first printable object. + fs::path output_file = this->model.propose_export_file_name(); + + switch (file_type) { + case FT_STL: output_file.replace_extension("stl"); break; + case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension? + case FT_3MF: output_file.replace_extension("3mf"); break; + default: break; + } + + auto dlg = Slic3r::make_unique(q, + ((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "", + true, + _(L("Save file as:")), + output_file.parent_path().string(), + output_file.filename().string(), + wildcard, + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + if (dlg->ShowModal() != wxID_OK) { + return nullptr; + } + + fs::path path(dlg->GetPath()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); + + return dlg; +} + +const GLCanvas3D::Selection& Plater::priv::get_selection() const +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + return view3D->get_canvas3d()->get_selection(); +#else + return canvas3D->get_selection(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +GLCanvas3D::Selection& Plater::priv::get_selection() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + return view3D->get_canvas3d()->get_selection(); +#else + return canvas3D->get_selection(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +int Plater::priv::get_selected_object_idx() const +{ + int idx = get_selection().get_object_idx(); + return ((0 <= idx) && (idx < 1000)) ? idx : -1; +} + +int Plater::priv::get_selected_volume_idx() const +{ + auto& selection = get_selection(); + int idx = selection.get_object_idx(); + if ((0 > idx) || (idx > 1000)) + return-1; + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + if (model.objects[idx]->volumes.size() > 1) + return v->volume_idx(); + return -1; +} + +void Plater::priv::selection_changed() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->enable_toolbar_item("delete", can_delete_object()); + view3D->enable_toolbar_item("more", can_increase_instances()); + view3D->enable_toolbar_item("fewer", can_decrease_instances()); + view3D->enable_toolbar_item("splitobjects", can_split_to_objects()); + view3D->enable_toolbar_item("splitvolumes", can_split_to_volumes()); + view3D->enable_toolbar_item("layersediting", layers_height_allowed()); + // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) + view3D->render(); +#else + this->canvas3D->enable_toolbar_item("delete", can_delete_object()); + this->canvas3D->enable_toolbar_item("more", can_increase_instances()); + this->canvas3D->enable_toolbar_item("fewer", can_decrease_instances()); + this->canvas3D->enable_toolbar_item("splitobjects", can_split_to_objects()); + this->canvas3D->enable_toolbar_item("splitvolumes", can_split_to_volumes()); + this->canvas3D->enable_toolbar_item("layersediting", layers_height_allowed()); + // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) + this->canvas3D->render(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::object_list_changed() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + // Enable/disable buttons depending on whether there are any objects on the platter. + view3D->enable_toolbar_item("deleteall", can_delete_all()); + view3D->enable_toolbar_item("arrange", can_arrange()); +#else + // Enable/disable buttons depending on whether there are any objects on the platter. + this->canvas3D->enable_toolbar_item("deleteall", can_delete_all()); + this->canvas3D->enable_toolbar_item("arrange", can_arrange()); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); + // XXX: is this right? +#if ENABLE_REMOVE_TABS_FROM_PLATER + const bool model_fits = view3D->check_volumes_outside_state() == ModelInstance::PVS_Inside; +#else + const bool model_fits = this->canvas3D->check_volumes_outside_state(config) == ModelInstance::PVS_Inside; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); +} + +void Plater::priv::select_all() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->select_all(); +#else + this->canvas3D->select_all(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + this->sidebar->obj_list()->update_selections(); +} + +void Plater::priv::remove(size_t obj_idx) +{ + // Prevent toolpaths preview from rendering while we modify the Print object + preview->set_enabled(false); + +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_layers_editing_enabled()) + view3D->enable_layers_editing(false); +#else + if (this->canvas3D->is_layers_editing_enabled()) + this->canvas3D->enable_layers_editing(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + model.delete_object(obj_idx); + // Delete object from Sidebar list + sidebar->obj_list()->delete_object_from_list(obj_idx); + + object_list_changed(); + update(); +} + + +void Plater::priv::delete_object_from_model(size_t obj_idx) +{ + model.delete_object(obj_idx); + object_list_changed(); + update(); +} + +void Plater::priv::reset() +{ + project_filename.Clear(); + + // Prevent toolpaths preview from rendering while we modify the Print object + preview->set_enabled(false); + +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_layers_editing_enabled()) + view3D->enable_layers_editing(false); +#else + if (this->canvas3D->is_layers_editing_enabled()) + this->canvas3D->enable_layers_editing(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + // Stop and reset the Print content. + this->background_process.reset(); + model.clear_objects(); + + // Delete all objects from list on c++ side + sidebar->obj_list()->delete_all_objects_from_list(); + object_list_changed(); + update(); + + + auto& config = wxGetApp().preset_bundle->project_config; + config.option("colorprint_heights")->values.clear(); +} + +void Plater::priv::mirror(Axis axis) +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->mirror_selection(axis); +#else + this->canvas3D->mirror_selection(axis); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::arrange() +{ + // don't do anything if currently arranging. Then this is a re-entrance + if(arranging.load()) return; + + // Guard the arrange process + arranging.store(true); + + // Disable the arrange button (to prevent reentrancies, we will call wxYied) +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->enable_toolbar_item("arrange", can_arrange()); +#else + this->canvas3D->enable_toolbar_item("arrange", can_arrange()); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + this->background_process.stop(); + unsigned count = 0; + for(auto obj : model.objects) count += obj->instances.size(); + + auto prev_range = statusbar()->get_range(); + statusbar()->set_range(count); + + auto statusfn = [this, count] (unsigned st, const std::string& msg) { + /* // In case we would run the arrange asynchronously + wxCommandEvent event(EVT_PROGRESS_BAR); + event.SetInt(st); + event.SetString(msg); + wxQueueEvent(this->q, event.Clone()); */ + statusbar()->set_progress(count - st); + statusbar()->set_status_text(msg); + + // ok, this is dangerous, but we are protected by the atomic flag + // 'arranging' and the arrange button is also disabled. + // This call is needed for the cancel button to work. + wxYieldIfNeeded(); + }; + + statusbar()->set_cancel_callback([this, statusfn](){ + arranging.store(false); + statusfn(0, L("Arranging canceled")); + }); + + static const std::string arrangestr = L("Arranging"); + + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; //PrintConfig::min_object_distance(config); + + auto min_obj_distance = static_cast(dist/SCALING_FACTOR); + + const auto *bed_shape_opt = config->opt("bed_shape"); + + assert(bed_shape_opt); + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + statusfn(0, arrangestr); + + try { + arr::BedShapeHint hint; + + // TODO: from Sasha from GUI or + hint.type = arr::BedShapeType::WHO_KNOWS; + + arr::arrange(model, + min_obj_distance, + bed, + hint, + false, // create many piles not just one pile + [statusfn](unsigned st) { statusfn(st, arrangestr); }, + [this] () { return !arranging.load(); }); + } catch(std::exception& /*e*/) { + GUI::show_error(this->q, L("Could not arrange model objects! " + "Some geometries may be invalid.")); + } + + statusfn(0, L("Arranging done.")); + statusbar()->set_range(prev_range); + statusbar()->set_cancel_callback(); // remove cancel button + arranging.store(false); + + // We enable back the arrange button +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->enable_toolbar_item("arrange", can_arrange()); +#else + this->canvas3D->enable_toolbar_item("arrange", can_arrange()); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + // Do a full refresh of scene tree, including regenerating all the GLVolumes. + //FIXME The update function shall just reload the modified matrices. + update(true); +} + +// This method will find an optimal orientation for the currently selected item +// Very similar in nature to the arrange method above... +void Plater::priv::sla_optimize_rotation() { + + // TODO: we should decide whether to allow arrange when the search is + // running we should probably disable explicit slicing and background + // processing + + if(rotoptimizing.load()) return; + rotoptimizing.store(true); + + int obj_idx = get_selected_object_idx(); + ModelObject * o = model.objects[obj_idx]; + + background_process.stop(); + + auto prev_range = statusbar()->get_range(); + statusbar()->set_range(100); + + auto stfn = [this] (unsigned st, const std::string& msg) { + statusbar()->set_progress(st); + statusbar()->set_status_text(msg); + + // could be problematic, but we need the cancel button. + wxYieldIfNeeded(); + }; + + statusbar()->set_cancel_callback([this, stfn](){ + rotoptimizing.store(false); + stfn(0, L("Orientation search canceled")); + }); + + auto r = sla::find_best_rotation( + *o, .005f, + [stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); }, + [this](){ return !rotoptimizing.load(); } + ); + + if(rotoptimizing.load()) // wasn't canceled + for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]}); + + // Correct the z offset of the object which was corrupted be the rotation + o->ensure_on_bed(); + + stfn(0, L("Orientation found.")); + statusbar()->set_range(prev_range); + statusbar()->set_cancel_callback(); + rotoptimizing.store(false); + + update(true); +} + +void Plater::priv::split_object() +{ + int obj_idx = get_selected_object_idx(); + if (obj_idx == -1) + return; + + // we clone model object because split_object() adds the split volumes + // into the same model object, thus causing duplicates when we call load_model_objects() + Model new_model = model; + ModelObject* current_model_object = new_model.objects[obj_idx]; + + if (current_model_object->volumes.size() > 1) + { + Slic3r::GUI::warning_catcher(q, _(L("The selected object can't be split because it contains more than one volume/material."))); + return; + } + + ModelObjectPtrs new_objects; + current_model_object->split(&new_objects); + if (new_objects.size() == 1) + Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); + else + { + unsigned int counter = 1; + for (ModelObject* m : new_objects) + m->name = current_model_object->name + "_" + std::to_string(counter++); + + remove(obj_idx); + + // load all model objects at once, otherwise the plate would be rearranged after each one + // causing original positions not to be kept + std::vector idxs = load_model_objects(new_objects); + + // select newly added objects + for (size_t idx : idxs) + { + get_selection().add_object((unsigned int)idx, false); + } + } +} + +void Plater::priv::split_volume() +{ + wxGetApp().obj_list()->split(); +} + +void Plater::priv::schedule_background_process() +{ + // Trigger the timer event after 0.5s + this->background_process_timer.Start(500, wxTIMER_ONE_SHOT); +} + +void Plater::priv::update_print_volume_state() +{ + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt("bed_shape")->values)); + BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height")))); + // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. + print_volume.min(2) = -1e10; + this->q->model().update_print_volume_state(print_volume); +} + +// Update background processing thread from the current config and Model. +// Returns a bitmask of UpdateBackgroundProcessReturnState. +unsigned int Plater::priv::update_background_process() +{ + // bitmap of enum UpdateBackgroundProcessReturnState + unsigned int return_state = 0; + + // If the update_background_process() was not called by the timer, kill the timer, so the async_apply_config() + // will not be called again in vain. + this->background_process_timer.Stop(); + // Update the "out of print bed" state of ModelInstances. + this->update_print_volume_state(); + // Apply new config to the possibly running background task. + Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); + + // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_layers_editing_enabled()) + view3D->get_wxglcanvas()->Refresh(); +#else + if (this->canvas3D->is_layers_editing_enabled()) + this->canvas3Dwidget->Refresh(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + + if (invalidated == Print::APPLY_STATUS_INVALIDATED) { + // Some previously calculated data on the Print was invalidated. + // Hide the slicing results, as the current slicing status is no more valid. + this->sidebar->show_sliced_info_sizer(false); + // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. + // Otherwise they will be just refreshed. + this->gcode_preview_data.reset(); + switch (this->printer_technology) { + case ptFFF: + if (this->preview != nullptr) + // If the preview is not visible, the following line just invalidates the preview, + // but the G-code paths are calculated first once the preview is made visible. + this->preview->reload_print(); + // We also need to reload 3D scene because of the wipe tower preview box + if (this->config->opt_bool("wipe_tower")) + return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + break; + case ptSLA: + return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + break; + } + } + + if (this->background_process.empty()) { + if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + // The background processing will not be restarted, because the Print / SLAPrint is empty. + // Simulate a "canceled" callback message. + wxCommandEvent evt; + evt.SetInt(-1); // canceled + this->on_process_completed(evt); + } + } else { + std::string err = this->background_process.validate(); + if (err.empty()) { + if (invalidated != Print::APPLY_STATUS_UNCHANGED) + return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; + } else { + // The print is not valid. + GUI::show_error(this->q, _(err)); + return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; + } + } + return return_state; +} + +void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job) +{ + wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty"); + + if (model.objects.empty()) + return; + + if (background_process.is_export_scheduled()) { + GUI::show_error(q, _(L("Another export job is currently running."))); + return; + } + + // bitmask of UpdateBackgroundProcessReturnState + unsigned int state = update_background_process(); + if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(false); +#else + canvas3D->reload_scene(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + return; + + if (! output_path.empty()) { + background_process.schedule_export(output_path.string()); + } else { + background_process.schedule_upload(std::move(upload_job)); + } + + if (! background_process.running()) { + // The print is valid and it should be started. + if (background_process.start()) + statusbar()->set_cancel_callback([this]() { + statusbar()->set_status_text(L("Cancelling")); + background_process.stop(); + }); + } +} + +void Plater::priv::async_apply_config() +{ + // bitmask of UpdateBackgroundProcessReturnState + unsigned int state = this->update_background_process(); + if (state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(false); +#else + this->canvas3D->reload_scene(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if ((state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 && this->background_processing_enabled()) { + // The print is valid and it can be started. + if (this->background_process.start()) + this->statusbar()->set_cancel_callback([this]() { + this->statusbar()->set_status_text(L("Cancelling")); + this->background_process.stop(); + }); + } +} + +void Plater::priv::update_fff_scene() +{ + if (this->preview != nullptr) + this->preview->reload_print(); + // In case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(true); +#else + this->canvas3D->reload_scene(true); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::update_sla_scene() +{ + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data. + if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART) + this->schedule_background_process(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(true); +#else + this->canvas3D->reload_scene(true); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + delayed_scene_refresh = false; + this->preview->reload_print(); +} + +void Plater::priv::reload_from_disk() +{ + const auto &selection = get_selection(); + const auto obj_orig_idx = selection.get_object_idx(); + if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; } + + auto *object_orig = model.objects[obj_orig_idx]; + std::vector input_paths(1, object_orig->input_file); + + const auto new_idxs = load_files(input_paths, true, false); + + for (const auto idx : new_idxs) { + ModelObject *object = model.objects[idx]; + + object->clear_instances(); + for (const ModelInstance *instance : object_orig->instances) { + object->add_instance(*instance); + } + + if (object->volumes.size() == object_orig->volumes.size()) { + for (size_t i = 0; i < object->volumes.size(); i++) { + object->volumes[i]->config.apply(object_orig->volumes[i]->config); + } + } + + // XXX: Restore more: layer_height_ranges, layer_height_profile, layer_height_profile_valid (?) + } + + remove(obj_orig_idx); +} + +void Plater::priv::fix_through_netfabb(const int obj_idx) +{ + if (obj_idx < 0) + return; + + const auto model_object = model.objects[obj_idx]; + Model model_fixed;// = new Model(); + fix_model_by_win10_sdk_gui(*model_object, this->fff_print, model_fixed); + + auto new_obj_idxs = load_model_objects(model_fixed.objects); + if (new_obj_idxs.empty()) + return; + + for(auto new_obj_idx : new_obj_idxs) { + auto o = model.objects[new_obj_idx]; + o->clear_instances(); + for (auto instance: model_object->instances) + o->add_instance(*instance); + // o->invalidate_bounding_box(); + + if (o->volumes.size() == model_object->volumes.size()) { + for (int i = 0; i < o->volumes.size(); i++) { + o->volumes[i]->config.apply(model_object->volumes[i]->config); + } + } + // FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, + } + + remove(obj_idx); +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Plater::priv::set_current_panel(wxPanel* panel) +{ + if (std::find(panels.begin(), panels.end(), panel) == panels.end()) + return; + + if (current_panel == panel) + return; + + current_panel = panel; + // to reduce flickering when changing view, first set as visible the new current panel + for (wxPanel* p : panels) + { + if (p == current_panel) + p->Show(); + } + // then set to invisible the other + for (wxPanel* p : panels) + { + if (p != current_panel) + p->Hide(); + } + + panel_sizer->Layout(); + + if (current_panel == view3D) + { + if (view3D->is_reload_delayed()) + { + // Delayed loading of the 3D scene. + if (this->printer_technology == ptSLA) + { + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data. + if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART) + this->schedule_background_process(); + } + view3D->reload_scene(true); + } + // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + view3D->set_as_dirty(); + } + else if (current_panel == preview) + { + this->q->reslice(); + preview->reload_print(); + preview->set_canvas_as_dirty(); + } +} +#else +void Plater::priv::on_notebook_changed(wxBookCtrlEvent&) +{ + wxCHECK_RET(canvas3D != nullptr, "on_notebook_changed on freed Plater"); + + const auto current_id = notebook->GetCurrentPage()->GetId(); +#if ENABLE_IMGUI + if (current_id == canvas3Dwidget->GetId()) { +#else + if (current_id == panel3d->GetId()) { +#endif // ENABLE_IMGUI + if (this->canvas3D->is_reload_delayed()) { + // Delayed loading of the 3D scene. + if (this->printer_technology == ptSLA) { + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data. + if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART) + this->schedule_background_process(); + } + this->canvas3D->reload_scene(true); + } + // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + this->canvas3D->set_as_dirty(); + } else if (current_id == preview->GetId()) { + preview->reload_print(); + preview->set_canvas_as_dirty(); + } +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +void Plater::priv::on_select_preset(wxCommandEvent &evt) +{ + auto preset_type = static_cast(evt.GetInt()); + auto *combo = static_cast(evt.GetEventObject()); + + auto idx = combo->get_extruder_idx(); + + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! but the OSX version derived from wxOwnerDrawnCombo. + //! So, to get selected string we do + //! combo->GetString(combo->GetSelection()) + //! instead of + //! combo->GetStringSelection().ToStdString()); + + std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); + + if (preset_type == Preset::TYPE_FILAMENT) { + wxGetApp().preset_bundle->set_filament_preset(idx, selected_string); + } + + // TODO: ? + if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { + // Only update the platter UI for the 2nd and other filaments. + wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); + } + else { + for (Tab* tab : wxGetApp().tabs_list) { + if (tab->type() == preset_type) { + tab->select_preset(selected_string); + break; + } + } + } + + // update plater with new config + wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + if (preset_type == Preset::TYPE_PRINTER) + wxGetApp().obj_list()->update_settings_items(); +} + +void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) +{ + this->statusbar()->set_progress(evt.status.percent); + this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); + if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) { + switch (this->printer_technology) { + case ptFFF: + this->update_fff_scene(); + break; + case ptSLA: +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_dragging()) +#else + if (this->canvas3D->is_dragging()) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + delayed_scene_refresh = true; + else + this->update_sla_scene(); + break; + } + } +} + +void Plater::priv::on_slicing_completed(wxCommandEvent &) +{ + switch (this->printer_technology) { + case ptFFF: + this->update_fff_scene(); + break; + case ptSLA: +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_dragging()) +#else + if (this->canvas3D->is_dragging()) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + delayed_scene_refresh = true; + else + this->update_sla_scene(); + break; + } +} + +void Plater::priv::on_process_completed(wxCommandEvent &evt) +{ + // Stop the background task, wait until the thread goes into the "Idle" state. + // At this point of time the thread should be either finished or canceled, + // so the following call just confirms, that the produced data were consumed. + this->background_process.stop(); + this->statusbar()->reset_cancel_callback(); + this->statusbar()->stop_busy(); + + bool canceled = evt.GetInt() < 0; + bool success = evt.GetInt() > 0; + // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. + this->background_process.reset_export(); + if (! success) { + wxString message = evt.GetString(); + if (message.IsEmpty()) + message = _(L("Export failed")); + this->statusbar()->set_status_text(message); + } + if (canceled) + this->statusbar()->set_status_text(L("Cancelled")); + + this->sidebar->show_sliced_info_sizer(success); + + // This updates the "Slice now", "Export G-code", "Arrange" buttons status. + // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables + // the "Slice now" and "Export G-code" buttons based on their "out of bed" status. + this->object_list_changed(); + + // refresh preview + switch (this->printer_technology) { + case ptFFF: + this->update_fff_scene(); + break; + case ptSLA: +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (view3D->is_dragging()) +#else + if (this->canvas3D->is_dragging()) +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + delayed_scene_refresh = true; + else + this->update_sla_scene(); + break; + } +} + +void Plater::priv::on_layer_editing_toggled(bool enable) +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->enable_layers_editing(enable); + if (enable && !view3D->is_layers_editing_enabled()) { + // Initialization of the OpenGL shaders failed. Disable the tool. + view3D->enable_toolbar_item("layersediting", false); + } + view3D->set_as_dirty(); +#else + this->canvas3D->enable_layers_editing(enable); + if (enable && !this->canvas3D->is_layers_editing_enabled()) { + // Initialization of the OpenGL shaders failed. Disable the tool. + this->canvas3D->enable_toolbar_item("layersediting", false); + } + canvas3Dwidget->Refresh(); + canvas3Dwidget->Update(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::on_action_add(SimpleEvent&) +{ + if (q != nullptr) + q->add_model(); +} + +void Plater::priv::on_action_split_objects(SimpleEvent&) +{ + split_object(); +} + +void Plater::priv::on_action_split_volumes(SimpleEvent&) +{ + split_volume(); +} + +void Plater::priv::on_action_layersediting(SimpleEvent&) +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + bool enable = !view3D->is_layers_editing_enabled(); + view3D->enable_layers_editing(enable); + if (enable && !view3D->is_layers_editing_enabled()) + view3D->enable_toolbar_item("layersediting", false); +#else + bool enable = !this->canvas3D->is_layers_editing_enabled(); + this->canvas3D->enable_layers_editing(enable); + if (enable && !this->canvas3D->is_layers_editing_enabled()) + this->canvas3D->enable_toolbar_item("layersediting", false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::on_object_select(SimpleEvent& evt) +{ + selection_changed(); + wxGetApp().obj_list()->update_selections(); +} + +void Plater::priv::on_viewport_changed(SimpleEvent& evt) +{ + wxObject* o = evt.GetEventObject(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (o == preview->get_wxglcanvas()) + preview->set_viewport_into_scene(view3D->get_canvas3d()); + else if (o == view3D->get_wxglcanvas()) + preview->set_viewport_from_scene(view3D->get_canvas3d()); +#else + if (o == preview->get_wxglcanvas()) + preview->set_viewport_into_scene(canvas3D); + else if (o == canvas3Dwidget) + preview->set_viewport_from_scene(canvas3D); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::priv::on_right_click(Vec2dEvent& evt) +{ + int obj_idx = get_selected_object_idx(); + if (obj_idx == -1) + return; + + wxMenu* menu = printer_technology == ptSLA ? &sla_object_menu : + get_selection().is_single_full_instance/*object*/() ? // show "Object menu" for each FullInstance instead of FullObject + &object_menu : &part_menu; + + sidebar->obj_list()->append_menu_item_settings(menu); + + if (q != nullptr) + q->PopupMenu(menu, (int)evt.data.x(), (int)evt.data.y()); +} + +void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = evt.data(0); + cfg.opt("wipe_tower_y", true)->value = evt.data(1); + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + +void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) +{ + // TODO +} + +// Update the scene from the background processing, +// if the update message was received during mouse manipulation. +void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) +{ + if (this->delayed_scene_refresh) { + this->delayed_scene_refresh = false; + this->update_sla_scene(); + } +} + +bool Plater::priv::init_object_menu() +{ + init_common_menu(&object_menu); + complit_init_object_menu(); + + init_common_menu(&sla_object_menu); + complit_init_sla_object_menu(); + + init_common_menu(&part_menu, true); + complit_init_part_menu(); + + return true; +} + +bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) +{ + wxMenuItem* item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete\tDel")), _(L("Remove the selected object")), + [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); + if (!is_part){ + wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies\t+")), _(L("Place one more copy of the selected object")), + [this](wxCommandEvent&) { q->increase_instances(); }, "add.png"); + wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies\t-")), _(L("Remove one copy of the selected object")), + [this](wxCommandEvent&) { q->decrease_instances(); }, "delete.png"); + wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies…")), _(L("Change the number of copies of the selected object")), + [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + if (q != nullptr) + { + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_increase->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_decrease_instances()); }, item_decrease->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_set_number_of_copies->GetId()); + } + menu->AppendSeparator(); + + append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), + [this](wxCommandEvent&) { reload_from_disk(); }); + + append_menu_item(menu, wxID_ANY, _(L("Export object as STL…")), _(L("Export this single object as STL file")), + [this](wxCommandEvent&) { q->export_stl(true); }); + } + menu->AppendSeparator(); + + wxMenu* mirror_menu = new wxMenu(); + if (mirror_menu == nullptr) + return false; + + append_menu_item(mirror_menu, wxID_ANY, _(L("Along X axis")), _(L("Mirror the selected object along the X axis")), + [this](wxCommandEvent&) { mirror(X); }, "bullet_red.png", menu); + append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")), + [this](wxCommandEvent&) { mirror(Y); }, "bullet_green.png", menu); + append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")), + [this](wxCommandEvent&) { mirror(Z); }, "bullet_blue.png", menu); + + wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object"))); + + // ui updates needs to be binded to the parent panel + if (q != nullptr) + { + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_object()); }, item_delete->GetId()); + } + + return true; +} + +bool Plater::priv::complit_init_object_menu() +{ + wxMenu* split_menu = new wxMenu(); + if (split_menu == nullptr) + return false; + + wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")), + [this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png", &object_menu); + wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), + [this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png", &object_menu); + + wxMenuItem* item_split = append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "shape_ungroup.png"); + +// append_menu_item(&object_menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), +// [this](wxCommandEvent&) { reload_from_disk(); }); +// +// append_menu_item(&object_menu, wxID_ANY, _(L("Export object as STL…")), _(L("Export this single object as STL file")), +// [this](wxCommandEvent&) { q->export_stl(true); }); + + // Append "Add..." popupmenu + object_menu.AppendSeparator(); + sidebar->obj_list()->append_menu_items_add_volume(&object_menu); + +// object_menu.AppendSeparator(); + + // ui updates needs to be binded to the parent panel + if (q != nullptr) + { + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects() || can_split_to_volumes()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects()); }, item_split_objects->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_volumes()); }, item_split_volumes->GetId()); + } + return true; +} + +bool Plater::priv::complit_init_sla_object_menu() +{ + wxMenuItem* item_split = append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")), + [this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png"); + + // Add the automatic rotation sub-menu + append_menu_item(&sla_object_menu, wxID_ANY, _(L("Optimize orientation")), _(L("Optimize the rotation of the object for better print results.")), + [this](wxCommandEvent&) { sla_optimize_rotation(); }); + +// sla_object_menu.AppendSeparator(); + + // ui updates needs to be binded to the parent panel + if (q != nullptr) + { + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects()); }, item_split->GetId()); + } + + return true; +} + +bool Plater::priv::complit_init_part_menu() +{ + wxMenuItem* item_split = append_menu_item(&part_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual sub-parts")), + [this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png"); + + auto obj_list = sidebar->obj_list(); + obj_list->append_menu_item_change_type(&part_menu); + +// part_menu.AppendSeparator(); + + // ui updates needs to be binded to the parent panel + if (q != nullptr) + { + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_volumes()); }, item_split->GetId()); + } + + return true; +} + +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Plater::priv::init_view_toolbar() +{ +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + ItemsIconsTexture::Metadata icons_data; + icons_data.filename = "view_toolbar.png"; + icons_data.icon_size = 64; + icons_data.icon_border_size = 0; + icons_data.icon_gap_size = 0; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!view_toolbar.init(icons_data, background_data)) +#else + if (!view_toolbar.init("view_toolbar.png", 64, 0, 0)) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return; + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); + view_toolbar.set_border(5.0f); + view_toolbar.set_gap_size(1.0f); + + GLToolbarItem::Data item; + + item.name = "3D"; + item.tooltip = GUI::L_str("3D editor view"); + item.sprite_id = 0; + item.action_event = EVT_GLVIEWTOOLBAR_3D; + item.is_toggable = false; + if (!view_toolbar.add_item(item)) + return; + + item.name = "Preview"; + item.tooltip = GUI::L_str("Preview"); + item.sprite_id = 1; + item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; + item.is_toggable = false; + if (!view_toolbar.add_item(item)) + return; + + view_toolbar.enable_item("3D"); + view_toolbar.enable_item("Preview"); + + view_toolbar.select_item("3D"); + view_toolbar.set_enabled(true); + + view3D->set_view_toolbar(&view_toolbar); + preview->set_view_toolbar(&view_toolbar); +#else + GLRadioToolbarItem::Data item; + + item.name = "3D"; + item.tooltip = GUI::L_str("3D editor view"); + item.sprite_id = 0; + item.action_event = EVT_GLVIEWTOOLBAR_3D; + if (!view_toolbar.add_item(item)) + return; + + item.name = "Preview"; + item.tooltip = GUI::L_str("Preview"); + item.sprite_id = 1; + item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; + if (!view_toolbar.add_item(item)) + return; + + view3D->set_view_toolbar(&view_toolbar); + preview->set_view_toolbar(&view_toolbar); + + view_toolbar.set_selection("3D"); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +} +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +bool Plater::priv::can_delete_object() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); +} + +bool Plater::priv::can_increase_instances() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); +} + +bool Plater::priv::can_decrease_instances() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); +} + +bool Plater::priv::can_split_to_objects() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts(); +} + +bool Plater::priv::can_split_to_volumes() const +{ + if (printer_technology == ptSLA) + return false; +// int obj_idx = get_selected_object_idx(); +// return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts(); + return sidebar->obj_list()->is_splittable(); +} + +bool Plater::priv::layers_height_allowed() const +{ + int obj_idx = get_selected_object_idx(); +#if ENABLE_REMOVE_TABS_FROM_PLATER + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#else + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && this->canvas3D->is_layers_editing_allowed(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +bool Plater::priv::can_delete_all() const +{ + return !model.objects.empty(); +} + +bool Plater::priv::can_arrange() const +{ + return !model.objects.empty() && !arranging.load(); +} + +bool Plater::priv::can_mirror() const +{ + return get_selection().is_from_single_instance(); +} + +void Plater::priv::update_object_menu() +{ + sidebar->obj_list()->append_menu_items_add_volume(&object_menu); +} + +// Plater / Public + +Plater::Plater(wxWindow *parent, MainFrame *main_frame) + : wxPanel(parent), p(new priv(this, main_frame)) +{ + // Initialization performed in the private c-tor +} + +Plater::~Plater() +{ +#if !ENABLE_REMOVE_TABS_FROM_PLATER + _3DScene::remove_canvas(p->canvas3Dwidget); + p->canvas3D = nullptr; +#endif // !ENABLE_REMOVE_TABS_FROM_PLATER +} + +Sidebar& Plater::sidebar() { return *p->sidebar; } +Model& Plater::model() { return p->model; } +const Print& Plater::fff_print() const { return p->fff_print; } +Print& Plater::fff_print() { return p->fff_print; } +const SLAPrint& Plater::sla_print() const { return p->sla_print; } +SLAPrint& Plater::sla_print() { return p->sla_print; } + +void Plater::load_project() +{ + wxString input_file; + wxGetApp().load_project(this, input_file); + + if (input_file.empty()) + return; + + p->reset(); + p->project_filename = input_file; + + std::vector input_paths; + input_paths.push_back(input_file.wx_str()); + load_files(input_paths); +} + +void Plater::add_model() +{ + wxArrayString input_files; + wxGetApp().import_model(this, input_files); + if (input_files.empty()) + return; + + std::vector input_paths; + for (const auto &file : input_files) { + input_paths.push_back(file.wx_str()); + } + load_files(input_paths, true, false); +} + +void Plater::extract_config_from_project() +{ + wxString input_file; + wxGetApp().load_project(this, input_file); + + if (input_file.empty()) + return; + + std::vector input_paths; + input_paths.push_back(input_file.wx_str()); + load_files(input_paths, false, true); +} + +void Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) { p->load_files(input_files, load_model, load_config); } + +void Plater::update() { p->update(); } + +void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } + +void Plater::select_view(const std::string& direction) { p->select_view(direction); } + +#if ENABLE_REMOVE_TABS_FROM_PLATER +void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); } +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + +void Plater::select_all() { p->select_all(); } + +void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } +void Plater::reset() { p->reset(); } + +void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } + +void Plater::remove_selected() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + this->p->view3D->delete_selected(); +#else + this->p->canvas3D->delete_selected(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +void Plater::increase_instances(size_t num) +{ + int obj_idx = p->get_selected_object_idx(); + if (obj_idx == -1) + return; + + ModelObject* model_object = p->model.objects[obj_idx]; + ModelInstance* model_instance = model_object->instances.back(); + + bool was_one_instance = model_object->instances.size()==1; + + float offset = 10.0; + for (size_t i = 0; i < num; i++, offset += 10.0) { + Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0); + model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation()); +// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); + } + + sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); + + if (p->get_config("autocenter") == "1") { + p->arrange(); + } else { + p->update(); + } + + p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1); + + p->selection_changed(); + + this->p->schedule_background_process(); +} + +void Plater::decrease_instances(size_t num) +{ + int obj_idx = p->get_selected_object_idx(); + if (obj_idx == -1) + return; + + ModelObject* model_object = p->model.objects[obj_idx]; + if (model_object->instances.size() > num) { + for (size_t i = 0; i < num; ++ i) + model_object->delete_last_instance(); + sidebar().obj_list()->decrease_object_instances(obj_idx, num); + } + else { + remove(obj_idx); + } + + p->update(); + + if (!model_object->instances.empty()) + p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1); + + p->selection_changed(); + this->p->schedule_background_process(); +} + +void Plater::set_number_of_copies(/*size_t num*/) +{ + int obj_idx = p->get_selected_object_idx(); + if (obj_idx == -1) + return; + + ModelObject* model_object = p->model.objects[obj_idx]; + + const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), + _("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); + if (num < 0) + return; + + int diff = (int)num - (int)model_object->instances.size(); + if (diff > 0) + increase_instances(diff); + else if (diff < 0) + decrease_instances(-diff); +} + +bool Plater::is_selection_empty() const +{ + return p->get_selection().is_empty(); +} + +void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) +{ + wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); + auto *object = p->model.objects[obj_idx]; + + wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); + + const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); + + remove(obj_idx); + p->load_model_objects(new_objects); +} + +void Plater::export_gcode(fs::path output_path) +{ + if (p->model.objects.empty()) + return; + + // select output file + if (output_path.empty()) { + // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that... + + // If possible, remove accents from accented latin characters. + // This function is useful for generating file names to be processed by legacy firmwares. + fs::path default_output_file; + try { + default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string()); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); + + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), + start_dir, + default_output_file.filename().string(), + GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT + ); + + if (dlg.ShowModal() == wxID_OK) { + fs::path path(dlg.GetPath()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); + output_path = path; + } + } else { + try { + output_path = this->p->background_process.current_print()->output_filepath(output_path.string()); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + } + + if (! output_path.empty()) { + p->export_gcode(std::move(output_path), PrintHostJob()); + } +} + +void Plater::export_stl(bool selection_only) +{ + if (p->model.objects.empty()) { return; } + + auto dialog = p->get_export_file(FT_STL); + if (! dialog) { return; } + + // Store a binary STL + wxString path = dialog->GetPath(); + auto path_cstr = path.c_str(); + + TriangleMesh mesh; + if (selection_only) { + const auto &selection = p->get_selection(); + if (selection.is_wipe_tower()) { return; } + + const auto obj_idx = selection.get_object_idx(); + if (obj_idx == -1) { return; } + mesh = p->model.objects[obj_idx]->mesh(); + } else { + auto mesh = p->model.mesh(); + } + + Slic3r::store_stl(path_cstr, &mesh, true); + p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path)); +} + +void Plater::export_amf() +{ + if (p->model.objects.empty()) { return; } + + auto dialog = p->get_export_file(FT_AMF); + if (! dialog) { return; } + + wxString path = dialog->GetPath(); + auto path_cstr = path.c_str(); + + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + if (Slic3r::store_amf(path_cstr, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { + // Success + p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path)); + } else { + // Failure + p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting AMF file %s")), path)); + } +} + +void Plater::export_3mf(const boost::filesystem::path& output_path) +{ + if (p->model.objects.empty()) { return; } + + wxString path; + bool export_config = true; + if (output_path.empty()) + { + auto dialog = p->get_export_file(FT_3MF); + if (!dialog) { return; } + path = dialog->GetPath(); + export_config = dialog->get_checkbox_value(); + } + else + path = output_path.string(); + + if (!path.Lower().EndsWith(".3mf")) + return; + + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + if (Slic3r::store_3mf(path.c_str(), &p->model, export_config ? &cfg : nullptr)) { + // Success + p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path)); + } else { + // Failure + p->statusbar()->set_status_text(wxString::Format(_(L("Error exporting 3MF file %s")), path)); + } +} + +void Plater::reslice() +{ + //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. + // bitmask of UpdateBackgroundProcessReturnState + unsigned int state = this->p->update_background_process(); + if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) +#if ENABLE_REMOVE_TABS_FROM_PLATER + this->p->view3D->reload_scene(false); +#else + this->p->canvas3D->reload_scene(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running() && !this->p->background_process.finished()) { + // The print is valid and it can be started. + if (this->p->background_process.start()) + this->p->statusbar()->set_cancel_callback([this]() { + this->p->statusbar()->set_status_text(L("Cancelling")); + this->p->background_process.stop(); + }); + } +} + +void Plater::send_gcode() +{ + if (p->model.objects.empty()) { return; } + + PrintHostJob upload_job(p->config); + if (upload_job.empty()) { return; } + + // Obtain default output path + fs::path default_output_file; + try { + default_output_file = this->p->background_process.current_print()->output_filepath(""); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + + Slic3r::PrintHostSendDialog dlg(default_output_file); + if (dlg.ShowModal() == wxID_OK) { + upload_job.upload_data.upload_path = dlg.filename(); + upload_job.upload_data.start_print = dlg.start_print(); + + p->export_gcode(fs::path(), std::move(upload_job)); + } +} + +void Plater::on_extruders_change(int num_extruders) +{ + auto& choices = sidebar().combos_filament(); + + wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); +// sidebar().scrolled_panel()->Freeze(); + + int i = choices.size(); + while ( i < num_extruders ) + { + PresetComboBox* choice/*{ nullptr }*/; + sidebar().init_filament_combo(&choice, i); + choices.push_back(choice); + + // initialize selection + wxGetApp().preset_bundle->update_platter_filament_ui(i, choice); + ++i; + } + + // remove unused choices if any + sidebar().remove_unused_filament_combos(num_extruders); + + sidebar().Layout(); + sidebar().scrolled_panel()->Refresh(); +} + +void Plater::on_config_change(const DynamicPrintConfig &config) +{ + bool update_scheduled = false; + for (auto opt_key : p->config->diff(config)) { + p->config->set_key_value(opt_key, config.option(opt_key)->clone()); + if (opt_key == "printer_technology") + this->set_printer_technology(config.opt_enum(opt_key)); + else if (opt_key == "bed_shape") { +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (p->view3D) p->view3D->set_bed_shape(p->config->option(opt_key)->values); +#else + this->p->canvas3D->set_bed_shape(p->config->option(opt_key)->values); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if (p->preview) p->preview->set_bed_shape(p->config->option(opt_key)->values); + update_scheduled = true; + } + else if (boost::starts_with(opt_key, "wipe_tower") || + // opt_key == "filament_minimal_purge_on_wipe_tower" // ? #ys_FIXME + opt_key == "single_extruder_multi_material") { + update_scheduled = true; + } + else if(opt_key == "variable_layer_height") { + if (p->config->opt_bool("variable_layer_height") != true) { +#if ENABLE_REMOVE_TABS_FROM_PLATER + p->view3D->enable_toolbar_item("layersediting", false); + p->view3D->enable_layers_editing(false); + p->view3D->set_as_dirty(); +#else + p->canvas3D->enable_toolbar_item("layersediting", false); + p->canvas3D->enable_layers_editing(0); + p->canvas3Dwidget->Refresh(); + p->canvas3Dwidget->Update(); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + } +#if ENABLE_REMOVE_TABS_FROM_PLATER + else if (p->view3D->is_layers_editing_allowed()) { + p->view3D->enable_toolbar_item("layersediting", true); +#else + else if (p->canvas3D->is_layers_editing_allowed()) { + p->canvas3D->enable_toolbar_item("layersediting", true); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + } + } + else if(opt_key == "extruder_colour") { + update_scheduled = true; + p->preview->set_number_extruders(p->config->option(opt_key)->values.size()); + } else if(opt_key == "max_print_height") { + update_scheduled = true; + } else if(opt_key == "printer_model") { + // update to force bed selection(for texturing) +#if ENABLE_REMOVE_TABS_FROM_PLATER + if (p->view3D) p->view3D->set_bed_shape(p->config->option("bed_shape")->values); +#else + p->canvas3D->set_bed_shape(p->config->option("bed_shape")->values); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if (p->preview) p->preview->set_bed_shape(p->config->option("bed_shape")->values); + update_scheduled = true; + } + } + + { + const auto prin_host_opt = p->config->option("print_host"); + p->sidebar->show_send(prin_host_opt != nullptr && !prin_host_opt->value.empty()); + } + + if (update_scheduled) + update(); + + if (p->main_frame->is_loaded()) + this->p->schedule_background_process(); +} + +const wxString& Plater::get_project_filename() const +{ + return p->project_filename; +} + +bool Plater::is_export_gcode_scheduled() const +{ + return p->background_process.is_export_scheduled(); +} + +int Plater::get_selected_object_idx() +{ + return p->get_selected_object_idx(); +} + +bool Plater::is_single_full_object_selection() const +{ + return p->get_selection().is_single_full_object(); +} + +GLCanvas3D* Plater::canvas3D() +{ +#if ENABLE_REMOVE_TABS_FROM_PLATER + return p->view3D->get_canvas3d(); +#else + return p->canvas3D; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +} + +PrinterTechnology Plater::printer_technology() const +{ + return p->printer_technology; +} + +void Plater::set_printer_technology(PrinterTechnology printer_technology) +{ + p->printer_technology = printer_technology; + if (p->background_process.select_technology(printer_technology)) { + // Update the active presets. + } + //FIXME for SLA synchronize + //p->background_process.apply(Model)! +} + +void Plater::changed_object(int obj_idx) +{ + if (obj_idx < 0) + return; + auto list = wxGetApp().obj_list(); + wxASSERT(list != nullptr); + if (list == nullptr) + return; + + if (list->is_parts_changed()) { + // recenter and re - align to Z = 0 + auto model_object = p->model.objects[obj_idx]; +#if !ENABLE_MODELVOLUME_TRANSFORM + model_object->center_around_origin(); +#endif // !ENABLE_MODELVOLUME_TRANSFORM + model_object->ensure_on_bed(); + if (this->p->printer_technology == ptSLA) { + // Update the SLAPrint from the current Model, so that the reload_scene() + // pulls the correct data. + this->p->update_background_process(); + } +#if ENABLE_REMOVE_TABS_FROM_PLATER + p->view3D->reload_scene(false); +#else + p->canvas3D->reload_scene(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + } + + // update print + this->p->schedule_background_process(); + if (list->is_parts_changed() || list->is_part_settings_changed()) { +#if !ENABLE_MODIFIED_CAMERA_TARGET + p->canvas3D->zoom_to_volumes(); +#endif // !ENABLE_MODIFIED_CAMERA_TARGET + } +} + +void Plater::fix_through_netfabb(const int obj_idx) { p->fix_through_netfabb(obj_idx); } + +void Plater::update_object_menu() { p->update_object_menu(); } + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp new file mode 100644 index 000000000..334ab740a --- /dev/null +++ b/src/slic3r/GUI/Plater.hpp @@ -0,0 +1,174 @@ +#ifndef slic3r_Plater_hpp_ +#define slic3r_Plater_hpp_ + +#include +#include +#include + +#include +#include + +#include "Preset.hpp" + +class wxButton; +class wxBoxSizer; +class wxGLCanvas; +class wxScrolledWindow; +class wxString; + +namespace Slic3r { + +class Model; +class Print; +class SLAPrint; + +namespace GUI { + +class MainFrame; +class ConfigOptionsGroup; +class ObjectManipulation; +class ObjectSettings; +class ObjectList; +class GLCanvas3D; + +using t_optgroups = std::vector >; + +class Plater; + +class PresetComboBox : public wxBitmapComboBox +{ +public: + PresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~PresetComboBox(); + + void set_label_marker(int item); + void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; } + int get_extruder_idx() const { return extruder_idx; } + void check_selection(); + +private: + typedef std::size_t Marker; + enum { LABEL_ITEM_MARKER = 0x4d }; + + Preset::Type preset_type; + int last_selected; + int extruder_idx = -1; +}; + +class Sidebar : public wxPanel +{ + /*ConfigMenuIDs*/int m_mode; +public: + Sidebar(Plater *parent); + Sidebar(Sidebar &&) = delete; + Sidebar(const Sidebar &) = delete; + Sidebar &operator=(Sidebar &&) = delete; + Sidebar &operator=(const Sidebar &) = delete; + ~Sidebar(); + + void init_filament_combo(PresetComboBox **combo, const int extr_idx); + void remove_unused_filament_combos(const int current_extruder_count); + void update_presets(Slic3r::Preset::Type preset_type); + + ObjectManipulation* obj_manipul(); + ObjectList* obj_list(); + ObjectSettings* obj_settings(); + wxScrolledWindow* scrolled_panel(); + + ConfigOptionsGroup* og_freq_chng_params(); + wxButton* get_wiping_dialog_button(); + void update_objects_list_extruder_column(int extruders_count); + void show_info_sizer(); + void show_sliced_info_sizer(const bool show); + void enable_buttons(bool enable); + void show_reslice(bool show); + void show_send(bool show); + bool is_multifilament(); + void set_mode_value(const /*ConfigMenuIDs*/int mode) { m_mode = mode; } + + std::vector& combos_filament(); +private: + struct priv; + std::unique_ptr p; +}; + +class Plater: public wxPanel +{ +public: + using fs_path = boost::filesystem::path; + + Plater(wxWindow *parent, MainFrame *main_frame); + Plater(Plater &&) = delete; + Plater(const Plater &) = delete; + Plater &operator=(Plater &&) = delete; + Plater &operator=(const Plater &) = delete; + ~Plater(); + + Sidebar& sidebar(); + Model& model(); + const Print& fff_print() const; + Print& fff_print(); + const SLAPrint& sla_print() const; + SLAPrint& sla_print(); + + void load_project(); + void add_model(); + void extract_config_from_project(); + + void load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); + + void update(); + void select_view(const std::string& direction); +#if ENABLE_REMOVE_TABS_FROM_PLATER + void select_view_3D(const std::string& name); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + // Called after the Preferences dialog is closed and the program settings are saved. + // Update the UI based on the current preferences. + void update_ui_from_settings(); + + void select_all(); + void remove(size_t obj_idx); + void reset(); + void delete_object_from_model(size_t obj_idx); + void remove_selected(); + void increase_instances(size_t num = 1); + void decrease_instances(size_t num = 1); + void set_number_of_copies(/*size_t num*/); + bool is_selection_empty() const; + + void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); + + // Note: empty path means "use the default" + void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); + void export_stl(bool selection_only = false); + void export_amf(); + void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + void reslice(); + void changed_object(int obj_idx); + void fix_through_netfabb(const int obj_idx); + void send_gcode(); + + void on_extruders_change(int extruders_count); + void on_config_change(const DynamicPrintConfig &config); + + void update_object_menu(); + + const wxString& get_project_filename() const; + bool is_export_gcode_scheduled() const; + + int get_selected_object_idx(); + bool is_single_full_object_selection() const; + GLCanvas3D* canvas3D(); + + PrinterTechnology printer_technology() const; + void set_printer_technology(PrinterTechnology printer_technology); + +private: + struct priv; + std::unique_ptr p; +}; + + +}} + +#endif diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 89a8ead92..5f5da8bd4 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,15 +1,15 @@ #include "Preferences.hpp" #include "AppConfig.hpp" #include "OptionsGroup.hpp" +#include "I18N.hpp" namespace Slic3r { namespace GUI { -PreferencesDialog::PreferencesDialog(wxWindow* parent, int event_preferences) : - wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize), - m_event_preferences(event_preferences) { - build(); - } +PreferencesDialog::PreferencesDialog(wxWindow* parent) : + wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize) { + build(); +} void PreferencesDialog::build() { @@ -124,10 +124,7 @@ void PreferencesDialog::accept() Close(); // needed on Linux // Nothify the UI to update itself from the ini file. - if (m_event_preferences > 0) { - wxCommandEvent event(m_event_preferences); - get_app()->ProcessEvent(event); - } + wxGetApp().update_ui_from_settings(); } } // GUI diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index d01d78b70..363daebbc 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -15,10 +15,9 @@ class PreferencesDialog : public wxDialog { std::map m_values; std::shared_ptr m_optgroup; - int m_event_preferences; public: - PreferencesDialog(wxWindow* parent, int event_preferences); - ~PreferencesDialog(){ } + PreferencesDialog(wxWindow* parent); + ~PreferencesDialog() {} void build(); void accept(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 8e61fa7d4..cee1dee5c 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -1,9 +1,15 @@ -//#undef NDEBUG #include #include "Preset.hpp" #include "AppConfig.hpp" #include "BitmapCache.hpp" +#include "I18N.hpp" + +#ifdef _MSC_VER + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include +#endif /* _MSC_VER */ #include #include @@ -13,6 +19,7 @@ #include #include +#include #include #include #include @@ -25,9 +32,10 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/Utils.hpp" -#include "../../libslic3r/PlaceholderParser.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "libslic3r/PlaceholderParser.hpp" +#include "Plater.hpp" using boost::property_tree::ptree; @@ -169,10 +177,12 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr { const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::nozzle_options()) { + if (key == "default_filament_profile") + continue; auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector() && key != "default_filament_profile") + if (opt != nullptr && opt->is_vector()) static_cast(opt)->resize(num_extruders, defaults.option(key)); } } @@ -190,7 +200,7 @@ void Preset::normalize(DynamicPrintConfig &config) size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::filament_options()) { - if (key == "compatible_printers") + if (key == "compatible_prints" || key == "compatible_printers") continue; auto *opt = config.option(key, false); /*assert(opt != nullptr); @@ -201,31 +211,27 @@ void Preset::normalize(DynamicPrintConfig &config) // The following keys are mandatory for the UI, but they are not part of FullPrintConfig, therefore they are handled separately. for (const std::string &key : { "filament_settings_id" }) { auto *opt = config.option(key, false); - assert(opt != nullptr); - assert(opt->type() == coStrings); + assert(opt == nullptr || opt->type() == coStrings); if (opt != nullptr && opt->type() == coStrings) static_cast(opt)->values.resize(n, std::string()); } } } -DynamicPrintConfig& Preset::load(const std::vector &keys, const StaticPrintConfig &defaults) +std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config) { - // Set the configuration from the defaults. - this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); - if (! this->is_default) { - // Load the preset file, apply preset values on top of defaults. - try { - this->config.load_from_ini(this->file); - Preset::normalize(this->config); - } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + this->file + "\n\tReason: " + err.what()); - } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file + "\n\tReason: " + err.what()); + std::string incorrect_keys; + for (const std::string &key : config.keys()) + if (! default_config.has(key)) { + if (incorrect_keys.empty()) + incorrect_keys = key; + else { + incorrect_keys += ", "; + incorrect_keys += key; + } + config.erase(key); } - } - this->loaded = true; - return this->config; + return incorrect_keys; } void Preset::save() @@ -239,6 +245,25 @@ std::string Preset::label() const return this->name + (this->is_dirty ? g_suffix_modified : ""); } +bool Preset::is_compatible_with_print(const Preset &active_print) const +{ + auto &condition = this->compatible_prints_condition(); + auto *compatible_prints = dynamic_cast(this->config.option("compatible_prints")); + bool has_compatible_prints = compatible_prints != nullptr && ! compatible_prints->values.empty(); + if (! has_compatible_prints && ! condition.empty()) { + try { + return PlaceholderParser::evaluate_boolean_expression(condition, active_print.config); + } catch (const std::runtime_error &err) { + //FIXME in case of an error, return "compatible with everything". + printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.name.c_str(), err.what()); + return true; + } + } + return this->is_default || active_print.name.empty() || ! has_compatible_prints || + std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) != + compatible_prints->values.end(); +} + bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const { auto &condition = this->compatible_printers_condition(); @@ -268,9 +293,12 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const return this->is_compatible_with_printer(active_printer, &config); } -bool Preset::update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) +bool Preset::update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print) { - return this->is_compatible = is_compatible_with_printer(active_printer, extra_config); + this->is_compatible = is_compatible_with_printer(active_printer, extra_config); + if (active_print != nullptr) + this->is_compatible &= is_compatible_with_print(*active_print); + return this->is_compatible; } void Preset::set_visible_from_appconfig(const AppConfig &app_config) @@ -305,6 +333,7 @@ const std::vector& Preset::print_options() "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", + "clip_multipart_objects", "over_bridge_flow_ratio", "clip_multipart_objects", "enforce_full_fill_volume", "external_infill_margin", "bridged_infill_margin", "elefant_foot_compensation", "xy_size_compensation", "hole_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", @@ -332,7 +361,9 @@ const std::vector& Preset::filament_options() "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "top_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", - "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits" + "min_print_speed", "start_filament_gcode", "end_filament_gcode", + "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -348,7 +379,8 @@ const std::vector& Preset::printer_options() "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", - "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", + "default_print_profile", "inherits", "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", @@ -375,16 +407,35 @@ const std::vector& Preset::nozzle_options() return s_opts; } -const std::vector& Preset::sla_printer_options() +const std::vector& Preset::sla_print_options() { static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "printer_technology", - "bed_shape", "max_print_height", - "display_width", "display_height", "display_pixels_x", "display_pixels_y", - "printer_correction", - "printer_notes", + "layer_height", + "supports_enable", + "support_head_front_diameter", + "support_head_penetration", + "support_head_width", + "support_pillar_diameter", + "support_pillar_widening_factor", + "support_base_diameter", + "support_base_height", + "support_critical_angle", + "support_max_bridge_length", + "support_object_elevation", + "support_density_at_horizontal", + "support_density_at_45", + "support_minimal_z", + "pad_enable", + "pad_wall_thickness", + "pad_wall_height", + "pad_max_merge_distance", + "pad_edge_radius", + "output_filename_format", + "default_sla_print_profile", + "compatible_printers", + "compatible_printers_condition", "inherits" }; } @@ -396,12 +447,30 @@ const std::vector& Preset::sla_material_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { - "layer_height", "initial_layer_height", + "initial_layer_height", "exposure_time", "initial_exposure_time", "material_correction_printing", "material_correction_curing", "material_notes", - "compatible_printers", - "compatible_printers_condition", "inherits" + "default_sla_material_profile", + "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits" + }; + } + return s_opts; +} + +const std::vector& Preset::sla_printer_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "printer_technology", + "bed_shape", "max_print_height", + "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_orientation", + "printer_correction", + "printer_notes", + "inherits" }; } return s_opts; @@ -446,20 +515,33 @@ void PresetCollection::add_default_preset(const std::vector &keys, { // Insert just the default preset. m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().load(keys, defaults); + m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + m_presets.back().loaded = true; ++ m_num_default_presets; } +bool is_file_plain(const std::string &path) +{ +#ifdef _MSC_VER + DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str()); + return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; +#else + return true; +#endif +} + // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); m_dir_path = dir.string(); - t_config_option_keys keys = this->default_preset().config.keys(); std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") && + // Ignore system and hidden files, which may be created by the DropBox synchronisation process. + // https://github.com/prusa3d/Slic3r/issues/1298 + is_file_plain(dir_entry.path().string())) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); @@ -472,8 +554,26 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); - //FIXME One should initialize with SLAFullPrintConfig for the SLA profiles! - preset.load(keys, static_cast(FullPrintConfig::defaults())); + // Load the preset file, apply preset values on top of defaults. + try { + DynamicPrintConfig config; + config.load_from_ini(preset.file); + // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. + const Preset &default_preset = this->default_preset_for(config); + preset.config = default_preset.config; + preset.config.apply(std::move(config)); + Preset::normalize(preset.config); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); + if (! incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << + preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + preset.loaded = true; + } catch (const std::ifstream::failure &err) { + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + } catch (const std::runtime_error &err) { + throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + } m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); @@ -500,9 +600,11 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const Dyna t_config_option_keys diff = cfg1.diff(cfg2); // Following keys are used by the UI, not by the slicing core, therefore they are not important // when comparing profiles for equality. Ignore them. - for (const char *key : { "compatible_printers", "compatible_printers_condition", "inherits", - "print_settings_id", "filament_settings_id", "printer_settings_id", - "printer_model", "printer_variant", "default_print_profile", "default_filament_profile" }) + for (const char *key : { "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits", + "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", + "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", + "printhost_apikey", "printhost_cafile" }) diff.erase(std::remove(diff.begin(), diff.end(), key), diff.end()); // Preset with the same name as stored inside the config exists. return diff.empty(); @@ -524,11 +626,12 @@ Preset& PresetCollection::load_external_preset( bool select) { // Load the preset over a default preset, so that the missing fields are filled in from the default preset. - DynamicPrintConfig cfg(this->default_preset().config); + DynamicPrintConfig cfg(this->default_preset_for(config).config); cfg.apply_only(config, cfg.keys(), true); // Is there a preset already loaded with the name stored inside the config? std::deque::iterator it = this->find_preset_internal(original_name); - if (it != m_presets.end() && it->name == original_name && profile_print_params_same(it->config, cfg)) { + bool found = it != m_presets.end() && it->name == original_name; + if (found && profile_print_params_same(it->config, cfg)) { // The preset exists and it matches the values stored inside config. if (select) this->select_preset(it - m_presets.begin()); @@ -536,7 +639,7 @@ Preset& PresetCollection::load_external_preset( } // Update the "inherits" field. std::string &inherits = Preset::inherits(cfg); - if (it != m_presets.end() && inherits.empty()) { + if (found && inherits.empty()) { // There is a profile with the same name already loaded. Should we update the "inherits" field? if (it->vendor == nullptr) inherits = it->inherits(); @@ -715,7 +818,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) } } -size_t PresetCollection::update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible) +size_t PresetCollection::update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible) { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); @@ -726,9 +829,9 @@ size_t PresetCollection::update_compatible_with_printer_internal(const Preset &a bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; - if (! preset_edited.update_compatible_with_printer(active_printer, &config) && + if (! preset_edited.update_compatible(active_printer, &config, active_print) && selected && unselect_if_incompatible) - m_idx_selected = (size_t)-1; + m_idx_selected = -1; if (selected) preset_selected.is_compatible = preset_edited.is_compatible; } @@ -745,7 +848,7 @@ size_t PresetCollection::update_compatible_with_printer_internal(const Preset &a // Update the wxChoice UI component from this list of presets. // Hide the -void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) +void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) return; @@ -757,12 +860,12 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) const Preset &selected_preset = this->get_selected_preset(); // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. - bool wide_icons = !selected_preset.is_compatible && m_bitmap_incompatible != nullptr; + bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; std::map nonsys_presets; wxString selected = ""; if (!this->m_presets.front().is_visible) - ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) @@ -789,7 +892,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) bmp = m_bitmap_cache->insert(bitmap_key, bmps); } - if (preset.is_default || preset.is_system){ + if (preset.is_default || preset.is_system) { ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) @@ -802,11 +905,11 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (i + 1 == m_num_default_presets) - ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); } if (!nonsys_presets.empty()) { - ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) @@ -849,7 +952,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati bmp = m_bitmap_cache->insert(bitmap_key, bmps); } - if (preset.is_default || preset.is_system){ + if (preset.is_default || preset.is_system) { ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) @@ -894,7 +997,8 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) std::string old_label = ui->GetString(ui_id).utf8_str().data(); std::string preset_name = Preset::remove_suffix_modified(old_label); const Preset *preset = this->find_preset(preset_name, false); - assert(preset != nullptr); +// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. +// assert(preset != nullptr); if (preset != nullptr) { std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; if (old_label != new_label) @@ -909,17 +1013,59 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } +template +void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c) +{ + const T* opt_init = static_cast(other.option(opt_key)); + const T* opt_cur = static_cast(this_c.option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } +} + +// Use deep_diff to correct return of changed options, considering individual options for each extruder. +inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other) +{ + t_config_option_keys diff; + for (const t_config_option_key &opt_key : config_this.keys()) { + const ConfigOption *this_opt = config_this.option(opt_key); + const ConfigOption *other_opt = config_other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + { + if (opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers") { + diff.emplace_back(opt_key); + continue; + } + switch (other_opt->type()) + { + case coInts: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coBools: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coFloats: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + default: diff.emplace_back(opt_key); break; + } + } + } + return diff; +} + std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) { std::vector changed; if (edited != nullptr && reference != nullptr) { changed = deep_compare ? - reference->config.deep_diff(edited->config) : + deep_diff(reference->config, edited->config) : reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. - std::initializer_list optional_keys { "compatible_printers" }; + std::initializer_list optional_keys { "compatible_prints", "compatible_printers" }; for (auto &opt_key : optional_keys) { if (reference->config.has(opt_key) != edited->config.has(opt_key)) changed.emplace_back(opt_key); @@ -1013,10 +1159,12 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe std::string PresetCollection::name() const { switch (this->type()) { - case Preset::TYPE_PRINT: return "print"; - case Preset::TYPE_FILAMENT: return "filament"; - case Preset::TYPE_PRINTER: return "printer"; - default: return "invalid"; + case Preset::TYPE_PRINT: return "print"; + case Preset::TYPE_FILAMENT: return "filament"; + case Preset::TYPE_SLA_PRINT: return "SLA print"; + case Preset::TYPE_SLA_MATERIAL: return "SLA material"; + case Preset::TYPE_PRINTER: return "printer"; + default: return "invalid"; } } @@ -1027,4 +1175,10 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } +const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const +{ + const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); + return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); +} + } // namespace Slic3r diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 821d7dc54..96230ad2b 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -6,8 +6,8 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" #include "slic3r/Utils/Semver.hpp" class wxBitmap; @@ -22,6 +22,7 @@ class PresetBundle; namespace GUI { class BitmapCache; + class PresetComboBox; } enum ConfigFileType @@ -83,6 +84,7 @@ public: { TYPE_INVALID, TYPE_PRINT, + TYPE_SLA_PRINT, TYPE_FILAMENT, TYPE_SLA_MATERIAL, TYPE_PRINTER, @@ -124,9 +126,6 @@ public: // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; - // Load this profile for the following keys only. - DynamicPrintConfig& load(const std::vector &keys, const StaticPrintConfig &defaults); - void save(); // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. @@ -137,6 +136,7 @@ public: void set_dirty(bool dirty = true) { this->is_dirty = dirty; } void reset_dirty() { this->is_dirty = false; } + bool is_compatible_with_print(const Preset &active_print) const; bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const; bool is_compatible_with_printer(const Preset &active_printer) const; @@ -145,17 +145,28 @@ public: std::string& inherits() { return Preset::inherits(this->config); } const std::string& inherits() const { return Preset::inherits(const_cast(this)->config); } + // Returns the "compatible_prints_condition". + static std::string& compatible_prints_condition(DynamicPrintConfig &cfg) { return cfg.option("compatible_prints_condition", true)->value; } + std::string& compatible_prints_condition() { + assert(this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL); + return Preset::compatible_prints_condition(this->config); + } + const std::string& compatible_prints_condition() const { return const_cast(this)->compatible_prints_condition(); } + // Returns the "compatible_printers_condition". static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option("compatible_printers_condition", true)->value; } - std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); } - const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast(this)->config); } + std::string& compatible_printers_condition() { + assert(this->type == TYPE_PRINT || this->type == TYPE_SLA_PRINT || this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL); + return Preset::compatible_printers_condition(this->config); + } + const std::string& compatible_printers_condition() const { return const_cast(this)->compatible_printers_condition(); } static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option>("printer_technology", true)->value; } PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); } const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast(this)->config); } // Mark this preset as compatible if it is compatible with active_printer. - bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); + bool update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print = nullptr); // Set is_visible according to application config void set_visible_from_appconfig(const AppConfig &app_config); @@ -175,13 +186,16 @@ public: static const std::vector& sla_printer_options(); static const std::vector& sla_material_options(); + static const std::vector& sla_print_options(); - static void update_suffix_modified(); + static void update_suffix_modified(); + static void normalize(DynamicPrintConfig &config); + // Report configuration fields, which are misplaced into a wrong group, remove them from the config. + static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config); protected: friend class PresetCollection; friend class PresetBundle; - static void normalize(DynamicPrintConfig &config); // Resize the extruder specific vectors () static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); static const std::string& suffix_modified(); @@ -199,9 +213,11 @@ public: typedef std::deque::iterator Iterator; typedef std::deque::const_iterator ConstIterator; Iterator begin() { return m_presets.begin() + m_num_default_presets; } - ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; } + ConstIterator begin() const { return m_presets.cbegin() + m_num_default_presets; } + ConstIterator cbegin() const { return m_presets.cbegin() + m_num_default_presets; } Iterator end() { return m_presets.end(); } - ConstIterator end() const { return m_presets.end(); } + ConstIterator end() const { return m_presets.cend(); } + ConstIterator cend() const { return m_presets.cend(); } void reset(bool delete_files); @@ -278,8 +294,9 @@ public: static const std::string& get_suffix_modified(); // Return a preset possibly with modifications. - Preset& default_preset() { return m_presets.front(); } - const Preset& default_preset() const { return m_presets.front(); } + Preset& default_preset(size_t idx = 0) { assert(idx < m_num_default_presets); return m_presets[idx]; } + const Preset& default_preset(size_t idx = 0) const { assert(idx < m_num_default_presets); return m_presets[idx]; } + virtual const Preset& default_preset_for(const DynamicPrintConfig & /* config */) const { return this->default_preset(); } // Return a preset by an index. If the preset is active, a temporary copy is returned. Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; } const Preset& preset(size_t idx) const { return const_cast(this)->preset(idx); } @@ -328,14 +345,14 @@ public: // For Print / Filament presets, disable those, which are not compatible with the printer. template - void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition) + void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible, PreferedCondition prefered_condition) { - if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1) + if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1) // Find some other compatible preset, or the "-- default --" preset. this->select_preset(this->first_compatible_idx(prefered_condition)); } - void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) - { this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); } + void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible) + { this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const std::string&){return true;}); } size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } @@ -355,7 +372,7 @@ public: // Update the choice UI from the list of presets. // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. - void update_platter_ui(wxBitmapComboBox *ui); + void update_platter_ui(GUI::PresetComboBox *ui); // Update a dirty floag of the current preset, update the labels of the UI component accordingly. // Return true if the dirty flag changed. @@ -403,7 +420,7 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } - size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible); + size_t update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible); static std::vector dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false); @@ -439,6 +456,16 @@ private: friend class PresetBundle; }; +// Printer supports the FFF and SLA technologies, with different set of configuration values, +// therefore this PresetCollection needs to handle two defaults. +class PrinterPresetCollection : public PresetCollection +{ +public: + PrinterPresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -") : + PresetCollection(type, keys, defaults, default_name) {} + const Preset& default_preset_for(const DynamicPrintConfig &config) const override; +}; + } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index adb1b7aea..ee7ba279f 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1,8 +1,9 @@ -//#undef NDEBUG #include #include "PresetBundle.hpp" #include "BitmapCache.hpp" +#include "Plater.hpp" +#include "I18N.hpp" #include #include @@ -24,9 +25,8 @@ #include #include -#include "../../libslic3r/libslic3r.h" -#include "../../libslic3r/PlaceholderParser.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -35,6 +35,7 @@ namespace Slic3r { static std::vector s_project_options { + "colorprint_heights", "wiping_volumes_extruders", "wiping_volumes_matrix" }; @@ -43,6 +44,7 @@ PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())), filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), + sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap), @@ -74,21 +76,34 @@ PresetBundle::PresetBundle() : this->sla_materials.default_preset().compatible_printers_condition(); this->sla_materials.default_preset().inherits(); + this->sla_prints.default_preset().config.optptr("sla_print_settings_id", true); + this->sla_prints.default_preset().compatible_printers_condition(); + this->sla_prints.default_preset().inherits(); + this->printers.add_default_preset(Preset::sla_printer_options(), static_cast(SLAFullPrintConfig::defaults()), "- default SLA -"); this->printers.preset(1).printer_technology() = ptSLA; for (size_t i = 0; i < 2; ++ i) { - Preset &preset = this->printers.preset(i); + // The following ugly switch is to avoid printers.preset(0) to return the edited instance, as the 0th default is the current one. + Preset &preset = this->printers.default_preset(i); preset.config.optptr("printer_settings_id", true); preset.config.optptr("printer_vendor", true); preset.config.optptr("printer_model", true); preset.config.optptr("printer_variant", true); - preset.config.optptr("default_print_profile", true); - preset.config.option("default_filament_profile", true)->values = { "" }; + if (i == 0) { + preset.config.optptr("default_print_profile", true); + preset.config.option("default_filament_profile", true)->values = { "" }; + } + else { + preset.config.optptr("default_sla_print_profile", true); + preset.config.optptr("default_sla_material_profile", true); + } + // default_sla_material_profile preset.inherits(); } // Load the default preset bitmaps. this->prints .load_bitmap_default("cog.png"); + this->sla_prints .load_bitmap_default("package_green.png"); this->filaments .load_bitmap_default("spool.png"); this->sla_materials.load_bitmap_default("package_green.png"); this->printers .load_bitmap_default("printer_empty.png"); @@ -96,6 +111,7 @@ PresetBundle::PresetBundle() : // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. this->prints .select_preset(0); + this->sla_prints .select_preset(0); this->filaments .select_preset(0); this->sla_materials.select_preset(0); this->printers .select_preset(0); @@ -126,12 +142,14 @@ void PresetBundle::reset(bool delete_files) // Clear the existing presets, delete their respective files. this->vendors.clear(); this->prints .reset(delete_files); + this->sla_prints .reset(delete_files); this->filaments .reset(delete_files); this->sla_materials.reset(delete_files); this->printers .reset(delete_files); this->filament_presets.clear(); this->filament_presets.emplace_back(this->filaments.get_selected_preset_name()); this->obsolete_presets.prints.clear(); + this->obsolete_presets.sla_prints.clear(); this->obsolete_presets.filaments.clear(); this->obsolete_presets.sla_materials.clear(); this->obsolete_presets.printers.clear(); @@ -149,12 +167,14 @@ void PresetBundle::setup_directories() data_dir / "presets", data_dir / "presets" / "print", data_dir / "presets" / "filament", + data_dir / "presets" / "sla_print", data_dir / "presets" / "sla_material", data_dir / "presets" / "printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", + data_dir / "sla_print", data_dir / "sla_material", data_dir / "printer" #endif @@ -186,6 +206,11 @@ void PresetBundle::load_presets(const AppConfig &config) } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } + try { + this->sla_prints.load_presets(dir_user_presets, "sla_print"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } try { this->filaments.load_presets(dir_user_presets, "filament"); } catch (const std::runtime_error &err) { @@ -202,7 +227,7 @@ void PresetBundle::load_presets(const AppConfig &config) errors_cummulative += err.what(); } this->update_multi_material_filament_presets(); - this->update_compatible_with_printer(false); + this->update_compatible(false); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); @@ -260,13 +285,16 @@ std::vector PresetBundle::merge_presets(PresetBundle &&other) { this->vendors.insert(other.vendors.begin(), other.vendors.end()); std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); + std::vector duplicate_sla_prints = this->sla_prints .merge_presets(std::move(other.sla_prints), this->vendors); std::vector duplicate_filaments = this->filaments .merge_presets(std::move(other.filaments), this->vendors); std::vector duplicate_sla_materials = this->sla_materials.merge_presets(std::move(other.sla_materials), this->vendors); std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); + append(this->obsolete_presets.sla_prints, std::move(other.obsolete_presets.sla_prints)); append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); append(this->obsolete_presets.sla_materials, std::move(other.obsolete_presets.sla_materials)); append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); + append(duplicate_prints, std::move(duplicate_sla_prints)); append(duplicate_prints, std::move(duplicate_filaments)); append(duplicate_prints, std::move(duplicate_sla_materials)); append(duplicate_prints, std::move(duplicate_printers)); @@ -300,6 +328,7 @@ void PresetBundle::load_selections(const AppConfig &config) // Parse the initial print / filament / printer profile names. std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::string initial_sla_print_profile_name = remove_ini_suffix(config.get("presets", "sla_print")); std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); std::string initial_sla_material_profile_name = remove_ini_suffix(config.get("presets", "sla_material")); std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); @@ -307,13 +336,22 @@ void PresetBundle::load_selections(const AppConfig &config) // Activate print / filament / printer profiles from the config. // If the printer profile enumerated by the config are not visible, select an alternate preset. // Do not select alternate profiles for the print / filament profiles as those presets - // will be selected by the following call of this->update_compatible_with_printer(true). - prints.select_preset_by_name_strict(initial_print_profile_name); - filaments.select_preset_by_name_strict(initial_filament_profile_name); - sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); + // will be selected by the following call of this->update_compatible(true). printers.select_preset_by_name(initial_printer_profile_name, true); + PrinterTechnology printer_technology = printers.get_selected_preset().printer_technology(); + if (printer_technology == ptFFF) { + prints.select_preset_by_name_strict(initial_print_profile_name); + filaments.select_preset_by_name_strict(initial_filament_profile_name); + sla_prints.select_preset_by_name(initial_sla_material_profile_name, true); + sla_materials.select_preset_by_name(initial_sla_material_profile_name, true); + } else { + prints.select_preset_by_name(initial_print_profile_name, true); + filaments.select_preset_by_name(initial_filament_profile_name, true); + sla_prints.select_preset_by_name_strict(initial_sla_material_profile_name); + sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); + } - if (printers.get_selected_preset().printer_technology() == ptFFF){ + if (printers.get_selected_preset().printer_technology() == ptFFF) { // Load the names of the other filament profiles selected for a multi-material printer. auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); @@ -325,7 +363,7 @@ void PresetBundle::load_selections(const AppConfig &config) break; this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); } - // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. + // Do not define the missing filaments, so that the update_compatible() will use the preferred filaments. this->filament_presets.resize(num_extruders, ""); } @@ -333,15 +371,15 @@ void PresetBundle::load_selections(const AppConfig &config) // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not // exist. - this->update_compatible_with_printer(true); + this->update_compatible(true); this->update_multi_material_filament_presets(); } // Export selections (current print, current filaments, current printer) into config.ini void PresetBundle::export_selections(AppConfig &config) { - assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); + assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1); + assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset_name()); config.set("presets", "filament", filament_presets.front()); @@ -350,24 +388,9 @@ void PresetBundle::export_selections(AppConfig &config) sprintf(name, "filament_%d", i); config.set("presets", name, filament_presets[i]); } + config.set("presets", "sla_print", sla_prints.get_selected_preset_name()); config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); - config.set("presets", "printer", printers.get_selected_preset_name()); -} - -void PresetBundle::export_selections(PlaceholderParser &pp) -{ - assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); - switch (printers.get_edited_preset().printer_technology()) { - case ptFFF: - pp.set("print_preset", prints.get_selected_preset().name); - pp.set("filament_preset", filament_presets); - break; - case ptSLA: - pp.set("sla_material_preset", sla_materials.get_selected_preset().name); - break; - } - pp.set("printer_preset", printers.get_selected_preset().name); + config.set("presets", "printer", printers.get_selected_preset_name()); } bool PresetBundle::load_compatible_bitmaps() @@ -387,24 +410,28 @@ bool PresetBundle::load_compatible_bitmaps() if (loaded_compatible) { prints .set_bitmap_compatible(m_bitmapCompatible); filaments .set_bitmap_compatible(m_bitmapCompatible); + sla_prints .set_bitmap_compatible(m_bitmapCompatible); sla_materials.set_bitmap_compatible(m_bitmapCompatible); // printers .set_bitmap_compatible(m_bitmapCompatible); } if (loaded_incompatible) { prints .set_bitmap_incompatible(m_bitmapIncompatible); filaments .set_bitmap_incompatible(m_bitmapIncompatible); + sla_prints .set_bitmap_incompatible(m_bitmapIncompatible); sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); // printers .set_bitmap_incompatible(m_bitmapIncompatible); } if (loaded_lock) { prints .set_bitmap_lock(m_bitmapLock); filaments .set_bitmap_lock(m_bitmapLock); + sla_prints .set_bitmap_lock(m_bitmapLock); sla_materials.set_bitmap_lock(m_bitmapLock); printers .set_bitmap_lock(m_bitmapLock); } if (loaded_lock_open) { prints .set_bitmap_lock_open(m_bitmapLock); filaments .set_bitmap_lock_open(m_bitmapLock); + sla_prints .set_bitmap_lock_open(m_bitmapLock); sla_materials.set_bitmap_lock_open(m_bitmapLock); printers .set_bitmap_lock_open(m_bitmapLock); } @@ -418,6 +445,14 @@ DynamicPrintConfig PresetBundle::full_config() const this->full_sla_config(); } +DynamicPrintConfig PresetBundle::full_config_secure() const +{ + DynamicPrintConfig config = this->full_config(); + config.erase("printhost_apikey"); + config.erase("printhost_cafile"); + return config; +} + DynamicPrintConfig PresetBundle::full_fff_config() const { DynamicPrintConfig out; @@ -432,6 +467,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const size_t num_extruders = nozzle_diameter->values.size(); // Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector. std::vector compatible_printers_condition; + std::vector compatible_prints_condition; std::vector inherits; compatible_printers_condition.emplace_back(this->prints.get_edited_preset().compatible_printers_condition()); inherits .emplace_back(this->prints.get_edited_preset().inherits()); @@ -439,6 +475,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const if (num_extruders <= 1) { out.apply(this->filaments.get_edited_preset().config); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); + compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition()); inherits .emplace_back(this->filaments.get_edited_preset().inherits()); } else { // Retrieve filament presets and build a single config object for them. @@ -451,13 +488,14 @@ DynamicPrintConfig PresetBundle::full_fff_config() const filament_configs.emplace_back(&this->filaments.first_visible().config); for (const DynamicPrintConfig *cfg : filament_configs) { compatible_printers_condition.emplace_back(Preset::compatible_printers_condition(*const_cast(cfg))); + compatible_prints_condition .emplace_back(Preset::compatible_prints_condition(*const_cast(cfg))); inherits .emplace_back(Preset::inherits(*const_cast(cfg))); } // Option values to set a ConfigOptionVector from. std::vector filament_opts(num_extruders, nullptr); // loop through options and apply them to the resulting config. for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { - if (key == "compatible_printers") + if (key == "compatible_prints" || key == "compatible_printers") continue; // Get a destination option. ConfigOption *opt_dst = out.option(key, false); @@ -478,7 +516,9 @@ DynamicPrintConfig PresetBundle::full_fff_config() const // Don't store the "compatible_printers_condition" for the printer profile, there is none. inherits.emplace_back(this->printers.get_edited_preset().inherits()); - // These two value types clash between the print and filament profiles. They should be renamed. + // These value types clash between the print and filament profiles. They should be renamed. + out.erase("compatible_prints"); + out.erase("compatible_prints_condition"); out.erase("compatible_printers"); out.erase("compatible_printers_condition"); out.erase("inherits"); @@ -509,7 +549,10 @@ DynamicPrintConfig PresetBundle::full_fff_config() const out.set_key_value(key, new ConfigOptionStrings(std::move(values))); }; add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_printers_condition_cummulative"); + add_if_some_non_empty(std::move(compatible_prints_condition), "compatible_prints_condition_cummulative"); add_if_some_non_empty(std::move(inherits), "inherits_cummulative"); + + out.option("printer_technology", true)->value = ptFFF; return out; } @@ -517,16 +560,21 @@ DynamicPrintConfig PresetBundle::full_sla_config() const { DynamicPrintConfig out; out.apply(SLAFullPrintConfig::defaults()); + out.apply(this->sla_prints.get_edited_preset().config); out.apply(this->sla_materials.get_edited_preset().config); out.apply(this->printers.get_edited_preset().config); // There are no project configuration values as of now, the project_config is reserved for FFF printers. // out.apply(this->project_config); - // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_materials, printers) into a single vector. + // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_prints, sla_materials, printers) into a single vector. std::vector compatible_printers_condition; + std::vector compatible_prints_condition; std::vector inherits; - compatible_printers_condition.emplace_back(this->/*prints*/sla_materials.get_edited_preset().compatible_printers_condition()); - inherits .emplace_back(this->/*prints*/sla_materials.get_edited_preset().inherits()); + compatible_printers_condition.emplace_back(this->sla_prints.get_edited_preset().compatible_printers_condition()); + inherits .emplace_back(this->sla_prints.get_edited_preset().inherits()); + compatible_printers_condition.emplace_back(this->sla_materials.get_edited_preset().compatible_printers_condition()); + compatible_prints_condition .emplace_back(this->sla_materials.get_edited_preset().compatible_prints_condition()); + inherits .emplace_back(this->sla_materials.get_edited_preset().inherits()); inherits .emplace_back(this->printers.get_edited_preset().inherits()); // These two value types clash between the print and filament profiles. They should be renamed. @@ -534,6 +582,7 @@ DynamicPrintConfig PresetBundle::full_sla_config() const out.erase("compatible_printers_condition"); out.erase("inherits"); + out.option("sla_print_settings_id", true)->value = this->sla_prints.get_selected_preset().name; out.option("sla_material_settings_id", true)->value = this->sla_materials.get_selected_preset().name; out.option("printer_settings_id", true)->value = this->printers.get_selected_preset().name; @@ -551,8 +600,11 @@ DynamicPrintConfig PresetBundle::full_sla_config() const out.set_key_value(key, new ConfigOptionStrings(std::move(values))); }; add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_printers_condition_cummulative"); + add_if_some_non_empty(std::move(compatible_prints_condition), "compatible_prints_condition_cummulative"); add_if_some_non_empty(std::move(inherits), "inherits_cummulative"); - return out; + + out.option("printer_technology", true)->value = ptSLA; + return out; } // Load an external config file containing the print, filament and printer presets. @@ -604,18 +656,6 @@ void PresetBundle::load_config_file(const std::string &path) } } -void PresetBundle::load_config_string(const char* str, const char* source_filename) -{ - if (str != nullptr) - { - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - config.load_from_gcode_string(str); - Preset::normalize(config); - load_config_file_config((source_filename == nullptr) ? "" : source_filename, true, std::move(config)); - } -} - // Load a config file from a boost property_tree. This is a private method called from load_config_file. void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config) { @@ -635,19 +675,31 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool size_t num_extruders = (printer_technology == ptFFF) ? std::min(config.option("nozzle_diameter" )->values.size(), config.option("filament_diameter")->values.size()) : - 0; + // 1 SLA material + 1; // Make a copy of the "compatible_printers_condition_cummulative" and "inherits_cummulative" vectors, which // accumulate values over all presets (print, filaments, printers). // These values will be distributed into their particular presets when loading. std::vector compatible_printers_condition_values = std::move(config.option("compatible_printers_condition_cummulative", true)->values); + std::vector compatible_prints_condition_values = std::move(config.option("compatible_prints_condition_cummulative", true)->values); std::vector inherits_values = std::move(config.option("inherits_cummulative", true)->values); std::string &compatible_printers_condition = Preset::compatible_printers_condition(config); + std::string &compatible_prints_condition = Preset::compatible_prints_condition(config); std::string &inherits = Preset::inherits(config); compatible_printers_condition_values.resize(num_extruders + 2, std::string()); + compatible_prints_condition_values.resize(num_extruders, std::string()); inherits_values.resize(num_extruders + 2, std::string()); // The "default_filament_profile" will be later extracted into the printer profile. - if (printer_technology == ptFFF) - config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); + switch (printer_technology) { + case ptFFF: + config.option("default_print_profile", true); + config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); + break; + case ptSLA: + config.option("default_sla_print_profile", true); + config.option("default_sla_material_profile", true); + break; + } // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. @@ -655,21 +707,30 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 2) If the loading succeeded, split and load the config into print / filament / printer settings. // First load the print and printer presets. - for (size_t i_group = 0; i_group < 2; ++ i_group) { - PresetCollection &presets = (i_group == 0) ? ((printer_technology == ptFFF) ? this->prints : this->sla_materials) : this->printers; - // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles. - size_t idx = (i_group == 0) ? 0 : num_extruders + 1; - inherits = inherits_values[idx]; - compatible_printers_condition = compatible_printers_condition_values[idx]; - if (is_external) - presets.load_external_preset(name_or_path, name, - config.opt_string((i_group == 0) ? ((printer_technology == ptFFF) ? "print_settings_id" : "sla_material_id") : "printer_settings_id", true), - config); - else - presets.load_preset(presets.path_from_name(name), name, config).save(); - } - if (Preset::printer_technology(config) == ptFFF) { + auto load_preset = + [&config, &inherits, &inherits_values, + &compatible_printers_condition, &compatible_printers_condition_values, + &compatible_prints_condition, &compatible_prints_condition_values, + is_external, &name, &name_or_path] + (PresetCollection &presets, size_t idx, const std::string &key) { + // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles. + inherits = inherits_values[idx]; + compatible_printers_condition = compatible_printers_condition_values[idx]; + if (idx > 0 && idx - 1 < compatible_prints_condition_values.size()) + compatible_prints_condition = compatible_prints_condition_values[idx - 1]; + if (is_external) + presets.load_external_preset(name_or_path, name, config.opt_string(key, true), config); + else + presets.load_preset(presets.path_from_name(name), name, config).save(); + }; + + switch (Preset::printer_technology(config)) { + case ptFFF: + { + load_preset(this->prints, 0, "print_settings_id"); + load_preset(this->printers, num_extruders + 1, "printer_settings_id"); + // 3) Now load the filaments. If there are multiple filament presets, split them and load them. auto old_filament_profile_names = config.option("filament_settings_id", true); old_filament_profile_names->values.resize(num_extruders, std::string()); @@ -678,12 +739,16 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. inherits = inherits_values[1]; compatible_printers_condition = compatible_printers_condition_values[1]; - if (is_external) - this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); - else - this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save(); + compatible_prints_condition = compatible_prints_condition_values.front(); + Preset *loaded = nullptr; + if (is_external) { + loaded = &this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); + } else { + loaded = &this->filaments.load_preset(this->filaments.path_from_name(name), name, config); + loaded->save(); + } this->filament_presets.clear(); - this->filament_presets.emplace_back(name); + this->filament_presets.emplace_back(loaded->name); } else { // Split the filament presets, load each of them separately. std::vector configs(num_extruders, this->filaments.default_preset().config); @@ -695,7 +760,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (other_opt->is_scalar()) { for (size_t i = 0; i < configs.size(); ++ i) configs[i].option(key, false)->set(other_opt); - } else if (key != "compatible_printers") { + } else if (key != "compatible_printers" && key != "compatible_prints") { for (size_t i = 0; i < configs.size(); ++ i) static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); } @@ -706,6 +771,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool DynamicPrintConfig &cfg = configs[i]; // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; + cfg.opt_string("compatible_prints_condition", true) = compatible_prints_condition_values[i]; cfg.opt_string("inherits", true) = inherits_values[i + 1]; // Load all filament presets, but only select the first one in the preset dialog. Preset *loaded = nullptr; @@ -729,12 +795,18 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool this->filament_presets.emplace_back(loaded->name); } } - // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); + break; + } + case ptSLA: + load_preset(this->sla_prints, 0, "sla_print_settings_id"); + load_preset(this->sla_materials, 1, "sla_material_settings_id"); + load_preset(this->printers, 2, "printer_settings_id"); + break; } - this->update_compatible_with_printer(false); + this->update_compatible(false); } // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. @@ -788,6 +860,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const return preset_name_dst; }; load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); + load_one(this->sla_prints, tmp_bundle.sla_prints, tmp_bundle.sla_prints .get_selected_preset().name, true); load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset().name, true); load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset().name, true); load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); @@ -795,7 +868,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); - this->update_compatible_with_printer(false); + this->update_compatible(false); } // Process the Config Bundle loaded as a Boost property tree. @@ -914,6 +987,7 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) { flatten_configbundle_hierarchy(tree, "print"); flatten_configbundle_hierarchy(tree, "filament"); + flatten_configbundle_hierarchy(tree, "sla_print"); flatten_configbundle_hierarchy(tree, "sla_material"); flatten_configbundle_hierarchy(tree, "printer"); } @@ -951,10 +1025,12 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. std::vector loaded_prints; std::vector loaded_filaments; + std::vector loaded_sla_prints; std::vector loaded_sla_materials; std::vector loaded_printers; std::string active_print; std::vector active_filaments; + std::string active_sla_print; std::string active_sla_material; std::string active_printer; size_t presets_loaded = 0; @@ -970,10 +1046,14 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla presets = &this->filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); + } else if (boost::starts_with(section.first, "sla_print:")) { + presets = &this->sla_prints; + loaded = &loaded_sla_prints; + preset_name = section.first.substr(10); } else if (boost::starts_with(section.first, "sla_material:")) { presets = &this->sla_materials; loaded = &loaded_sla_materials; - preset_name = section.first.substr(9); + preset_name = section.first.substr(13); } else if (boost::starts_with(section.first, "printer:")) { presets = &this->printers; loaded = &loaded_printers; @@ -990,6 +1070,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla active_filaments.resize(idx + 1, std::string()); active_filaments[idx] = kvp.second.data(); } + } else if (kvp.first == "sla_print") { + active_sla_print = kvp.second.data(); } else if (kvp.first == "sla_material") { active_sla_material = kvp.second.data(); } else if (kvp.first == "printer") { @@ -1005,6 +1087,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla dst = &this->obsolete_presets.prints; else if (kvp.first == "filament") dst = &this->obsolete_presets.filaments; + else if (kvp.first == "sla_print") + dst = &this->obsolete_presets.sla_prints; else if (kvp.first == "sla_material") dst = &this->obsolete_presets.sla_materials; else if (kvp.first == "printer") @@ -1023,16 +1107,28 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla continue; if (presets != nullptr) { // Load the print, filament or printer preset. - const DynamicPrintConfig &default_config = presets->default_preset().config; - DynamicPrintConfig config(default_config); - for (auto &kvp : section.second) - config.set_deserialize(kvp.first, kvp.second.data()); - // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys; - if (config.remove_keys_not_in(default_config, incorrect_keys) > 0) - BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << - section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + const DynamicPrintConfig *default_config = nullptr; + DynamicPrintConfig config; + if (presets == &this->printers) { + // Select the default config based on the printer_technology field extracted from kvp. + DynamicPrintConfig config_src; + for (auto &kvp : section.second) + config_src.set_deserialize(kvp.first, kvp.second.data()); + default_config = &presets->default_preset_for(config_src).config; + config = *default_config; + config.apply(config_src); + } else { + default_config = &presets->default_preset().config; + config = *default_config; + for (auto &kvp : section.second) + config.set_deserialize(kvp.first, kvp.second.data()); + } Preset::normalize(config); + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(config, *default_config); + if (! incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { // Filter out printer presets, which are not mentioned in the vendor profile. // These presets are considered not installed. @@ -1095,6 +1191,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); + if (! active_sla_print.empty()) + sla_materials.select_preset_by_name(active_sla_print, true); if (! active_sla_material.empty()) sla_materials.select_preset_by_name(active_sla_material, true); if (! active_printer.empty()) @@ -1105,7 +1203,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla this->update_multi_material_filament_presets(); for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; - this->update_compatible_with_printer(false); + this->update_compatible(false); } return presets_loaded; @@ -1153,22 +1251,25 @@ void PresetBundle::update_multi_material_filament_presets() } } -void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) +void PresetBundle::update_compatible(bool select_other_if_incompatible) { - const Preset &printer_preset = this->printers.get_edited_preset(); + const Preset &printer_preset = this->printers.get_edited_preset(); switch (printers.get_edited_preset().printer_technology()) { case ptFFF: { - const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + assert(printer_preset.config.has("default_print_profile")); + assert(printer_preset.config.has("default_filament_profile")); + const Preset &print_preset = this->prints.get_edited_preset(); + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; prefered_print_profile.empty() ? - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + this->prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible) : + this->prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name) { return profile_name == prefered_print_profile; }); prefered_filament_profiles.empty() ? - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible) : + this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible, [&prefered_filament_profiles](const std::string& profile_name) { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); if (select_other_if_incompatible) { @@ -1185,25 +1286,34 @@ void PresetBundle::update_compatible_with_printer(bool select_other_if_incompati const std::string &preferred = (idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profiles.front(); filament_name = this->filaments.first_compatible( - [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + [&preferred](const std::string& profile_name) { return profile_name == preferred; }).name; } } } } + break; } case ptSLA: { - const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); - const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; - prefered_print_profile.empty() ? - this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); - } + assert(printer_preset.config.has("default_sla_print_profile")); + assert(printer_preset.config.has("default_sla_material_profile")); + const Preset &sla_print_preset = this->sla_prints.get_edited_preset(); + const std::string &prefered_sla_print_profile = printer_preset.config.opt_string("default_sla_print_profile"); + (prefered_sla_print_profile.empty()) ? + this->sla_prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible) : + this->sla_prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible, + [&prefered_sla_print_profile](const std::string& profile_name){ return profile_name == prefered_sla_print_profile; }); + const std::string &prefered_sla_material_profile = printer_preset.config.opt_string("default_sla_material_profile"); + prefered_sla_material_profile.empty() ? + this->sla_materials.update_compatible(printer_preset, &sla_print_preset, select_other_if_incompatible) : + this->sla_materials.update_compatible(printer_preset, &sla_print_preset, select_other_if_incompatible, + [&prefered_sla_material_profile](const std::string& profile_name){ return profile_name == prefered_sla_material_profile; }); + break; + } } } -void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings +void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings) { boost::nowide::ofstream c; c.open(path, std::ios::out | std::ios::trunc); @@ -1212,13 +1322,16 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami c << "# " << Slic3r::header_slic3r_generated() << std::endl; // Export the print, filament and printer profiles. - for (size_t i_group = 0; i_group < 3; ++ i_group) { - const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers; - for (const Preset &preset : presets()) { - if (preset.is_default || preset.is_external) + + for (const PresetCollection *presets : { + (const PresetCollection*)&this->prints, (const PresetCollection*)&this->filaments, + (const PresetCollection*)&this->sla_prints, (const PresetCollection*)&this->sla_materials, + (const PresetCollection*)&this->printers }) { + for (const Preset &preset : (*presets)()) { + if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings)) // Only export the common presets, not external files or the default preset. continue; - c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl; + c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; } @@ -1227,6 +1340,7 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // Export the names of the active presets. c << std::endl << "[presets]" << std::endl; c << "print = " << this->prints.get_selected_preset().name << std::endl; + c << "sla_print = " << this->sla_prints.get_selected_preset().name << std::endl; c << "sla_material = " << this->sla_materials.get_selected_preset().name << std::endl; c << "printer = " << this->printers.get_selected_preset().name << std::endl; for (size_t i = 0; i < this->filament_presets.size(); ++ i) { @@ -1285,7 +1399,7 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out return true; } -void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) +void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) { if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA) return; @@ -1308,7 +1422,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma std::map nonsys_presets; wxString selected_str = ""; if (!this->filaments().front().is_visible) - ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) { const Preset &preset = this->filaments.preset(i); bool selected = this->filament_presets[idx_extruder] == preset.name; @@ -1347,7 +1461,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma bitmap = m_bitmapCache->insert(bitmap_key, bmps); } - if (preset.is_default || preset.is_system){ + if (preset.is_default || preset.is_system) { ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); if (selected) @@ -1361,12 +1475,12 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (preset.is_default) - ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); } if (!nonsys_presets.empty()) { - ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); + ui->set_label_marker(ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected_str) @@ -1382,6 +1496,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); filaments.set_default_suppressed(default_suppressed); + sla_prints.set_default_suppressed(default_suppressed); sla_materials.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed); } diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index 68ec534da..9f289f1e9 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -13,8 +13,6 @@ namespace GUI { class BitmapCache; }; -class PlaceholderParser; - // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -35,13 +33,12 @@ public: // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); - // Export selections (current print, current filaments, current printer) into a placeholder parser. - void export_selections(PlaceholderParser &pp); PresetCollection prints; + PresetCollection sla_prints; PresetCollection filaments; PresetCollection sla_materials; - PresetCollection printers; + PrinterPresetCollection printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; @@ -57,6 +54,7 @@ public: struct ObsoletePresets { std::vector prints; + std::vector sla_prints; std::vector filaments; std::vector sla_materials; std::vector printers; @@ -67,23 +65,25 @@ public: { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } DynamicPrintConfig full_config() const; + // full_config() with the "printhost_apikey" and "printhost_cafile" removed. + DynamicPrintConfig full_config_secure() const; // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. void load_config(const std::string &name, DynamicPrintConfig config) { this->load_config_file_config(name, false, std::move(config)); } + // Load configuration that comes from a model file containing configuration, such as 3MF et al. + // This method is called by the Plater. + void load_config_model(const std::string &name, DynamicPrintConfig config) + { this->load_config_file_config(name, true, std::move(config)); } + // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. void load_config_file(const std::string &path); - // Load an external config source containing the print, filament and printer presets. - // The given string must contain the full set of parameters (same as those exported to gcode). - // If the string is parsed successfully, its print / filament / printer profiles will be activated. - void load_config_string(const char* str, const char* source_filename = nullptr); - // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. // Load settings into the provided settings instance. @@ -102,10 +102,10 @@ public: size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); // Export a config bundle file containing all the presets and the names of the active presets. - void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); + void export_configbundle(const std::string &path, bool export_system_settings = false); // Update a filament selection combo box on the platter for an idx_extruder. - void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui); + void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -119,11 +119,11 @@ public: void update_multi_material_filament_presets(); // Update the is_compatible flag of all print and filament presets depending on whether they are marked - // as compatible with the currently selected printer. + // as compatible with the currently selected printer (and print in case of filament presets). // Also updates the is_visible flag of each preset. // If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible // preset if the current print or filament preset is not compatible. - void update_compatible_with_printer(bool select_other_if_incompatible); + void update_compatible(bool select_other_if_incompatible); static bool parse_color(const std::string &scolor, unsigned char *rgb_out); diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 69fbc6fb9..a7becfa16 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -1,15 +1,16 @@ -//#undef NDEBUG #include +#include "libslic3r/Flow.hpp" +#include "libslic3r/libslic3r.h" + #include "PresetBundle.hpp" #include "PresetHints.hpp" -#include "Flow.hpp" #include #include -#include "../../libslic3r/libslic3r.h" #include "GUI.hpp" +#include "I18N.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp new file mode 100644 index 000000000..a5de7c3c6 --- /dev/null +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -0,0 +1,49 @@ +#include "PrintHostDialogs.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/I18N.hpp" + +namespace fs = boost::filesystem; + +namespace Slic3r { + +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) + : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE) + , txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())) + , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) +{ + auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); + label_dir_hint->Wrap(CONTENT_WIDTH); + + content_sizer->Add(txt_filename, 0, wxEXPAND); + content_sizer->Add(label_dir_hint); + content_sizer->AddSpacer(VERT_SPACING); + content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + + btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); + + txt_filename->SetFocus(); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); + + Fit(); +} + +fs::path PrintHostSendDialog::filename() const +{ + return fs::path(txt_filename->GetValue().wx_str()); +} + +bool PrintHostSendDialog::start_print() const +{ + return box_print->GetValue(); } +} diff --git a/src/slic3r/Utils/PrintHostSendDialog.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp similarity index 60% rename from src/slic3r/Utils/PrintHostSendDialog.hpp rename to src/slic3r/GUI/PrintHostDialogs.hpp index dc4a8d6f7..d27fbe576 100644 --- a/src/slic3r/Utils/PrintHostSendDialog.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -20,19 +20,30 @@ namespace Slic3r { + class PrintHostSendDialog : public GUI::MsgDialog { -private: - wxTextCtrl *txt_filename; - wxCheckBox *box_print; - bool can_start_print; - public: - PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print); - boost::filesystem::path filename() const; - bool print() const; + PrintHostSendDialog(const boost::filesystem::path &path); + boost::filesystem::path filename() const; + bool start_print() const; + +private: + wxTextCtrl *txt_filename; + wxCheckBox *box_print; + bool can_start_print; }; + +class PrintHostQueueDialog : public wxDialog +{ +public: + PrintHostQueueDialog(); + +private: +}; + + } #endif diff --git a/src/slic3r/GUI/ProgressIndicator.hpp b/src/slic3r/GUI/ProgressIndicator.hpp index 0cf8b4a17..280ba63ac 100644 --- a/src/slic3r/GUI/ProgressIndicator.hpp +++ b/src/slic3r/GUI/ProgressIndicator.hpp @@ -14,31 +14,31 @@ public: using CancelFn = std::function; // Cancel function signature. private: - float state_ = .0f, max_ = 1.f, step_; - CancelFn cancelfunc_ = [](){}; + float m_state = .0f, m_max = 1.f, m_step; + CancelFn m_cancelfunc = [](){}; public: inline virtual ~ProgressIndicator() {} /// Get the maximum of the progress range. - float max() const { return max_; } + float max() const { return m_max; } /// Get the current progress state - float state() const { return state_; } + float state() const { return m_state; } /// Set the maximum of the progress range - virtual void max(float maxval) { max_ = maxval; } + virtual void max(float maxval) { m_max = maxval; } /// Set the current state of the progress. - virtual void state(float val) { state_ = val; } + virtual void state(float val) { m_state = val; } /** * @brief Number of states int the progress. Can be used instead of giving a * maximum value. */ virtual void states(unsigned statenum) { - step_ = max_ / statenum; + m_step = m_max / statenum; } /// Message shown on the next status update. @@ -51,13 +51,13 @@ public: virtual void message_fmt(const std::string& fmt, ...); /// Set up a cancel callback for the operation if feasible. - virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; } + virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; } /** * Explicitly shut down the progress indicator and call the associated * callback. */ - virtual void cancel() { cancelfunc_(); } + virtual void cancel() { m_cancelfunc(); } /// Convenience function to call message and status update in one function. void update(float st, const std::string& msg) { diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 363e34cb2..8cdb53c9d 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -5,148 +5,160 @@ #include #include #include -#include "GUI.hpp" +#include "GUI_App.hpp" #include namespace Slic3r { ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): - self(new wxStatusBar(parent ? parent : GUI::get_main_frame(), + self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, id == -1? wxID_ANY : id)), - timer_(new wxTimer(self)), - prog_ (new wxGauge(self, + m_timer(new wxTimer(self)), + m_prog (new wxGauge(self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize)), - cancelbutton_(new wxButton(self, + m_cancelbutton(new wxButton(self, -1, "Cancel", wxDefaultPosition, wxDefaultSize)) { - prog_->Hide(); - cancelbutton_->Hide(); + m_prog->Hide(); + m_cancelbutton->Hide(); self->SetFieldsCount(3); int w[] = {-1, 150, 155}; self->SetStatusWidths(3, w); self->Bind(wxEVT_TIMER, [this](const wxTimerEvent&) { - if (prog_->IsShown()) timer_->Stop(); - if(is_busy()) prog_->Pulse(); + if (m_prog->IsShown()) m_timer->Stop(); + if(is_busy()) m_prog->Pulse(); }); self->Bind(wxEVT_SIZE, [this](wxSizeEvent& event){ wxRect rect; self->GetFieldRect(1, rect); auto offset = 0; - cancelbutton_->Move(rect.GetX() + offset, rect.GetY() + offset); - cancelbutton_->SetSize(rect.GetWidth() - offset, rect.GetHeight()); + m_cancelbutton->Move(rect.GetX() + offset, rect.GetY() + offset); + m_cancelbutton->SetSize(rect.GetWidth() - offset, rect.GetHeight()); self->GetFieldRect(2, rect); - prog_->Move(rect.GetX() + offset, rect.GetY() + offset); - prog_->SetSize(rect.GetWidth() - offset, rect.GetHeight()); + m_prog->Move(rect.GetX() + offset, rect.GetY() + offset); + m_prog->SetSize(rect.GetWidth() - offset, rect.GetHeight()); event.Skip(); }); - cancelbutton_->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { - if(cancel_cb_) cancel_cb_(); - m_perl_cancel_callback.call(); - cancelbutton_->Hide(); + m_cancelbutton->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { + if (m_cancel_cb) + m_cancel_cb(); + m_cancelbutton->Hide(); }); } ProgressStatusBar::~ProgressStatusBar() { - if(timer_->IsRunning()) timer_->Stop(); + if(m_timer->IsRunning()) m_timer->Stop(); } int ProgressStatusBar::get_progress() const { - return prog_->GetValue(); + return m_prog->GetValue(); } void ProgressStatusBar::set_progress(int val) { - if(!prog_->IsShown()) show_progress(true); + if(!m_prog->IsShown()) show_progress(true); + if(val < 0) return; - if(val == prog_->GetRange()) { - prog_->SetValue(0); + if(val == m_prog->GetRange()) { + m_prog->SetValue(0); show_progress(false); - } else { - prog_->SetValue(val); + } + else { + m_prog->SetValue(val); } } int ProgressStatusBar::get_range() const { - return prog_->GetRange(); + return m_prog->GetRange(); } void ProgressStatusBar::set_range(int val) { - if(val != prog_->GetRange()) { - prog_->SetRange(val); + if(val != m_prog->GetRange()) { + m_prog->SetRange(val); } } void ProgressStatusBar::show_progress(bool show) { - prog_->Show(show); - prog_->Pulse(); + m_prog->Show(show); + m_prog->Pulse(); } void ProgressStatusBar::start_busy(int rate) { - busy_ = true; + m_busy = true; show_progress(true); - if (!timer_->IsRunning()) { - timer_->Start(rate); + if (!m_timer->IsRunning()) { + m_timer->Start(rate); } } void ProgressStatusBar::stop_busy() { - timer_->Stop(); + m_timer->Stop(); show_progress(false); - prog_->SetValue(0); - busy_ = false; + m_prog->SetValue(0); + m_busy = false; } void ProgressStatusBar::set_cancel_callback(ProgressStatusBar::CancelFn ccb) { - cancel_cb_ = ccb; - if(ccb) cancelbutton_->Show(); - else cancelbutton_->Hide(); + m_cancel_cb = ccb; + if(ccb) m_cancelbutton->Show(); + else m_cancelbutton->Hide(); } void ProgressStatusBar::run(int rate) { - if(!timer_->IsRunning()) { - timer_->Start(rate); + if(!m_timer->IsRunning()) { + m_timer->Start(rate); } } void ProgressStatusBar::embed(wxFrame *frame) { - wxFrame* mf = frame? frame : GUI::get_main_frame(); + wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe; mf->SetStatusBar(self); } void ProgressStatusBar::set_status_text(const wxString& txt) { - self->SetStatusText(wxString::FromUTF8(txt.c_str())); + self->SetStatusText(txt); +} + +void ProgressStatusBar::set_status_text(const std::string& txt) +{ + this->set_status_text(txt.c_str()); +} + +void ProgressStatusBar::set_status_text(const char *txt) +{ + this->set_status_text(wxString::FromUTF8(txt)); } void ProgressStatusBar::show_cancel_button() { - cancelbutton_->Show(); + m_cancelbutton->Show(); } void ProgressStatusBar::hide_cancel_button() { - cancelbutton_->Hide(); + m_cancelbutton->Hide(); } } diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 7c2171a5e..225b0331e 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -4,8 +4,6 @@ #include #include -#include "callback.hpp" - class wxTimer; class wxGauge; class wxButton; @@ -22,11 +20,12 @@ namespace Slic3r { * of the Slicer main window. It consists of a message area to the left and a * progress indication area to the right with an optional cancel button. */ -class ProgressStatusBar { +class ProgressStatusBar +{ wxStatusBar *self; // we cheat! It should be the base class but: perl! - wxTimer *timer_; - wxGauge *prog_; - wxButton *cancelbutton_; + wxTimer *m_timer; + wxGauge *m_prog; + wxButton *m_cancelbutton; public: /// Cancel callback function type @@ -35,28 +34,31 @@ public: ProgressStatusBar(wxWindow *parent = nullptr, int id = -1); ~ProgressStatusBar(); - int get_progress() const; - void set_progress(int); - int get_range() const; - void set_range(int = 100); - void show_progress(bool); - void start_busy(int = 100); - void stop_busy(); - inline bool is_busy() const { return busy_; } - void set_cancel_callback(CancelFn = CancelFn()); - inline void remove_cancel_callback() { set_cancel_callback(); } - void run(int rate); - void embed(wxFrame *frame = nullptr); - void set_status_text(const wxString& txt); + int get_progress() const; + // if the argument is less than 0 it shows the last state or + // pulses if no state was set before. + void set_progress(int); + int get_range() const; + void set_range(int = 100); + void show_progress(bool); + void start_busy(int = 100); + void stop_busy(); + inline bool is_busy() const { return m_busy; } + void set_cancel_callback(CancelFn = CancelFn()); + inline void reset_cancel_callback() { set_cancel_callback(); } + void run(int rate); + void embed(wxFrame *frame = nullptr); + void set_status_text(const wxString& txt); + void set_status_text(const std::string& txt); + void set_status_text(const char *txt); // Temporary methods to satisfy Perl side - void show_cancel_button(); - void hide_cancel_button(); + void show_cancel_button(); + void hide_cancel_button(); - PerlCallback m_perl_cancel_callback; private: - bool busy_ = false; - CancelFn cancel_cb_; + bool m_busy = false; + CancelFn m_cancel_cb; }; namespace GUI { diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 8954ff93b..cbf660f2c 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -3,7 +3,7 @@ #include "RammingChart.hpp" #include "GUI.hpp" - +#include "I18N.hpp" wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); @@ -52,7 +52,7 @@ void Chart::draw() { // draw x-axis: float last_mark = -10000; - for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1) { + for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1f) { int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x; int y = m_rect.GetBottom(); if (x-last_mark < 50) continue; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp new file mode 100644 index 000000000..f7f1cfedb --- /dev/null +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -0,0 +1,145 @@ +#include "SysInfoDialog.hpp" +#include "I18N.hpp" +#include "3DScene.hpp" +#include "GUI.hpp" + +#include +#include + +namespace Slic3r { +namespace GUI { + +std::string get_main_info(bool format_as_html) +{ + std::stringstream out; + + std::string b_start = format_as_html ? "" : ""; + std::string b_end = format_as_html ? "" : ""; + std::string line_end = format_as_html ? "
" : "\n"; + + if (!format_as_html) + out << b_start << SLIC3R_FORK_NAME << b_end << line_end; + out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end; + out << b_start << "Build: " << b_end << SLIC3R_BUILD << line_end; + out << line_end; + out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end; + out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end; + out << b_start << +#if defined _WIN32 + "Windows Version: " +#else + // Hopefully some kind of unix / linux. + "System Version: " +#endif + << b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end; + + return out.str(); +} + +SysInfoDialog::SysInfoDialog() + : wxDialog(NULL, wxID_ANY, _(L("Slic3r Prusa Edition - System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); + hsizer->SetMinSize(wxSize(600, -1)); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 10); + + // logo + wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG); + auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp)); + hsizer->Add(logo, 0, wxEXPAND | wxTOP | wxBOTTOM, 15); + + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + hsizer->Add(vsizer, 1, wxEXPAND|wxLEFT, 20); + + // title + { + wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_FORK_NAME, wxDefaultPosition, wxDefaultSize); + wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + title_font.SetWeight(wxFONTWEIGHT_BOLD); + title_font.SetFamily(wxFONTFAMILY_ROMAN); + title_font.SetPointSize(22); + title->SetFont(title_font); + vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 50); + } + + // main_info_text + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + + const int fs = font.GetPointSize() - 1; + int size[] = { static_cast(fs*1.5), static_cast(fs*1.4), static_cast(fs*1.3), fs, fs, fs, fs }; + + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + { + html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html->SetBorders(2); + const auto text = wxString::Format( + "" + "" + "" + "%s" + "" + "" + "", bgr_clr_str, text_clr_str, text_clr_str, + get_main_info(true)); + html->SetPage(text); + vsizer->Add(html, 1, wxEXPAND); + } + + // opengl_info + wxHtmlWindow* opengl_info_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); + { + opengl_info_html->SetMinSize(wxSize(-1, 200)); + opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + opengl_info_html->SetBorders(10); + const auto text = wxString::Format( + "" + "" + "" + "%s" + "" + "" + "", bgr_clr_str, text_clr_str, text_clr_str, + _3DScene::get_gl_info(true, true)); + opengl_info_html->SetPage(text); + main_sizer->Add(opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); + } + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); + auto btn_copy_to_clipboard = new wxButton(this, wxID_ANY, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize); + buttons->Insert(0, btn_copy_to_clipboard, 0, wxLEFT, 5); + btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this); + + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK); + main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); + + this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this); + logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this); + + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); +} + +void SysInfoDialog::onCopyToClipboard(wxEvent &) +{ + wxTheClipboard->Open(); + const auto text = get_main_info(false)+"\n"+_3DScene::get_gl_info(false, true); + wxTheClipboard->SetData(new wxTextDataObject(text)); + wxTheClipboard->Close(); +} + +void SysInfoDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); + this->Close(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/SysInfoDialog.hpp b/src/slic3r/GUI/SysInfoDialog.hpp new file mode 100644 index 000000000..ee1b85ce6 --- /dev/null +++ b/src/slic3r/GUI/SysInfoDialog.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_GUI_SysInfoDialog_hpp_ +#define slic3r_GUI_SysInfoDialog_hpp_ + +#include +#include + +namespace Slic3r { +namespace GUI { + +class SysInfoDialog : public wxDialog +{ + wxString text_info {wxEmptyString}; +public: + SysInfoDialog(); + +private: + void onCopyToClipboard(wxEvent &); + void onCloseDialog(wxEvent &); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d28d74d44..f5792ca4b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1,8 +1,8 @@ -#include "../../libslic3r/GCodeSender.hpp" +#include "libslic3r/GCodeSender.hpp" #include "Tab.hpp" #include "PresetBundle.hpp" #include "PresetHints.hpp" -#include "../../libslic3r/Utils.hpp" +#include "libslic3r/Utils.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -27,17 +27,51 @@ #include "wxExtensions.hpp" #include -#include +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" namespace Slic3r { namespace GUI { -static wxString dots("…", wxConvUTF8); + +wxDEFINE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); +wxDEFINE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); + +Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : + m_parent(parent), m_title(title), m_name(name) +{ + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name); + set_type(); + + m_compatible_printers.type = Preset::TYPE_PRINTER; + m_compatible_printers.key_list = "compatible_printers"; + m_compatible_printers.key_condition = "compatible_printers_condition"; + m_compatible_printers.dialog_title = _(L("Compatible printers")); + m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")); + + m_compatible_prints.type = Preset::TYPE_PRINT; + m_compatible_prints.key_list = "compatible_prints"; + m_compatible_prints.key_condition = "compatible_prints_condition"; + m_compatible_prints.dialog_title = _(L("Compatible print profiles")); + m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); + + wxGetApp().tabs_list.push_back(this); +} + +void Tab::set_type() +{ + if (m_name == "print") { m_type = Slic3r::Preset::TYPE_PRINT; } + else if (m_name == "sla_print") { m_type = Slic3r::Preset::TYPE_SLA_PRINT; } + else if (m_name == "filament") { m_type = Slic3r::Preset::TYPE_FILAMENT; } + else if (m_name == "sla_material") { m_type = Slic3r::Preset::TYPE_SLA_MATERIAL; } + else if (m_name == "printer") { m_type = Slic3r::Preset::TYPE_PRINTER; } + else { m_type = Slic3r::Preset::TYPE_INVALID; assert(false); } +} // sub new -void Tab::create_preset_tab(PresetBundle *preset_bundle) +void Tab::create_preset_tab() { - m_preset_bundle = preset_bundle; + m_preset_bundle = wxGetApp().preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. #ifdef __WXOSX__ @@ -98,7 +132,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) "or click this button."))); // Determine the theme color of OS (dark or light) - auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + auto luma = wxGetApp().get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. m_bmp_value_lock .LoadFile(from_u8(var("sys_lock.png")), wxBITMAP_TYPE_PNG); m_bmp_value_unlock .LoadFile(from_u8(var(luma >= 128 ? "sys_unlock.png" : "sys_unlock_grey.png")), wxBITMAP_TYPE_PNG); @@ -112,27 +146,27 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) set_tooltips_text(); m_undo_btn->SetBitmap(m_bmp_white_bullet); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(); })); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); m_undo_to_sys_btn->SetBitmap(m_bmp_white_bullet); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_roll_back_value(true); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); m_question_btn->SetBitmap(m_bmp_question); m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { auto dlg = new ButtonsDescription(this, &m_icon_descriptions); - if (dlg->ShowModal() == wxID_OK){ + if (dlg->ShowModal() == wxID_OK) { // Colors for ui "decoration" - for (Tab *tab : get_tabs_list()){ - tab->m_sys_label_clr = get_label_clr_sys(); - tab->m_modified_label_clr = get_label_clr_modified(); + for (Tab *tab : wxGetApp().tabs_list) { + tab->m_sys_label_clr = wxGetApp().get_label_clr_sys(); + tab->m_modified_label_clr = wxGetApp().get_label_clr_modified(); tab->update_labels_colour(); } } })); // Colors for ui "decoration" - m_sys_label_clr = get_label_clr_sys(); - m_modified_label_clr = get_label_clr_modified(); - m_default_text_clr = get_label_clr_default(); + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + m_default_text_clr = wxGetApp().get_label_clr_default(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); @@ -148,7 +182,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(32); m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); -// m_hsizer->Add(m_cc_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -173,7 +206,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e){ + m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, //! but the OSX version derived from wxOwnerDrawnCombo, instead of: //! select_preset(m_presets_choice->GetStringSelection().ToStdString()); @@ -181,11 +214,11 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) int selected_item = m_presets_choice->GetSelection(); if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) return; - if (selected_item >= 0){ + if (selected_item >= 0) { std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); if (selected_string.find("-------") == 0 /*selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"*/){ + selected_string == "------- User presets -------"*/) { m_presets_choice->SetSelection(m_selected_preset_item); return; } @@ -194,16 +227,18 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) } })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e){ save_preset(); })); - m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e){ delete_preset(); })); - m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e){ + m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); + m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); + m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { toggle_show_hide_incompatible(); })); // Initialize the DynamicPrintConfig by default keys/values. build(); rebuild_page_tree(); - update(); +// update(); + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_preset(); } void Tab::load_initial_data() @@ -222,9 +257,9 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); if (icon_idx == -1) { // Add a new icon to the icon list. - const auto img_icon = new wxIcon(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG); - m_icons->Add(*img_icon); - icon_idx = ++m_icon_count; + wxIcon img_icon(from_u8(Slic3r::var(icon)), wxBITMAP_TYPE_PNG); + m_icons->Add(img_icon); + icon_idx = ++m_icon_count; m_icon_index[icon] = icon_idx; } } @@ -235,7 +270,7 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str auto panel = this; #endif PageShp page(new Page(panel, title, icon_idx)); - page->SetScrollbars(1, 1, 1, 1); + page->SetScrollbars(1, 1, 1, 2); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -267,7 +302,7 @@ void Tab::update_labels_colour() const wxColour *color = &m_sys_label_clr; // value isn't equal to system value - if ((opt.second & osSystemValue) == 0){ + if ((opt.second & osSystemValue) == 0) { // value is equal to last saved if ((opt.second & osInitValue) != 0) color = &m_default_text_clr; @@ -275,7 +310,7 @@ void Tab::update_labels_colour() else color = &m_modified_label_clr; } - if (opt.first == "bed_shape" || opt.first == "compatible_printers") { + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { if (m_colored_Label != nullptr) { m_colored_Label->SetForegroundColour(*color); m_colored_Label->Refresh(true); @@ -290,7 +325,7 @@ void Tab::update_labels_colour() Thaw(); auto cur_item = m_treectrl->GetFirstVisibleItem(); - while (cur_item){ + while (cur_item) { auto title = m_treectrl->GetItemText(cur_item); for (auto page : m_pages) { @@ -317,7 +352,7 @@ void Tab::update_changed_ui() const bool deep_compare = (m_name == "printer" || m_name == "sla_material"); auto dirty_options = m_presets->current_dirty_options(deep_compare); auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); - if (name() == "printer"){ + if (m_type == Slic3r::Preset::TYPE_PRINTER) { TabPrinter* tab = static_cast(this); if (tab->m_initial_extruders_count != tab->m_extruders_count) dirty_options.emplace_back("extruders_count"); @@ -346,7 +381,7 @@ void Tab::update_changed_ui() const wxString *tt = &m_tt_value_revert; // value isn't equal to system value - if ((opt.second & osSystemValue) == 0){ + if ((opt.second & osSystemValue) == 0) { is_nonsys_value = true; sys_icon = m_bmp_non_system; sys_tt = m_tt_non_system; @@ -363,7 +398,7 @@ void Tab::update_changed_ui() icon = &m_bmp_white_bullet; tt = &m_tt_white_bullet; } - if (opt.first == "bed_shape" || opt.first == "compatible_printers") { + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { if (m_colored_Label != nullptr) { m_colored_Label->SetForegroundColour(*color); m_colored_Label->Refresh(true); @@ -412,7 +447,7 @@ void TabPrinter::init_options_list() for (const auto opt_key : m_config->keys()) { - if (opt_key == "bed_shape"){ + if (opt_key == "bed_shape") { m_options_list.emplace(opt_key, m_opt_status_value); continue; } @@ -437,7 +472,7 @@ void TabSLAMaterial::init_options_list() for (const auto opt_key : m_config->keys()) { - if (opt_key == "compatible_printers"){ + if (opt_key == "compatible_prints" || opt_key == "compatible_printers") { m_options_list.emplace(opt_key, m_opt_status_value); continue; } @@ -458,33 +493,37 @@ void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool { auto opt = m_options_list.find(opt_key); if (sys_page) sys_page = (opt->second & osSystemValue) != 0; - if (!modified_page) modified_page = (opt->second & osInitValue) == 0; + modified_page |= (opt->second & osInitValue) == 0; } void Tab::update_changed_tree_ui() { auto cur_item = m_treectrl->GetFirstVisibleItem(); + if (!cur_item || !m_treectrl->IsVisible(cur_item)) + return; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - while (cur_item){ + while (cur_item) { auto title = m_treectrl->GetItemText(cur_item); for (auto page : m_pages) { - if (page->title() != title) + if (page->title() != title) continue; bool sys_page = true; bool modified_page = false; - if (title == _("General")){ + if (title == _("General")) { std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; for (auto &opt_key : optional_keys) { get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (title == _("Dependencies")){ - if (name() != "printer") - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); - else { - sys_page = m_presets->get_selected_preset_parent() ? true:false; + if (title == _("Dependencies")) { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + sys_page = m_presets->get_selected_preset_parent() != nullptr; modified_page = false; + } else { + if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) + get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); } } for (auto group : page->m_optgroups) @@ -507,14 +546,14 @@ void Tab::update_changed_tree_ui() page->m_is_nonsys_values = !sys_page; page->m_is_modified_values = modified_page; - if (selection == title){ + if (selection == title) { m_is_nonsys_values = page->m_is_nonsys_values; m_is_modified_values = page->m_is_modified_values; } break; } - auto next_item = m_treectrl->GetNextVisible(cur_item); - cur_item = next_item; + auto next_item = m_treectrl->GetNextVisible(cur_item); + cur_item = next_item; } update_undo_buttons(); } @@ -545,30 +584,38 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); for (auto page : m_pages) if (page->title() == selection) { - for (auto group : page->m_optgroups){ - if (group->title == _("Capabilities")){ + for (auto group : page->m_optgroups) { + if (group->title == _("Capabilities")) { if ((m_options_list["extruders_count"] & os) == 0) to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); } - if (group->title == _("Size and coordinates")){ - if ((m_options_list["bed_shape"] & os) == 0){ + if (group->title == _("Size and coordinates")) { + if ((m_options_list["bed_shape"] & os) == 0) { to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); load_key_value("bed_shape", true/*some value*/, true); } } - if (group->title == _("Profile dependencies") && name() != "printer"){ - if ((m_options_list["compatible_printers"] & os) == 0){ + if (group->title == _("Profile dependencies")) { + if (m_type != Slic3r::Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); load_key_value("compatible_printers", true/*some value*/, true); bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers_checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers_btn->Disable() : m_compatible_printers_btn->Enable(); + m_compatible_printers.checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); + } + if ((m_type == Slic3r::Preset::TYPE_PRINT || m_type == Slic3r::Preset::TYPE_SLA_PRINT) && (m_options_list["compatible_prints"] & os) == 0) { + to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); + load_key_value("compatible_prints", true/*some value*/, true); + + bool is_empty = m_config->option("compatible_prints")->values.empty(); + m_compatible_prints.checkbox->SetValue(is_empty); + is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); } } - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; + for (auto kvp : group->m_opt_map) { + const std::string& opt_key = kvp.first; if ((m_options_list[opt_key] & os) == 0) to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); } @@ -582,18 +629,16 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) // Update the combo box label of the selected preset based on its "dirty" state, // comparing the selected preset config with $self->{config}. -void Tab::update_dirty(){ +void Tab::update_dirty() +{ m_presets->update_dirty_ui(m_presets_choice); on_presets_changed(); update_changed_ui(); -// update_dirty_presets(m_cc_presets_choice); } void Tab::update_tab_ui() { m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); -// update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); -// update_presetsctrl(m_presetctrl, m_show_incompatible_presets); } // Load a provied DynamicConfig into the tab, modifying the active preset. @@ -614,17 +659,37 @@ void Tab::load_config(const DynamicPrintConfig& config) } // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. -void Tab::reload_config(){ +void Tab::reload_config() +{ Freeze(); for (auto page : m_pages) page->reload_config(); Thaw(); } +void Tab::update_visibility(ConfigOptionMode mode) +{ + Freeze(); + + for (auto page : m_pages) + page->update_visibility(mode); + update_page_tree_visibility(); + + m_hsizer->Layout(); + Refresh(); + + Thaw(); + + // to update tree items color + wxTheApp->CallAfter([this]() { + update_changed_tree_ui(); + }); +} + Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { Field* field = nullptr; - for (auto page : m_pages){ + for (auto page : m_pages) { field = page->get_field(opt_key, opt_index); if (field != nullptr) return field; @@ -635,7 +700,7 @@ Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/ // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. -bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value){ +bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) { bool changed = false; for(auto page: m_pages) { if (page->set_value(opt_key, value)) @@ -652,43 +717,34 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo { if (!saved_value) change_opt_value(*m_config, opt_key, value); // Mark the print & filament enabled if they are compatible with the currently selected preset. - if (opt_key.compare("compatible_printers") == 0) { + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { // Don't select another profile if this profile happens to become incompatible. - m_preset_bundle->update_compatible_with_printer(false); - } + m_preset_bundle->update_compatible(false); + } m_presets->update_dirty_ui(m_presets_choice); on_presets_changed(); update(); } -extern wxFrame *g_wxMainFrame; - void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - if (m_event_value_change > 0) { - wxCommandEvent event(m_event_value_change); - std::string str_out = opt_key + " " + m_name; - event.SetString(str_out); - if (opt_key == "extruders_count") - { - int val = boost::any_cast(value); - event.SetInt(val); - } - - if (opt_key == "printer_technology") - { - int val = boost::any_cast(value); - event.SetInt(val); - g_wxMainFrame->ProcessWindowEvent(event); - return; - } - - g_wxMainFrame->ProcessWindowEvent(event); + wxCommandEvent event(EVT_TAB_VALUE_CHANGED); + event.SetEventObject(this); + event.SetString(opt_key); + if (opt_key == "extruders_count") + { + int val = boost::any_cast(value); + event.SetInt(val); } + + wxPostEvent(this, event); + + + auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(); if (opt_key == "fill_density") { - boost::any val = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, opt_key); - get_optgroup(ogFrequentlyChangingParameters)->set_value(opt_key, val); + boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); + og_freq_chng_params->set_value(opt_key, val); } if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") { @@ -697,12 +753,12 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); + og_freq_chng_params->set_value("support", new_selection); } if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); + og_freq_chng_params->set_value("brim", val); } if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) @@ -713,15 +769,17 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; - get_wiping_dialog_button()->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); - - (get_wiping_dialog_button()->GetParent())->Layout(); + auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); + if (wiping_dialog_button) { + wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + wiping_dialog_button->GetParent()->Layout(); + } } @@ -732,11 +790,17 @@ void Tab::update_wiping_button_visibility() { // to uddate number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (m_event_presets_changed > 0) { - wxCommandEvent event(m_event_presets_changed); - event.SetString(m_name); - g_wxMainFrame->ProcessWindowEvent(event); - } + // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. + for (auto t: m_dependent_tabs) + { + // If the printer tells us that the print or filament/sla_material preset has been switched or invalidated, + // refresh the print or filament/sla_material tab page. + wxGetApp().get_tab(t)->load_current_preset(); + } + + wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); + event.SetEventObject(this); + wxPostEvent(this, event); update_preset_description_line(); } @@ -760,24 +824,43 @@ void Tab::update_preset_description_line() if (parent && parent->vendor) { description_line += "\n\n" + _(L("Additional information:")) + "\n"; - description_line += "\t" + _(L("vendor")) + ": " + (name()=="printer" ? "\n\t\t" : "") + parent->vendor->name + + description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + ", ver: " + parent->vendor->config_version.to_string(); - if (name() == "printer"){ - const std::string &printer_model = preset.config.opt_string("printer_model"); - const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); - const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; - if (!printer_model.empty()) + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + const std::string &printer_model = preset.config.opt_string("printer_model"); + if (! printer_model.empty()) description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; - if (!default_print_profile.empty()) - description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; - if (!default_filament_profiles.empty()) + switch (preset.printer_technology()) { + case ptFFF: { - description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; - for (auto& profile : default_filament_profiles){ - if (&profile != &*default_filament_profiles.begin()) - description_line += ", "; - description_line += profile; + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + if (!default_print_profile.empty()) + description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + if (!default_filament_profiles.empty()) + { + description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + for (auto& profile : default_filament_profiles) { + if (&profile != &*default_filament_profiles.begin()) + description_line += ", "; + description_line += profile; + } } + break; + } + case ptSLA: + { + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); + if (!default_sla_material_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; + + const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); + if (!default_sla_print_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; + break; + } } } } @@ -787,30 +870,24 @@ void Tab::update_preset_description_line() void Tab::update_frequently_changed_parameters() { - boost::any value = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, "fill_density"); - get_optgroup(ogFrequentlyChangingParameters)->set_value("fill_density", value); + auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(); + if (!og_freq_chng_params) return; + boost::any value = og_freq_chng_params->get_config_value(*m_config, "fill_density"); + og_freq_chng_params->set_value("fill_density", value); wxString new_selection = !m_config->opt_bool("support_material") ? _("None") : m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); + og_freq_chng_params->set_value("support", new_selection); bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); + og_freq_chng_params->set_value("brim", val); update_wiping_button_visibility(); } -void Tab::reload_compatible_printers_widget() -{ - bool has_any = !m_config->option("compatible_printers")->values.empty(); - has_any ? m_compatible_printers_btn->Enable() : m_compatible_printers_btn->Disable(); - m_compatible_printers_checkbox->SetValue(!has_any); - get_field("compatible_printers_condition")->toggle(!has_any); -} - void TabPrint::build() { m_presets = &m_preset_bundle->prints; @@ -1066,12 +1143,11 @@ void TabPrint::build() page = add_options_page(_(L("Dependencies")), "wrench.png"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = { _(L("Compatible printers")), "" }; - line.widget = [this](wxWindow* parent){ - return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); + line = optgroup->create_single_option_line("compatible_printers"); + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_printers); }; optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); @@ -1085,14 +1161,15 @@ void TabPrint::build() } // Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabPrint::reload_config(){ - reload_compatible_printers_widget(); +void TabPrint::reload_config() +{ + this->compatible_widget_reload(m_compatible_printers); Tab::reload_config(); } void TabPrint::update() { - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME Freeze(); @@ -1209,22 +1286,22 @@ void TabPrint::update() } } if (!str_fill_pattern.empty()){ - auto top_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; + const std::vector top_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; bool correct_100p_fill = false; - for (auto fill : top_fill_pattern) + for (const std::string &fill : top_fill_pattern) { - if (str_fill_pattern.compare(fill) == 0) + if (str_fill_pattern == fill) correct_100p_fill = true; } - auto bottom_fill_pattern = m_config->def()->get("bottom_fill_pattern")->enum_values; - for (auto fill : bottom_fill_pattern) + const std::vector bottom_fill_pattern = m_config->def()->get("bottom_fill_pattern")->enum_values; + for (const std::string &fill : bottom_fill_pattern) { if (str_fill_pattern.compare(fill) == 0) correct_100p_fill = true; } // get fill_pattern name from enum_labels for using this one at dialog_msg str_fill_pattern = m_config->def()->get("fill_pattern")->enum_labels[fill_pattern]; - if (!correct_100p_fill){ + if (!correct_100p_fill) { wxString msg_text = _(L("The ")) + str_fill_pattern + _(L(" infill pattern is not supposed to work at 100% density.\n" "\nShall I switch to rectilinear fill pattern?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); @@ -1421,8 +1498,8 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_final_speed"); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); - line = { _(L("Ramming")), "" }; - line.widget = [this](wxWindow* parent){ + line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; + line.widget = [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(ramming_dialog_btn); @@ -1461,16 +1538,25 @@ void TabFilament::build() page = add_options_page(_(L("Dependencies")), "wrench.png"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = { _(L("Compatible printers")), "" }; - line.widget = [this](wxWindow* parent){ - return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); + + line = optgroup->create_single_option_line("compatible_printers"); + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_printers); }; optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); + line = optgroup->create_single_option_line("compatible_prints"); + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + line = Line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { @@ -1480,14 +1566,16 @@ void TabFilament::build() } // Reload current config (aka presets->edited_preset->config) into the UI fields. -void TabFilament::reload_config(){ - reload_compatible_printers_widget(); +void TabFilament::reload_config() +{ + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); Tab::reload_config(); } void TabFilament::update() { - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME Freeze(); @@ -1538,8 +1626,6 @@ void TabPrinter::build() m_printer_technology = m_presets->get_selected_preset().printer_technology(); m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff(); - -// on_value_change("printer_technology", m_printer_technology); // to update show/hide preset ComboBoxes } void TabPrinter::build_fff() @@ -1558,10 +1644,10 @@ void TabPrinter::build_fff() auto page = add_options_page(_(L("General")), "printer_empty.png"); auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); - Line line{ _(L("Bed shape")), "" }; - line.widget = [this](wxWindow* parent){ + Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; + line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - btn->SetFont(Slic3r::GUI::small_font()); + btn->SetFont(wxGetApp().small_font()); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -1571,7 +1657,7 @@ void TabPrinter::build_fff() { auto dlg = new BedShapeDialog(this); dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK){ + if (dlg->ShowModal() == wxID_OK) { load_key_value("bed_shape", dlg->GetValue()); update_changed_ui(); } @@ -1590,17 +1676,18 @@ void TabPrinter::build_fff() def.label = L("Extruders"); def.tooltip = L("Number of extruders of the printer."); def.min = 1; + def.mode = comExpert; Option option(def, "extruders_count"); optgroup->append_single_option_line(option); optgroup->append_single_option_line("single_extruder_multi_material"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){ + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); - wxTheApp->CallAfter([this, opt_key, value, extruders_count](){ - if (opt_key.compare("extruders_count")==0 || opt_key.compare("single_extruder_multi_material")==0) { + wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { + if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { extruders_count_changed(extruders_count); update_dirty(); - if (opt_key.compare("single_extruder_multi_material")==0) // the single_extruder_multimaterial was added to force pages + if (opt_key == "single_extruder_multi_material") // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped } else { @@ -1617,7 +1704,7 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(_(L("USB/Serial connection"))); line = {_(L("Serial port")), ""}; Option serial_port = optgroup->get_option("serial_port"); - serial_port.side_widget = ([this](wxWindow* parent){ + serial_port.side_widget = ([this](wxWindow* parent) { auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(Slic3r::var("arrow_rotate_clockwise.png")), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); btn->SetToolTip(_(L("Rescan serial ports"))); @@ -1627,7 +1714,7 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {update_serial_ports(); }); return sizer; }); - auto serial_test = [this](wxWindow* parent){ + auto serial_test = [this](wxWindow* parent) { auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(Slic3r::GUI::small_font()); @@ -1635,7 +1722,7 @@ void TabPrinter::build_fff() auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){ + btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { auto sender = Slic3r::make_unique(); auto res = sender->connect( m_config->opt_string("serial_port"), @@ -1720,7 +1807,7 @@ void TabPrinter::build_fff() auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e){ + btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() != wxID_CANCEL) { @@ -1754,9 +1841,9 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){ - wxTheApp->CallAfter([this, opt_key, value](){ - if (opt_key.compare("silent_mode") == 0) { + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + wxTheApp->CallAfter([this, opt_key, value]() { + if (opt_key == "silent_mode") { bool val = boost::any_cast(value); if (m_use_silent_mode != val) { m_rebuild_kinematics_page = true; @@ -1843,8 +1930,8 @@ void TabPrinter::build_sla() auto page = add_options_page(_(L("General")), "printer_empty.png"); auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); - Line line{ _(L("Bed shape")), "" }; - line.widget = [this](wxWindow* parent){ + Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; + line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); // btn->SetFont(Slic3r::GUI::small_font); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); @@ -1856,7 +1943,7 @@ void TabPrinter::build_sla() { auto dlg = new BedShapeDialog(this); dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK){ + if (dlg->ShowModal() == wxID_OK) { load_key_value("bed_shape", dlg->GetValue()); update_changed_ui(); } @@ -1876,6 +1963,7 @@ void TabPrinter::build_sla() line.append_option(option); line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); + optgroup->append_single_option_line("display_orientation"); optgroup = page->new_optgroup(_(L("Corrections"))); line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; @@ -1906,20 +1994,22 @@ void TabPrinter::build_sla() optgroup->append_line(line); } -void TabPrinter::update_serial_ports(){ +void TabPrinter::update_serial_ports() +{ Field *field = get_field("serial_port"); Choice *choice = static_cast(field); choice->set_values(Utils::scan_serial_ports()); } -void TabPrinter::extruders_count_changed(size_t extruders_count){ +void TabPrinter::extruders_count_changed(size_t extruders_count) +{ m_extruders_count = extruders_count; m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); m_preset_bundle->update_multi_material_filament_presets(); build_extruder_pages(); reload_config(); on_value_change("extruders_count", extruders_count); - update_objects_list_extruder_column(extruders_count); + wxGetApp().sidebar().update_objects_list_extruder_column(extruders_count); } void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) @@ -2003,7 +2093,7 @@ void TabPrinter::build_extruder_pages() break; } - if (existed_page < n_before_extruders && is_marlin_flavor){ + if (existed_page < n_before_extruders && is_marlin_flavor) { auto page = build_kinematics_page(); m_pages.insert(m_pages.begin() + n_before_extruders, page); } @@ -2031,14 +2121,15 @@ void TabPrinter::build_extruder_pages() optgroup->append_single_option_line("cooling_tube_length"); optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); + optgroup->append_single_option_line("high_current_on_filament_swap"); m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); m_has_single_extruder_MM_page = true; } - for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx){ + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { //# build page - char buf[MIN_BUF_LENGTH_FOR_L]; + char buf[512]; sprintf(buf, _CHB(L("Extruder %d")), extruder_idx + 1); auto page = add_options_page(from_u8(buf), "funnel.png", true); m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); @@ -2101,7 +2192,8 @@ void TabPrinter::on_preset_loaded() void TabPrinter::update_pages() { // update m_pages ONLY if printer technology is changed - if (m_presets->get_edited_preset().printer_technology() == m_printer_technology) + const PrinterTechnology new_printer_technology = m_presets->get_edited_preset().printer_technology(); + if (new_printer_technology == m_printer_technology) return; // hide all old pages @@ -2113,14 +2205,13 @@ void TabPrinter::update_pages() // build Tab according to the technology, if it's not exist jet OR // set m_pages_(technology after changing) to m_pages - if (m_presets->get_edited_preset().printer_technology() == ptFFF) + // m_printer_technology will be set by Tab::load_current_preset() + if (new_printer_technology == ptFFF) m_pages_fff.empty() ? build_fff() : m_pages.swap(m_pages_fff); else m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); rebuild_page_tree(true); - - on_value_change("printer_technology", m_presets->get_edited_preset().printer_technology()); // to update show/hide preset ComboBoxes } void TabPrinter::update() @@ -2232,7 +2323,8 @@ void TabPrinter::update_fff() Thaw(); } -void TabPrinter::update_sla(){ ; } +void TabPrinter::update_sla() +{ ; } // Initialize the UI from the current preset void Tab::load_current_preset() @@ -2245,6 +2337,8 @@ void Tab::load_current_preset() // For the printer profile, generate the extruder pages. if (preset.printer_technology() == ptFFF) on_preset_loaded(); + else + wxGetApp().sidebar().update_objects_list_extruder_column(1); // Reload preset pages with the new configuration values. reload_config(); @@ -2260,40 +2354,42 @@ void Tab::load_current_preset() // (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter([this]{ // checking out if this Tab exists till this moment - if (!checked_tab(this)) + if (!wxGetApp().checked_tab(this)) return; update_tab_ui(); // update show/hide tabs - if (m_name == "printer"){ + if (m_name == "printer") { PrinterTechnology& printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology != static_cast(this)->m_printer_technology) { - for (auto& tab : get_preset_tabs()){ - if (tab.technology != printer_technology) - { - int page_id = get_tab_panel()->FindPage(tab.panel); - get_tab_panel()->GetPage(page_id)->Show(false); - get_tab_panel()->RemovePage(page_id); - } - else - get_tab_panel()->InsertPage(get_tab_panel()->FindPage(this), tab.panel, tab.panel->title()); + for (auto tab : wxGetApp().tabs_list) { + if (tab->type() == Preset::TYPE_PRINTER) // Printer tab is shown every time + continue; + if (tab->supports_printer_technology(printer_technology)) + wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title()); + else { + int page_id = wxGetApp().tab_panel()->FindPage(tab); + wxGetApp().tab_panel()->GetPage(page_id)->Show(false); + wxGetApp().tab_panel()->RemovePage(page_id); + } } - static_cast(this)->m_printer_technology = printer_technology; } - } - - on_presets_changed(); - - if (name() == "print") - update_frequently_changed_parameters(); - if (m_name == "printer"){ - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + on_presets_changed(); + if (printer_technology == ptFFF) { + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } } + else { + on_presets_changed(); + if (m_name == "print") + update_frequently_changed_parameters(); + } + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; init_options_list(); update_changed_ui(); @@ -2305,8 +2401,9 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) { Freeze(); // get label of the currently selected item - auto selected = m_treectrl->GetItemText(m_treectrl->GetSelection()); - auto rootItem = m_treectrl->GetRootItem(); + const auto sel_item = m_treectrl->GetSelection(); + const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; + const auto rootItem = m_treectrl->GetRootItem(); auto have_selection = 0; m_treectrl->DeleteChildren(rootItem); @@ -2330,20 +2427,66 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) Thaw(); } +void Tab::update_page_tree_visibility() +{ + const auto sel_item = m_treectrl->GetSelection(); + const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; + const auto rootItem = m_treectrl->GetRootItem(); + + auto have_selection = 0; + m_treectrl->DeleteChildren(rootItem); + for (auto p : m_pages) + { + if (!p->get_show()) + continue; + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + if (p->title() == selected) { + m_treectrl->SelectItem(itemId); + have_selection = 1; + } + } + + if (!have_selection) { + // this is triggered on first load, so we don't disable the sel change event + m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); + } + +} + // Called by the UI combo box when the user switches profiles. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(std::string preset_name /*= ""*/) +void Tab::select_preset(std::string preset_name) { // If no name is provided, select the "-- default --" preset. if (preset_name.empty()) preset_name = m_presets->default_preset().name; - auto current_dirty = m_presets->current_is_dirty(); - auto printer_tab = m_presets->name() == "printer"; - auto canceled = false; - m_reload_dependent_tabs = {}; + bool current_dirty = m_presets->current_is_dirty(); + bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; + bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; + bool canceled = false; +// m_reload_dependent_tabs = {}; + m_dependent_tabs = {}; if (current_dirty && !may_discard_current_dirty_preset()) { canceled = true; + } else if (print_tab) { + // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material + // are compatible with the new print. + // If it is not compatible and the current filament or SLA material are dirty, let user decide + // whether to discard the changes or keep the current print selection. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; + bool old_preset_dirty = dependent.current_is_dirty(); + bool new_preset_compatible = dependent.get_edited_preset().is_compatible_with_print(*m_presets->find_preset(preset_name, true)); + if (! canceled) + canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name); + if (! canceled) { + // The preset will be switched to a different, compatible preset, or the '-- default --'. + m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL); + if (old_preset_dirty) + dependent.discard_current_changes(); + } } else if (printer_tab) { // Before switching the printer to a new one, verify, whether the currently active print and filament // are compatible with the new printer. @@ -2355,33 +2498,38 @@ void Tab::select_preset(std::string preset_name /*= ""*/) const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - struct PresetUpdate { - std::string name; - PresetCollection *presets; - PrinterTechnology technology; - bool old_preset_dirty; - bool new_preset_compatible; - }; - std::vector updates = { - { "print", &m_preset_bundle->prints, ptFFF }, - { "filament", &m_preset_bundle->filaments, ptFFF }, - { "sla_material", &m_preset_bundle->sla_materials, ptSLA } - }; - for (PresetUpdate &pu : updates) { - pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); - pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset); - if (! canceled) - canceled = pu.old_preset_dirty && ! pu.new_preset_compatible && ! may_discard_current_dirty_preset(pu.presets, preset_name); - } - if (! canceled) { - for (PresetUpdate &pu : updates) { - // The preset will be switched to a different, compatible preset, or the '-- default --'. - if (pu.technology == new_printer_technology) - m_reload_dependent_tabs.emplace_back(pu.name); - if (pu.old_preset_dirty) - pu.presets->discard_current_changes(); - } - } + if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset()) + canceled = true; + else { + struct PresetUpdate { + Preset::Type tab_type; + PresetCollection *presets; + PrinterTechnology technology; + bool old_preset_dirty; + bool new_preset_compatible; + }; + std::vector updates = { + { Preset::Type::TYPE_PRINT, &m_preset_bundle->prints, ptFFF }, + { Preset::Type::TYPE_SLA_PRINT, &m_preset_bundle->sla_prints, ptSLA }, + { Preset::Type::TYPE_FILAMENT, &m_preset_bundle->filaments, ptFFF }, + { Preset::Type::TYPE_SLA_MATERIAL, &m_preset_bundle->sla_materials,ptSLA } + }; + for (PresetUpdate &pu : updates) { + pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); + pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset); + if (!canceled) + canceled = pu.old_preset_dirty && !pu.new_preset_compatible && !may_discard_current_dirty_preset(pu.presets, preset_name); + } + if (!canceled) { + for (PresetUpdate &pu : updates) { + // The preset will be switched to a different, compatible preset, or the '-- default --'. + if (pu.technology == new_printer_technology) + m_dependent_tabs.emplace_back(pu.tab_type); + if (pu.old_preset_dirty) + pu.presets->discard_current_changes(); + } + } + } } if (canceled) { update_tab_ui(); @@ -2390,13 +2538,13 @@ void Tab::select_preset(std::string preset_name /*= ""*/) on_presets_changed(); } else { if (current_dirty) - m_presets->discard_current_changes() ; + m_presets->discard_current_changes(); m_presets->select_preset_by_name(preset_name, false); // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. - if (current_dirty || printer_tab) - m_preset_bundle->update_compatible_with_printer(true); + if (current_dirty || print_tab || printer_tab) + m_preset_bundle->update_compatible(true); // Initialize the UI from the current preset. if (printer_tab) static_cast(this)->update_pages(); @@ -2410,37 +2558,56 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr { if (presets == nullptr) presets = m_presets; // Display a dialog showing the dirty options in a human readable form. - auto old_preset = presets->get_edited_preset(); - auto type_name = presets->name(); - auto tab = " "; - auto name = old_preset.is_default ? + const Preset& old_preset = presets->get_edited_preset(); + std::string type_name = presets->name(); + wxString tab = " "; + wxString name = old_preset.is_default ? _(L("Default ")) + type_name + _(L(" preset")) : (type_name + _(L(" preset\n")) + tab + old_preset.name); // Collect descriptions of the dirty options. - std::vector option_names; - for(auto opt_key: presets->current_dirty_options()) { - auto opt = m_config->def()->options.at(opt_key); + wxString changes; + for (const std::string &opt_key : presets->current_dirty_options()) { + const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); std::string name = ""; - if (!opt.category.empty()) + if (! opt.category.empty()) name += opt.category + " > "; name += !opt.full_label.empty() ? opt.full_label : opt.label; - option_names.push_back(name); + changes += tab + from_u8(name) + "\n"; } // Show a confirmation dialog with the list of dirty options. - std::string changes = ""; - for (auto changed_name : option_names) - changes += tab + changed_name + "\n"; - auto message = (!new_printer_name.empty()) ? - name + _(L("\n\nis not compatible with printer\n")) +tab + new_printer_name+ _(L("\n\nand it has the following unsaved changes:")) : - name + _(L("\n\nhas the following unsaved changes:")); + wxString message = name + "\n\n"; + if (new_printer_name.empty()) + message += _(L("has the following unsaved changes:")); + else { + message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? + _(L("is not compatible with printer")) : + _(L("is not compatible with print profile")); + message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; + message += _(L("and it has the following unsaved changes:")); + } auto confirm = new wxMessageDialog(parent(), - message + "\n" +changes +_(L("\n\nDiscard changes and continue anyway?")), + message + "\n" + changes + _(L("\n\nDiscard changes and continue anyway?")), _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); return confirm->ShowModal() == wxID_YES; } +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool Tab::may_switch_to_SLA_preset() +{ + if (wxGetApp().obj_list()->has_multi_part_objects()) + { + show_info( parent(), + _(L("It's impossible to print multi-part object(s) with SLA technology.")) + + _(L("\n\nPlease check your object list before preset changing.")), + _(L("Attention!")) ); + return false; + } + return true; +} + void Tab::OnTreeSelChange(wxTreeEvent& event) { if (m_disable_tree_sel_changed_event) return; @@ -2454,9 +2621,13 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) wxWindowUpdateLocker noUpdates(this); #endif + if (m_pages.empty()) + return; + Page* page = nullptr; - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto p : m_pages) + const auto sel_item = m_treectrl->GetSelection(); + const auto selection = sel_item ? m_treectrl->GetItemText(sel_item) : ""; + for (auto p : m_pages) if (p->title() == selection) { page = p.get(); @@ -2523,7 +2694,7 @@ void Tab::save_preset(std::string name /*= ""*/) if (dlg->ShowModal() != wxID_OK) return; name = dlg->get_name(); - if (name == ""){ + if (name == "") { show_error(this, _(L("The supplied name is empty. It can't be saved."))); return; } @@ -2541,7 +2712,7 @@ void Tab::save_preset(std::string name /*= ""*/) // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini m_presets->save_current_preset(name); // Mark the print & filament enabled if they are compatible with the currently selected preset. - m_preset_bundle->update_compatible_with_printer(false); + m_preset_bundle->update_compatible(false); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the platter. @@ -2569,7 +2740,7 @@ void Tab::delete_preset() // Delete the file and select some other reasonable preset. // The 'external' presets will only be removed from the preset list, their files will not be deleted. try{ m_presets->delete_current_preset(); } - catch (const std::exception &e) + catch (const std::exception & /* e */) { return; } @@ -2597,8 +2768,9 @@ void Tab::update_ui_from_settings() { // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled // in application preferences. - m_show_btn_incompatible_presets = get_app_config()->get("show_incompatible_presets")[0] == '1' ? true : false; - bool show = m_show_btn_incompatible_presets && m_presets->name().compare("printer") != 0; + m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; + bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; + Layout(); show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); // If the 'show / hide presets' button is hidden, hide the incompatible presets. if (show) { @@ -2613,50 +2785,53 @@ void Tab::update_ui_from_settings() } // Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. -wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn) +wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) { - *checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); - *btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - (*btn)->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + deps.btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add((*checkbox), 0, wxALIGN_CENTER_VERTICAL); - sizer->Add((*btn), 0, wxALIGN_CENTER_VERTICAL); + sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); + sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); - (*checkbox)->Bind(wxEVT_CHECKBOX, ([=](wxCommandEvent e) + deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) { - (*btn)->Enable(!(*checkbox)->GetValue()); + deps.btn->Enable(! deps.checkbox->GetValue()); // All printers have been made compatible with this preset. - if ((*checkbox)->GetValue()) - load_key_value("compatible_printers", std::vector {}); - get_field("compatible_printers_condition")->toggle((*checkbox)->GetValue()); - update_changed_ui(); + if (deps.checkbox->GetValue()) + this->load_key_value(deps.key_list, std::vector {}); + this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); + this->update_changed_ui(); }) ); - (*btn)->Bind(wxEVT_BUTTON, ([this, parent, checkbox, btn](wxCommandEvent e) + deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) { - // # Collect names of non-default non-external printer profiles. - PresetCollection *printers = &m_preset_bundle->printers; + // Collect names of non-default non-external profiles. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : + (printer_technology == ptFFF) ? m_preset_bundle->prints : m_preset_bundle->sla_prints; wxArrayString presets; - for (size_t idx = 0; idx < printers->size(); ++idx) + for (size_t idx = 0; idx < depending_presets.size(); ++ idx) { - Preset& preset = printers->preset(idx); - if (!preset.is_default && !preset.is_external && !preset.is_system) - presets.Add(preset.name); + Preset& preset = depending_presets.preset(idx); + bool add = ! preset.is_default && ! preset.is_external; + if (add && deps.type == Preset::TYPE_PRINTER) + // Only add printers with the same technology as the active printer. + add &= preset.printer_technology() == printer_technology; + if (add) + presets.Add(from_u8(preset.name)); } - wxMultiChoiceDialog dlg(parent, - _(L("Select the printers this profile is compatible with.")), - _(L("Compatible printers")), presets); - // # Collect and set indices of printers marked as compatible. + wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); + // Collect and set indices of depending_presets marked as compatible. wxArrayInt selections; - auto *compatible_printers = dynamic_cast(m_config->option("compatible_printers")); + auto *compatible_printers = dynamic_cast(m_config->option(deps.key_list)); if (compatible_printers != nullptr || !compatible_printers->values.empty()) for (auto preset_name : compatible_printers->values) for (size_t idx = 0; idx < presets.GetCount(); ++idx) - if (presets[idx].compare(preset_name) == 0) - { + if (presets[idx] == preset_name) { selections.Add(idx); break; } @@ -2669,182 +2844,23 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox for (auto idx : selections) value.push_back(presets[idx].ToStdString()); if (value.empty()) { - (*checkbox)->SetValue(1); - (*btn)->Disable(); + deps.checkbox->SetValue(1); + deps.btn->Disable(); } - // All printers have been made compatible with this preset. - load_key_value("compatible_printers", value); - update_changed_ui(); + // All depending_presets have been made compatible with this preset. + this->load_key_value(deps.key_list, value); + this->update_changed_ui(); } })); - return sizer; + return sizer; } -void Tab::update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible) +void Tab::compatible_widget_reload(PresetDependencies &deps) { - if (ui == nullptr) - return; - ui->Freeze(); - ui->DeleteAllItems(); - auto presets = m_presets->get_presets(); - auto idx_selected = m_presets->get_idx_selected(); - auto suffix_modified = m_presets->get_suffix_modified(); - int icon_compatible = 0; - int icon_incompatible = 1; - int cnt_items = 0; - - auto root_sys = ui->AppendContainer(wxDataViewItem(0), _(L("System presets"))); - auto root_def = ui->AppendContainer(wxDataViewItem(0), _(L("Default presets"))); - - auto show_def = get_app_config()->get("no_defaults")[0] != '1'; - - for (size_t i = presets.front().is_visible ? 0 : 1; i < presets.size(); ++i) { - const Preset &preset = presets[i]; - if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) - continue; - - auto preset_name = wxString::FromUTF8((preset.name + (preset.is_dirty ? suffix_modified : "")).c_str()); - - wxDataViewItem item; - if (preset.is_system) - item = ui->AppendItem(root_sys, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else if (show_def && preset.is_default) - item = ui->AppendItem(root_def, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else - { - auto parent = m_presets->get_preset_parent(preset); - if (parent == nullptr) - item = ui->AppendItem(root_def, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else - { - auto parent_name = parent->name; - - wxDataViewTreeStoreContainerNode *node = ui->GetStore()->FindContainerNode(root_sys); - if (node) - { - wxDataViewTreeStoreNodeList::iterator iter; - for (iter = node->GetChildren().begin(); iter != node->GetChildren().end(); iter++) - { - wxDataViewTreeStoreNode* child = *iter; - auto child_item = child->GetItem(); - auto item_text = ui->GetItemText(child_item); - if (item_text == parent_name) - { - auto added_child = ui->AppendItem(child->GetItem(), preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - if (!added_child){ - ui->DeleteItem(child->GetItem()); - auto new_parent = ui->AppendContainer(root_sys, parent_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - ui->AppendItem(new_parent, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - } - break; - } - } - } - } - } - - cnt_items++; - if (i == idx_selected){ - ui->Select(item); - m_cc_presets_choice->SetText(preset_name); - } - } - if (ui->GetStore()->GetChildCount(root_def) == 0) - ui->DeleteItem(root_def); - - ui->Thaw(); -} - -void Tab::update_tab_presets(wxComboCtrl* ui, bool show_incompatible) -{ - if (ui == nullptr) - return; - ui->Freeze(); - ui->Clear(); - auto presets = m_presets->get_presets(); - auto idx_selected = m_presets->get_idx_selected(); - auto suffix_modified = m_presets->get_suffix_modified(); - int icon_compatible = 0; - int icon_incompatible = 1; - int cnt_items = 0; - - wxDataViewTreeCtrlComboPopup* popup = wxDynamicCast(m_cc_presets_choice->GetPopupControl(), wxDataViewTreeCtrlComboPopup); - if (popup != nullptr) - { - popup->DeleteAllItems(); - - auto root_sys = popup->AppendContainer(wxDataViewItem(0), _(L("System presets"))); - auto root_def = popup->AppendContainer(wxDataViewItem(0), _(L("Default presets"))); - - auto show_def = get_app_config()->get("no_defaults")[0] != '1'; - - for (size_t i = presets.front().is_visible ? 0 : 1; i < presets.size(); ++i) { - const Preset &preset = presets[i]; - if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) - continue; - - auto preset_name = wxString::FromUTF8((preset.name + (preset.is_dirty ? suffix_modified : "")).c_str()); - - wxDataViewItem item; - if (preset.is_system) - item = popup->AppendItem(root_sys, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else if (show_def && preset.is_default) - item = popup->AppendItem(root_def, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else - { - auto parent = m_presets->get_preset_parent(preset); - if (parent == nullptr) - item = popup->AppendItem(root_def, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - else - { - auto parent_name = parent->name; - - wxDataViewTreeStoreContainerNode *node = popup->GetStore()->FindContainerNode(root_sys); - if (node) - { - wxDataViewTreeStoreNodeList::iterator iter; - for (iter = node->GetChildren().begin(); iter != node->GetChildren().end(); iter++) - { - wxDataViewTreeStoreNode* child = *iter; - auto child_item = child->GetItem(); - auto item_text = popup->GetItemText(child_item); - if (item_text == parent_name) - { - auto added_child = popup->AppendItem(child->GetItem(), preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - if (!added_child){ - popup->DeleteItem(child->GetItem()); - auto new_parent = popup->AppendContainer(root_sys, parent_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - popup->AppendItem(new_parent, preset_name, - preset.is_compatible ? icon_compatible : icon_incompatible); - } - break; - } - } - } - } - } - - cnt_items++; - if (i == idx_selected){ - popup->Select(item); - m_cc_presets_choice->SetText(preset_name); - } - } - if (popup->GetStore()->GetChildCount(root_def) == 0) - popup->DeleteItem(root_def); - } - ui->Thaw(); + bool has_any = ! m_config->option(deps.key_list)->values.empty(); + has_any ? deps.btn->Enable() : deps.btn->Disable(); + deps.checkbox->SetValue(! has_any); + this->get_field(deps.key_condition)->toggle(! has_any); } void Tab::fill_icon_descriptions() @@ -2922,10 +2938,19 @@ void Page::reload_config() group->reload_config(); } +void Page::update_visibility(ConfigOptionMode mode) +{ + bool ret_val = false; + for (auto group : m_optgroups) + ret_val = group->update_visibility(mode) || ret_val; + + m_show = ret_val; +} + Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1*/) const { Field* field = nullptr; - for (auto opt : m_optgroups){ + for (auto opt : m_optgroups) { field = opt->get_fieldc(opt_key, opt_index); if (field != nullptr) return field; @@ -2933,7 +2958,7 @@ Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1* return field; } -bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value){ +bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value) { bool changed = false; for(auto optgroup: m_optgroups) { if (optgroup->set_value(opt_key, value)) @@ -2945,8 +2970,22 @@ bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value // package Slic3r::GUI::Tab::Page; ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_label_width /*= -1*/) { + auto extra_column = [](wxWindow* parent, const Line& line) + { + std::string bmp_name; + if (line.get_options().size() == 0) + bmp_name = "error.png"; + else { + auto mode = line.get_options()[0].opt.mode; //we assume that we have one option per line + bmp_name = mode == comExpert ? "mode_expert_.png" : + mode == comAdvanced ? "mode_middle_.png" : "mode_simple_.png"; + } + auto bmp = new wxStaticBitmap(parent, wxID_ANY, wxBitmap(from_u8(var(bmp_name)), wxBITMAP_TYPE_PNG)); + return bmp; + }; + //! config_ have to be "right" - ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true); + ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); if (noncommon_label_width >= 0) optgroup->label_width = noncommon_label_width; @@ -2955,7 +2994,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la #else auto tab = GetParent(); #endif - optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value){ + optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { //! This function will be called from OptionGroup. //! Using of CallAfter is redundant. //! And in some cases it causes update() function to be recalled again @@ -2965,17 +3004,17 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la //! }); }; - optgroup->m_get_initial_config = [this, tab](){ + optgroup->m_get_initial_config = [this, tab]() { DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; return config; }; - optgroup->m_get_sys_config = [this, tab](){ + optgroup->m_get_sys_config = [this, tab]() { DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; return config; }; - optgroup->have_sys_config = [this, tab](){ + optgroup->have_sys_config = [this, tab]() { return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; }; @@ -3016,8 +3055,8 @@ void SavePresetWindow::accept() bool is_unusable_symbol = false; bool is_unusable_postfix = false; const std::string unusable_postfix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++){ - if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos){ + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { is_unusable_symbol = true; break; } @@ -3029,12 +3068,12 @@ void SavePresetWindow::accept() show_error(this,_(L("The supplied name is not valid;")) + "\n" + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } - else if (is_unusable_postfix){ + else if (is_unusable_postfix) { show_error(this,_(L("The supplied name is not valid;")) + "\n" + _(L("the following postfix are not allowed:")) + "\n\t" + //unusable_postfix); wxString::FromUTF8(unusable_postfix.c_str())); } - else if (m_chosen_name.compare("- default -") == 0) { + else if (m_chosen_name == "- default -") { show_error(this, _(L("The supplied name is not available."))); } else { @@ -3051,7 +3090,7 @@ void TabSLAMaterial::build() auto page = add_options_page(_(L("Material")), "package_green.png"); auto optgroup = page->new_optgroup(_(L("Layers"))); - optgroup->append_single_option_line("layer_height"); +// optgroup->append_single_option_line("layer_height"); optgroup->append_single_option_line("initial_layer_height"); optgroup = page->new_optgroup(_(L("Exposure"))); @@ -3062,7 +3101,7 @@ void TabSLAMaterial::build() optgroup->label_width = 190; std::vector corrections = { "material_correction_printing", "material_correction_curing" }; std::vector axes{ "X", "Y", "Z" }; - for (auto& opt_key : corrections){ + for (auto& opt_key : corrections) { auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; int id = 0; for (auto& axis : axes) { @@ -3085,9 +3124,100 @@ void TabSLAMaterial::build() page = add_options_page(_(L("Dependencies")), "wrench.png"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); - auto line = Line { _(L("Compatible printers")), "" }; - line.widget = [this](wxWindow* parent){ - return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); + Line line = optgroup->create_single_option_line("compatible_printers"); + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_printers); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + line = optgroup->create_single_option_line("compatible_prints"); + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); +} + +// Reload current config (aka presets->edited_preset->config) into the UI fields. +void TabSLAMaterial::reload_config() +{ + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); +} + +void TabSLAMaterial::update() +{ + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + return; // #ys_FIXME +} + +void TabSLAPrint::build() +{ + m_presets = &m_preset_bundle->sla_prints; + load_initial_data(); + + auto page = add_options_page(_(L("Layers and perimeters")), "package_green.png"); + + auto optgroup = page->new_optgroup(_(L("Layers"))); + optgroup->append_single_option_line("layer_height"); + + page = add_options_page(_(L("Supports")), "building.png"); + optgroup = page->new_optgroup(_(L("Supports"))); + optgroup->append_single_option_line("supports_enable"); + + optgroup = page->new_optgroup(_(L("Support head"))); + optgroup->append_single_option_line("support_head_front_diameter"); + optgroup->append_single_option_line("support_head_penetration"); + optgroup->append_single_option_line("support_head_width"); + + optgroup = page->new_optgroup(_(L("Support pillar"))); + optgroup->append_single_option_line("support_pillar_diameter"); + optgroup->append_single_option_line("support_pillar_widening_factor"); + optgroup->append_single_option_line("support_base_diameter"); + optgroup->append_single_option_line("support_base_height"); + optgroup->append_single_option_line("support_object_elevation"); + + optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); + optgroup->append_single_option_line("support_critical_angle"); + optgroup->append_single_option_line("support_max_bridge_length"); + + optgroup = page->new_optgroup(_(L("Automatic generation"))); + optgroup->append_single_option_line("support_density_at_horizontal"); + optgroup->append_single_option_line("support_density_at_45"); + optgroup->append_single_option_line("support_minimal_z"); + + page = add_options_page(_(L("Pad")), "brick.png"); + optgroup = page->new_optgroup(_(L("Pad"))); + optgroup->append_single_option_line("pad_enable"); + optgroup->append_single_option_line("pad_wall_thickness"); + optgroup->append_single_option_line("pad_wall_height"); + optgroup->append_single_option_line("pad_max_merge_distance"); + optgroup->append_single_option_line("pad_edge_radius"); + + page = add_options_page(_(L("Output options")), "page_white_go.png"); + optgroup = page->new_optgroup(_(L("Output file"))); + Option option = optgroup->get_option("output_filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + Line line = optgroup->create_single_option_line("compatible_printers");//Line { _(L("Compatible printers")), "" }; + line.widget = [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_printers); }; optgroup->append_line(line, &m_colored_Label); @@ -3103,10 +3233,17 @@ void TabSLAMaterial::build() optgroup->append_line(line); } -void TabSLAMaterial::update() +// Reload current config (aka presets->edited_preset->config) into the UI fields. +void TabSLAPrint::reload_config() { - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptFFF) - return; // ys_FIXME + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); +} + +void TabSLAPrint::update() +{ + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + return; // #ys_FIXME } } // GUI diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 39baaa490..d84073667 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -23,16 +23,14 @@ #include #include #include -#include -#include #include #include #include #include "BedShapeDialog.hpp" +#include "Event.hpp" -//!enum { ID_TAB_TREE = wxID_HIGHEST + 1 }; namespace Slic3r { namespace GUI { @@ -49,6 +47,7 @@ class Page : public wxScrolledWindow wxString m_title; size_t m_iconID; wxBoxSizer* m_vsizer; + bool m_show = true; public: Page(wxWindow* parent, const wxString title, const int iconID) : m_parent(parent), @@ -57,10 +56,10 @@ public: { Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_vsizer = new wxBoxSizer(wxVERTICAL); - m_item_color = &get_label_clr_default(); + m_item_color = &wxGetApp().get_label_clr_default(); SetSizer(m_vsizer); } - ~Page(){} + ~Page() {} bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; @@ -75,6 +74,7 @@ public: size_t iconID() const { return m_iconID; } void set_config(DynamicPrintConfig* config_in) { m_config = config_in; } void reload_config(); + void update_visibility(ConfigOptionMode mode); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1); @@ -90,13 +90,17 @@ public: const wxColour get_item_colour() { return *m_item_color; } + bool get_show() const { return m_show; } protected: // Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. const wxColour* m_item_color; }; -// Slic3r::GUI::Tab; + +wxDECLARE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); +wxDECLARE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); + using PageShp = std::shared_ptr; class Tab: public wxPanel @@ -107,6 +111,7 @@ class Tab: public wxPanel int m_size_move = -1; #endif // __WXOSX__ protected: + Preset::Type m_type; std::string m_name; const wxString m_title; wxBitmapComboBox* m_presets_choice; @@ -117,13 +122,22 @@ protected: wxBoxSizer* m_left_sizer; wxTreeCtrl* m_treectrl; wxImageList* m_icons; - wxCheckBox* m_compatible_printers_checkbox; - wxButton* m_compatible_printers_btn; + + struct PresetDependencies { + Preset::Type type = Preset::TYPE_INVALID; + wxCheckBox *checkbox = nullptr; + wxButton *btn = nullptr; + std::string key_list; // "compatible_printers" + std::string key_condition; + std::string dialog_title; + std::string dialog_label; + }; + PresetDependencies m_compatible_printers; + PresetDependencies m_compatible_prints; + wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; wxButton* m_question_btn; - wxComboCtrl* m_cc_presets_choice; - wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; // Cached bitmaps. @@ -172,22 +186,21 @@ protected: bool m_show_incompatible_presets; std::vector m_reload_dependent_tabs = {}; + std::vector m_dependent_tabs = {}; enum OptStatus { osSystemValue = 1, osInitValue = 2 }; std::map m_options_list; int m_opt_status_value = 0; t_icon_descriptions m_icon_descriptions = {}; - // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. - wxEventType m_event_value_change = 0; - wxEventType m_event_presets_changed = 0; - bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; bool m_postpone_update_ui {false}; size_t m_selected_preset_item{ 0 }; + void set_type(); + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; @@ -197,34 +210,24 @@ public: wxStaticText* m_colored_Label = nullptr; public: - Tab() {} - Tab(wxNotebook* parent, const wxString& title, const char* name) : - m_parent(parent), m_title(title), m_name(name) { - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name); - get_tabs_list().push_back(this); - } - ~Tab(){ - delete_tab_from_list(this); + Tab(wxNotebook* parent, const wxString& title, const char* name); + ~Tab() { + wxGetApp().delete_tab_from_list(this); } wxWindow* parent() const { return m_parent; } wxString title() const { return m_title; } std::string name() const { return m_name; } + Preset::Type type() const { return m_type; } + virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; - // Set the events to the callbacks posted to the main frame window (currently implemented in Perl). - void set_event_value_change(wxEventType evt) { m_event_value_change = evt; } - void set_event_presets_changed(wxEventType evt) { m_event_presets_changed = evt; } - - void create_preset_tab(PresetBundle *preset_bundle); + void create_preset_tab(); void load_current_preset(); void rebuild_page_tree(bool tree_sel_change_event = false); + void update_page_tree_visibility(); void select_preset(std::string preset_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); - wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); - - void update_presetsctrl(wxDataViewTreeCtrl* ui, bool show_incompatible); - void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); - void reload_compatible_printers_widget(); + bool may_switch_to_SLA_preset(); void OnTreeSelChange(wxTreeEvent& event); void OnKeyDown(wxKeyEvent& event); @@ -245,7 +248,7 @@ public: PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); virtual void OnActivate(); - virtual void on_preset_loaded(){} + virtual void on_preset_loaded() {} virtual void build() = 0; virtual void update() = 0; virtual void init_options_list(); @@ -254,6 +257,7 @@ public: void update_tab_ui(); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); + void update_visibility(ConfigOptionMode mode); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); @@ -267,23 +271,25 @@ public: void on_value_change(const std::string& opt_key, const boost::any& value); void update_wiping_button_visibility(); + protected: + wxSizer* compatible_widget_create(wxWindow* parent, PresetDependencies &deps); + void compatible_widget_reload(PresetDependencies &deps); + void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); + void on_presets_changed(); void update_preset_description_line(); void update_frequently_changed_parameters(); - void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); void fill_icon_descriptions(); void set_tooltips_text(); }; -//Slic3r::GUI::Tab::Print; class TabPrint : public Tab { public: - TabPrint() {} TabPrint(wxNotebook* parent) : Tab(parent, _(L("Print Settings")), "print") {} - ~TabPrint(){} + ~TabPrint() {} ogStaticText* m_recommended_thin_wall_thickness_description_line; bool m_support_material_overhangs_queried = false; @@ -292,26 +298,24 @@ public: void reload_config() override; void update() override; void OnActivate() override; + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; - -//Slic3r::GUI::Tab::Filament; class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line; ogStaticText* m_cooling_description_line; public: - TabFilament() {} TabFilament(wxNotebook* parent) : Tab(parent, _(L("Filament Settings")), "filament") {} - ~TabFilament(){} + ~TabFilament() {} void build() override; void reload_config() override; void update() override; void OnActivate() override; + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; -//Slic3r::GUI::Tab::Printer; class TabPrinter : public Tab { bool m_has_single_extruder_MM_page = false; @@ -322,9 +326,9 @@ class TabPrinter : public Tab std::vector m_pages_fff; std::vector m_pages_sla; public: - wxButton* m_serial_test_btn; - wxButton* m_print_host_test_btn; - wxButton* m_printhost_browse_btn; + wxButton* m_serial_test_btn = nullptr; + wxButton* m_print_host_test_btn = nullptr; + wxButton* m_printhost_browse_btn = nullptr; size_t m_extruders_count; size_t m_extruders_count_old = 0; @@ -333,9 +337,8 @@ public: PrinterTechnology m_printer_technology = ptFFF; - TabPrinter() {} TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Hardware Settings")), "printer") {} - ~TabPrinter(){} + ~TabPrinter() {} void build() override; void build_fff(); @@ -350,26 +353,41 @@ public: void build_extruder_pages(); void on_preset_loaded() override; void init_options_list() override; + bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } }; class TabSLAMaterial : public Tab { public: - TabSLAMaterial() {} TabSLAMaterial(wxNotebook* parent) : - Tab(parent, _(L("SLA Material Settings")), "sla_material") {} - ~TabSLAMaterial(){} + Tab(parent, _(L("Material Settings")), "sla_material") {} + ~TabSLAMaterial() {} void build() override; + void reload_config() override; void update() override; void init_options_list() override; + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +}; + +class TabSLAPrint : public Tab +{ +public: + TabSLAPrint(wxNotebook* parent) : + Tab(parent, _(L("Print Settings")), "sla_print") {} + ~TabSLAPrint() {} + void build() override; + void reload_config() override; + void update() override; +// void init_options_list() override; + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } }; class SavePresetWindow :public wxDialog { public: - SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){} - ~SavePresetWindow(){} + SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))) {} + ~SavePresetWindow() {} std::string m_chosen_name; wxComboBox* m_combo; diff --git a/src/slic3r/GUI/TabIface.cpp b/src/slic3r/GUI/TabIface.cpp deleted file mode 100644 index 29833322b..000000000 --- a/src/slic3r/GUI/TabIface.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "TabIface.hpp" -#include "Tab.hpp" - -namespace Slic3r { - -void TabIface::load_current_preset() { m_tab->load_current_preset(); } -void TabIface::update_tab_ui() { m_tab->update_tab_ui(); } -void TabIface::update_ui_from_settings() { m_tab->update_ui_from_settings();} -void TabIface::select_preset(char* name) { m_tab->select_preset(name);} -void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*config);} -void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast(value)); } -bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();} -void TabIface::OnActivate() { return m_tab->OnActivate();} -size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); } -std::string TabIface::title() { return m_tab->title().ToUTF8().data(); } -DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); } -PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; } -std::vector TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); } - -}; // namespace Slic3r diff --git a/src/slic3r/GUI/TabIface.hpp b/src/slic3r/GUI/TabIface.hpp deleted file mode 100644 index 2f7f4e8e7..000000000 --- a/src/slic3r/GUI/TabIface.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef slic3r_TabIface_hpp_ -#define slic3r_TabIface_hpp_ - -#include -#include - -namespace Slic3r { - class DynamicPrintConfig; - class PresetCollection; - -namespace GUI { - class Tab; -} - -class TabIface { -public: - TabIface() : m_tab(nullptr) {} - TabIface(GUI::Tab *tab) : m_tab(tab) {} -// TabIface(const TabIface &rhs) : m_tab(rhs.m_tab) {} - - void load_current_preset(); - void update_tab_ui(); - void update_ui_from_settings(); - void select_preset(char* name); - std::string title(); - void load_config(DynamicPrintConfig* config); - void load_key_value(char* opt_key, char* value); - bool current_preset_is_dirty(); - void OnActivate(); - DynamicPrintConfig* get_config(); - PresetCollection* get_presets(); - std::vector get_dependent_tabs(); - size_t get_selected_preset_item(); - -protected: - GUI::Tab *m_tab; -}; // namespace GUI - -}; // namespace Slic3r - -#endif /* slic3r_TabIface_hpp_ */ diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 70d9c851c..346a9e231 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -12,6 +12,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "I18N.hpp" #include "ConfigWizard.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/Widget.hpp b/src/slic3r/GUI/Widget.hpp deleted file mode 100644 index bcf772469..000000000 --- a/src/slic3r/GUI/Widget.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef WIDGET_HPP -#define WIDGET_HPP -#include -#ifndef WX_PRECOM -#include -#endif - -class Widget { -protected: - wxSizer* _sizer; -public: - Widget(): _sizer(nullptr) { } - bool valid() const { return _sizer != nullptr; } - wxSizer* sizer() const { return _sizer; } -}; -#endif diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index eef4017c1..2530f5fea 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -2,6 +2,7 @@ #include #include "WipeTowerDialog.hpp" #include "GUI.hpp" +#include "I18N.hpp" #include @@ -108,7 +109,7 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters) m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} ); - Refresh(this); + Refresh(true); // erase background } void RammingPanel::line_parameters_changed() { diff --git a/src/slic3r/GUI/callback.hpp b/src/slic3r/GUI/callback.hpp deleted file mode 100644 index ac92721a5..000000000 --- a/src/slic3r/GUI/callback.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// I AM A PHONY PLACEHOLDER FOR THE PERL CALLBACK. -// GET RID OF ME! - -#ifndef slic3r_GUI_PerlCallback_phony_hpp_ -#define slic3r_GUI_PerlCallback_phony_hpp_ - -#include - -namespace Slic3r { - -class PerlCallback { -public: - PerlCallback(void *) {} - PerlCallback() {} - void register_callback(void *) {} - void deregister_callback() {} - void call() const {} - void call(int) const {} - void call(int, int) const {} - void call(const std::vector&) const {} - void call(double) const {} - void call(double, double) const {} - void call(double, double, double) const {} - void call(double, double, double, double) const {} - void call(bool b) const {} -}; - -} // namespace Slic3r - -#endif /* slic3r_GUI_PerlCallback_phony_hpp_ */ diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 13730a497..2daba5df4 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1,14 +1,61 @@ #include "wxExtensions.hpp" -#include "GUI.hpp" -#include "../../libslic3r/Utils.hpp" -#include "BitmapCache.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" #include #include #include #include +#include "BitmapCache.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectList.hpp" + +wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); +wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); + +wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler) +{ + if (id == wxID_ANY) + id = wxNewId(); + + wxMenuItem* item = menu->Append(id, string, description); + item->SetBitmap(icon); + +#ifdef __WXMSW__ + if (event_handler != nullptr && event_handler != menu) + event_handler->Bind(wxEVT_MENU, cb, id); + else +#endif // __WXMSW__ + menu->Bind(wxEVT_MENU, cb, id); + + return item; +} + +wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, const std::string& icon, wxEvtHandler* event_handler) +{ + const wxBitmap& bmp = !icon.empty() ? wxBitmap(Slic3r::var(icon), wxBITMAP_TYPE_PNG) : wxNullBitmap; + return append_menu_item(menu, id, string, description, cb, bmp, event_handler); +} + +wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon) +{ + if (id == wxID_ANY) + id = wxNewId(); + + wxMenuItem* item = new wxMenuItem(menu, id, string, description); + if (!icon.empty()) + item->SetBitmap(wxBitmap(Slic3r::var(icon), wxBITMAP_TYPE_PNG)); + + item->SetSubMenu(sub_menu); + menu->Append(item); + + return item; +} + const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; @@ -270,7 +317,7 @@ bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxSt m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane")); - wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + wxColour&& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); m_pDisclosureTriangleButton->SetBackgroundColour(clr); this->SetBackgroundColour(clr); m_pPane->SetBackgroundColour(clr); @@ -361,17 +408,19 @@ void PrusaObjectDataViewModelNode::set_part_action_icon() { Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; bool PrusaObjectDataViewModelNode::update_settings_digest(const std::vector& categories) { - if (m_type != "settings" || m_opt_categories == categories) + if (m_type != itSettings || m_opt_categories == categories) return false; m_opt_categories = categories; m_name = wxEmptyString; - m_icon = m_empty_icon; + m_bmp = m_empty_bmp; - auto categories_icon = Slic3r::GUI::get_category_icon(); + std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; for (auto& cat : m_opt_categories) m_name += cat + "; "; + if (!m_name.IsEmpty()) + m_name.erase(m_name.Length()-2, 2); // Delete last "; " wxBitmap *bmp = m_bitmap_cache->find(m_name.ToStdString()); if (bmp == nullptr) { @@ -405,9 +454,10 @@ PrusaObjectDataViewModel::~PrusaObjectDataViewModel() m_bitmap_cache = nullptr; } -wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name) +wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int extruder) { - auto root = new PrusaObjectDataViewModelNode(name); + const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + auto root = new PrusaObjectDataViewModelNode(name, extruder_str); m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); @@ -416,47 +466,41 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name) return child; } -wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int instances_count/*, int scale*/) -{ - auto root = new PrusaObjectDataViewModelNode(name, instances_count); - m_objects.push_back(root); - // notify control - wxDataViewItem child((void*)root); - wxDataViewItem parent((void*)NULL); - ItemAdded(parent, child); - return child; -} - -wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, +wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, - const wxBitmap& icon, + const int volume_type, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); - const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); - if (create_frst_child && (root->GetChildren().Count() == 0 || - (root->GetChildren().Count() == 1 && root->GetNthChild(0)->m_type == "settings"))) + // because of istance_root is a last item of the object + int insert_position = root->GetChildCount() - 1; + if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) + insert_position = -1; + + if (create_frst_child && root->m_volumes_cnt == 0) { - const auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG); - const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0); - root->Append(node); + const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); + insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); + + root->m_volumes_cnt++; + if (insert_position > 0) insert_position++; } - const auto volume_id = root->GetChildCount() > 0 && root->GetNthChild(0)->m_type == "settings" ? - root->GetChildCount() - 1 : root->GetChildCount(); - - const auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id); - root->Append(node); + const auto node = new PrusaObjectDataViewModelNode(root, name, *m_volume_bmps[volume_type], extruder_str, root->m_volumes_cnt); + insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control const wxDataViewItem child((void*)node); - ItemAdded(parent_item, child); + ItemAdded(parent_item, child); + root->m_volumes_cnt++; + return child; } @@ -465,7 +509,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddSettingsChild(const wxDataViewItem & PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); - const auto node = new PrusaObjectDataViewModelNode(root); + const auto node = new PrusaObjectDataViewModelNode(root, itSettings); root->Insert(node, 0); // notify control const wxDataViewItem child((void*)node); @@ -473,6 +517,52 @@ wxDataViewItem PrusaObjectDataViewModel::AddSettingsChild(const wxDataViewItem & return child; } +int get_istances_root_idx(PrusaObjectDataViewModelNode *parent_node) +{ + // because of istance_root is a last item of the object + const int inst_root_idx = parent_node->GetChildCount()-1; + + if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->m_type == itInstanceRoot) + return inst_root_idx; + + return -1; +} + +wxDataViewItem PrusaObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +{ + PrusaObjectDataViewModelNode *parent_node = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // Check and create/get instances root node + const int inst_root_id = get_istances_root_idx(parent_node); + + PrusaObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ? + new PrusaObjectDataViewModelNode(parent_node, itInstanceRoot) : + parent_node->GetNthChild(inst_root_id); + const wxDataViewItem inst_root_item((void*)inst_root_node); + + if (inst_root_id < 0) { + parent_node->Append(inst_root_node); + // notify control + ItemAdded(parent_item, inst_root_item); +// if (num == 1) num++; + } + + // Add instance nodes + PrusaObjectDataViewModelNode *instance_node = nullptr; + size_t counter = 0; + while (counter < num) { + instance_node = new PrusaObjectDataViewModelNode(inst_root_node, itInstance); + inst_root_node->Append(instance_node); + // notify control + const wxDataViewItem instance_item((void*)instance_node); + ItemAdded(inst_root_item, instance_item); + ++counter; + } + + return wxDataViewItem((void*)instance_node); +} + wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -486,31 +576,118 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) // first remove the node from the parent's array of children; // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it - if (node_parent){ + if (node_parent) { + if (node->m_type == itInstanceRoot) + { + for (int i = node->GetChildCount() - 1; i > 0; i--) + Delete(wxDataViewItem(node->GetNthChild(i))); + return parent; + } + auto id = node_parent->GetChildren().Index(node); - auto v_id = node->GetVolumeId(); + auto idx = node->GetIdx(); + + + if (node->m_type == itVolume) { + node_parent->m_volumes_cnt--; + DeleteSettings(item); + } node_parent->GetChildren().Remove(node); - if (id > 0){ + + if (id > 0) { if(id == node_parent->GetChildCount()) id--; ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); } - //update volume_id value for remaining child-nodes + //update idx value for remaining child-nodes auto children = node_parent->GetChildren(); - for (size_t i = 0; i < node_parent->GetChildCount() && v_id>=0; i++) + for (size_t i = 0; i < node_parent->GetChildCount() && idx>=0; i++) { - auto volume_id = children[i]->GetVolumeId(); - if (volume_id > v_id) - children[i]->SetVolumeId(volume_id-1); + auto cur_idx = children[i]->GetIdx(); + if (cur_idx > idx) + children[i]->SetIdx(cur_idx-1); } + + // if there is last instance item, delete both of it and instance root item + if (node_parent->GetChildCount() == 1 && node_parent->GetNthChild(0)->m_type == itInstance) + { + delete node; + ItemDeleted(parent, item); + + PrusaObjectDataViewModelNode *last_instance_node = node_parent->GetNthChild(0); + node_parent->GetChildren().Remove(last_instance_node); + delete last_instance_node; + ItemDeleted(parent, wxDataViewItem(last_instance_node)); + + PrusaObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + // if there is last volume item after deleting, delete this last volume too + if (node_parent->GetChildCount() <= 3) + { + int vol_cnt = 0; + int vol_idx = 0; + for (int i = 0; i < node_parent->GetChildCount(); ++i) { + if (node_parent->GetNthChild(i)->GetType() == itVolume) { + vol_idx = i; + vol_cnt++; + } + if (vol_cnt > 1) + break; + } + + if (vol_cnt == 1) { + delete node; + ItemDeleted(parent, item); + + PrusaObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); + DeleteSettings(wxDataViewItem(last_child_node)); + node_parent->GetChildren().Remove(last_child_node); + node_parent->m_volumes_cnt = 0; + delete last_child_node; + +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ItemDeleted(parent, wxDataViewItem(last_child_node)); + + wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); + auto it = find(m_objects.begin(), m_objects.end(), node_parent); + event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); + wxPostEvent(m_ctrl, event); + + ret_item = parent; + + return ret_item; + } + } } else { auto it = find(m_objects.begin(), m_objects.end(), node); auto id = it - m_objects.begin(); if (it != m_objects.end()) + { + // Delete all sub-items + int i = m_objects[id]->GetChildCount() - 1; + while (i >= 0) { + Delete(wxDataViewItem(m_objects[id]->GetNthChild(i))); + i = m_objects[id]->GetChildCount() - 1; + } m_objects.erase(it); - if (id > 0){ + } + if (id > 0) { if(id == m_objects.size()) id--; ret_item = wxDataViewItem(m_objects[id]); } @@ -532,6 +709,43 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) return ret_item; } +wxDataViewItem PrusaObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &parent_item, size_t num) +{ + auto ret_item = wxDataViewItem(0); + PrusaObjectDataViewModelNode *parent_node = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return ret_item; + + const int inst_root_id = get_istances_root_idx(parent_node); + if (inst_root_id < 0) return ret_item; + + wxDataViewItemArray items; + PrusaObjectDataViewModelNode *inst_root_node = parent_node->GetNthChild(inst_root_id); + const wxDataViewItem inst_root_item((void*)inst_root_node); + + const int inst_cnt = inst_root_node->GetChildCount(); + const bool delete_inst_root_item = inst_cnt - num < 2 ? true : false; + + int stop = delete_inst_root_item ? 0 : inst_cnt - num; + for (int i = inst_cnt - 1; i >= stop;--i) { + PrusaObjectDataViewModelNode *last_instance_node = inst_root_node->GetNthChild(i); + inst_root_node->GetChildren().Remove(last_instance_node); + delete last_instance_node; + ItemDeleted(inst_root_item, wxDataViewItem(last_instance_node)); + } + + if (delete_inst_root_item) { + ret_item = parent_item; + parent_node->GetChildren().Remove(inst_root_node); + ItemDeleted(parent_item, inst_root_item); +#ifndef __WXGTK__ + if (parent_node->GetChildCount() == 0) + parent_node->m_container = false; +#endif //__WXGTK__ + } + + return ret_item; +} + void PrusaObjectDataViewModel::DeleteAll() { while (!m_objects.empty()) @@ -558,6 +772,9 @@ void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) auto item = wxDataViewItem(node); children.RemoveAt(id); + if (node->m_type == itVolume) + root->m_volumes_cnt--; + // free the node delete node; @@ -571,6 +788,55 @@ void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) #endif //__WXGTK__ } +void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + if (node->m_type != itVolume) + continue; + + auto item = wxDataViewItem(node); + DeleteSettings(item); + children.RemoveAt(id); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + root->m_volumes_cnt = 0; + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + +void PrusaObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) return; + + // if volume has a "settings"item, than delete it before volume deleting + if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) { + auto settings_node = node->GetNthChild(0); + auto settings_item = wxDataViewItem(settings_node); + node->GetChildren().RemoveAt(0); + delete settings_node; + ItemDeleted(parent, settings_item); + } +} + wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) { if (obj_idx >= m_objects.size()) @@ -584,25 +850,48 @@ wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) wxDataViewItem PrusaObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx) { - if (obj_idx >= m_objects.size()) { + if (obj_idx >= m_objects.size() || obj_idx < 0) { printf("Error! Out of objects range.\n"); return wxDataViewItem(0); } auto parent = m_objects[obj_idx]; - if (parent->GetChildCount() == 0) { + if (parent->GetChildCount() == 0 || + (parent->GetChildCount() == 1 && parent->GetNthChild(0)->GetType() & itSettings )) { + if (volume_idx == 0) + return GetItemById(obj_idx); + printf("Error! Object has no one volume.\n"); return wxDataViewItem(0); } for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_volume_id == volume_idx) + if (parent->GetNthChild(i)->m_idx == volume_idx && parent->GetNthChild(i)->GetType() & itVolume) return wxDataViewItem(parent->GetNthChild(i)); return wxDataViewItem(0); } -int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) +wxDataViewItem PrusaObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +{ + if (obj_idx >= m_objects.size() || obj_idx < 0) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); + if (!instances_item) + return wxDataViewItem(0); + + auto parent = (PrusaObjectDataViewModelNode*)instances_item.GetID();; + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_idx == inst_idx) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int PrusaObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { wxASSERT(item.IsOk()); @@ -614,14 +903,92 @@ int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) return it - m_objects.begin(); } -int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) +int PrusaObjectDataViewModel::GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const { wxASSERT(item.IsOk()); PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false + if (!node || node->m_type != type) return -1; - return node->GetVolumeId(); + return node->GetIdx(); +} + +int PrusaObjectDataViewModel::GetObjectIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItem(GetTopParent(item)); +} + +int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itVolume); +} + +int PrusaObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itInstance); +} + +void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) +{ + wxASSERT(item.IsOk()); + type = itUndef; + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) + return; + + idx = node->GetIdx(); + type = node->GetType(); + + PrusaObjectDataViewModelNode *parent_node = node->GetParent(); + if (!parent_node) return; + if (type == itInstance) + parent_node = node->GetParent()->GetParent(); + if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } + + auto it = find(m_objects.begin(), m_objects.end(), parent_node); + if (it != m_objects.end()) + obj_idx = it - m_objects.begin(); + else + type = itUndef; +} + +int PrusaObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const +{ + if (m_objects.empty()) + return -1; + + int row_num = 0; + + for (int i = 0; i < m_objects.size(); i++) + { + row_num++; + if (item == wxDataViewItem(m_objects[i])) + return row_num; + + for (int j = 0; j < m_objects[i]->GetChildCount(); j++) + { + row_num++; + PrusaObjectDataViewModelNode* cur_node = m_objects[i]->GetNthChild(j); + if (item == wxDataViewItem(cur_node)) + return row_num; + + if (cur_node->m_type == itVolume && cur_node->GetChildCount() == 1) + row_num++; + if (cur_node->m_type == itInstanceRoot) + { + row_num++; + for (int t = 0; t < cur_node->GetChildCount(); t++) + { + row_num++; + if (item == wxDataViewItem(cur_node->GetNthChild(t))) + return row_num; + } + } + } + } + + return -1; } wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const @@ -633,21 +1000,6 @@ wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const return node->m_name; } -wxString PrusaObjectDataViewModel::GetCopy(const wxDataViewItem &item) const -{ - PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_copy; -} - -wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const -{ - PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - return node->m_icon; -} - wxBitmap& PrusaObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const { PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); @@ -661,17 +1013,13 @@ void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); switch (col) { - case 0:{ - const PrusaDataViewBitmapText data(node->m_name, node->m_bmp); - variant << data; - break;} - case 1: - variant = node->m_copy; + case 0: + variant << PrusaDataViewBitmapText(node->m_name, node->m_bmp); break; - case 2: + case 1: variant = node->m_extruder; break; - case 3: + case 2: variant << node->m_action_icon; break; default: @@ -694,7 +1042,7 @@ bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item return m_objects[item_idx]->SetValue(variant, col); } - +/* wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -708,7 +1056,7 @@ wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item) return ret_item; auto volume_id = node->GetVolumeId(); - if (0 < volume_id && volume_id < node_parent->GetChildCount()){ + if (0 < volume_id && volume_id < node_parent->GetChildCount()) { node_parent->SwapChildrens(volume_id - 1, volume_id); ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id - 1)); ItemChanged(item); @@ -732,7 +1080,7 @@ wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &ite return ret_item; auto volume_id = node->GetVolumeId(); - if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()){ + if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()) { node_parent->SwapChildrens(volume_id + 1, volume_id); ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id + 1)); ItemChanged(item); @@ -742,7 +1090,7 @@ wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &ite ret_item = wxDataViewItem(node_parent->GetNthChild(node_parent->GetChildCount()-1)); return ret_item; } - +*/ wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_id, int new_volume_id, const wxDataViewItem &parent) { auto ret_item = wxDataViewItem(0); @@ -753,14 +1101,14 @@ wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_i if (!node_parent) // happens if item.IsOk()==false return ret_item; - const size_t shift = node_parent->GetChildren().Item(0)->m_type == "settings" ? 1 : 0; + const size_t shift = node_parent->GetChildren().Item(0)->m_type == itSettings ? 1 : 0; PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); node_parent->GetChildren().Remove(deleted_node); ItemDeleted(parent, wxDataViewItem(deleted_node)); node_parent->Insert(deleted_node, new_volume_id+shift); ItemAdded(parent, wxDataViewItem(deleted_node)); - const auto settings_item = HasSettings(wxDataViewItem(deleted_node)); + const auto settings_item = GetSettingsItem(wxDataViewItem(deleted_node)); if (settings_item) ItemAdded(wxDataViewItem(deleted_node), settings_item); @@ -769,7 +1117,7 @@ wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_i int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; for (int id = id_frst; id <= id_last; ++id) - children[id+shift]->SetVolumeId(id); + children[id+shift]->SetIdx(id); return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift)); } @@ -779,8 +1127,8 @@ bool PrusaObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned in wxASSERT(item.IsOk()); PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - // disable extruder selection for the "Settings" item - return !(col == 2 && node->m_extruder.IsEmpty()); + // disable extruder selection for the non "itObject|itVolume" item + return !(col == 1 && node->m_extruder.IsEmpty()); } wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const @@ -792,12 +1140,32 @@ wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) c PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); // objects nodes has no parent too - if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end()) + if (node->m_type == itObject) return wxDataViewItem(0); return wxDataViewItem((void*)node->GetParent()); } +wxDataViewItem PrusaObjectDataViewModel::GetTopParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (node->m_type == itObject) + return item; + + PrusaObjectDataViewModelNode *parent_node = node->GetParent(); + while (parent_node->m_type != itObject) + { + node = parent_node; + parent_node = node->GetParent(); + } + + return wxDataViewItem((void*)parent_node); +} + bool PrusaObjectDataViewModel::IsContainer(const wxDataViewItem &item) const { // the invisible root node can have children @@ -833,28 +1201,47 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, return count; } -wxDataViewItem PrusaObjectDataViewModel::HasSettings(const wxDataViewItem &item) const +ItemType PrusaObjectDataViewModel::GetItemType(const wxDataViewItem &item) const { if (!item.IsOk()) + return itUndef; + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_type; +} + +wxDataViewItem PrusaObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const +{ + if (!parent_item.IsOk()) return wxDataViewItem(0); - PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (node->GetChildCount() == 0) return wxDataViewItem(0); - auto& children = node->GetChildren(); - if (children[0]->m_type == "settings") - return wxDataViewItem((void*)children[0]);; + for (int i = 0; i < node->GetChildCount(); i++) { + if (node->GetNthChild(i)->m_type == type) + return wxDataViewItem((void*)node->GetNthChild(i)); + } return wxDataViewItem(0); } +wxDataViewItem PrusaObjectDataViewModel::GetSettingsItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itSettings); +} + +wxDataViewItem PrusaObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itInstanceRoot); +} + bool PrusaObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) return false; PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - return node->m_type == "settings"; + return node->m_type == itSettings; } @@ -869,11 +1256,38 @@ void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, ItemChanged(item); } +void PrusaObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const int type) +{ + if (!item.IsOk() || GetItemType(item) != itVolume) + return; + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + node->SetBitmap(*m_volume_bmps[type]); + ItemChanged(item); +} + +//----------------------------------------------------------------------------- +// PrusaDataViewBitmapText +//----------------------------------------------------------------------------- + +wxIMPLEMENT_DYNAMIC_CLASS(PrusaDataViewBitmapText, wxObject) + IMPLEMENT_VARIANT_OBJECT(PrusaDataViewBitmapText) + // --------------------------------------------------------- // PrusaIconTextRenderer // --------------------------------------------------------- +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +PrusaBitmapTextRenderer::PrusaBitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, + int align /*= wxDVR_DEFAULT_ALIGNMENT*/): +wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) +{ + SetMode(mode); + SetAlignment(align); +} +#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + bool PrusaBitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; @@ -885,6 +1299,13 @@ bool PrusaBitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const return false; } +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY +wxString PrusaBitmapTextRenderer::GetAccessibleDescription() const +{ + return m_value.GetText(); +} +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + bool PrusaBitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) { int xoffset = 0; @@ -915,10 +1336,66 @@ wxSize PrusaBitmapTextRenderer::GetSize() const } +wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); + PrusaObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); + + if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) + return nullptr; + + PrusaDataViewBitmapText data; + data << value; + + m_was_unusable_symbol = false; + + wxPoint position = labelRect.GetPosition(); + if (data.GetBitmap().IsOk()) { + const int bmp_width = data.GetBitmap().GetWidth(); + position.x += bmp_width; + labelRect.SetWidth(labelRect.GetWidth() - bmp_width); + } + + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), + position, labelRect.GetSize(), wxTE_PROCESS_ENTER); + text_editor->SetInsertionPointEnd(); + text_editor->SelectAll(); + + return text_editor; +} + +bool PrusaBitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); + if (!text_editor || text_editor->GetValue().IsEmpty()) + return false; + + std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); + const char* unusable_symbols = "<>:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + m_was_unusable_symbol = true; + return false; + } + } + + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + GetView()->GetModel()->GetValue(valueOld, m_item, 0); + + PrusaDataViewBitmapText bmpText; + bmpText << valueOld; + + // But replace the text with the value entered by user. + bmpText.SetText(text_editor->GetValue()); + + value << bmpText; + return true; +} + // ---------------------------------------------------------------------------- // PrusaDoubleSlider // ---------------------------------------------------------------------------- - PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent, wxWindowID id, int lowerValue, @@ -1093,11 +1570,43 @@ void PrusaDoubleSlider::get_size(int *w, int *h) is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; } -double PrusaDoubleSlider::get_double_value(const SelectedSlider& selection) const +double PrusaDoubleSlider::get_double_value(const SelectedSlider& selection) +{ + if (m_values.empty() || m_lower_value<0) + return 0.0; + if (m_values.size() <= m_higher_value) { + correct_higher_value(); + return m_values.back().second; + } + return m_values[selection == ssLower ? m_lower_value : m_higher_value].second; +} + +std::vector PrusaDoubleSlider::GetTicksValues() const +{ + std::vector values; + + if (!m_values.empty()) + for (auto tick : m_ticks) + values.push_back(m_values[tick].second); + + return values; +} + +void PrusaDoubleSlider::SetTicksValues(const std::vector& heights) { if (m_values.empty()) - return 0.0; - return m_values[selection == ssLower ? m_lower_value : m_higher_value].second; + return; + + m_ticks.clear(); + unsigned int i = 0; + for (auto h : heights) { + while (i < m_values.size() && m_values[i].second - 1e-6 < h) + ++i; + if (i == m_values.size()) + return; + m_ticks.insert(i-1); + } + } void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) @@ -1186,7 +1695,8 @@ void PrusaDoubleSlider::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, c dc.DrawLine(pt_beg, pt_end); //draw action icon - draw_action_icon(dc, pt_beg, pt_end); + if (m_is_enabled_tick_manipulation) + draw_action_icon(dc, pt_beg, pt_end); } } @@ -1196,6 +1706,8 @@ wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const if (m_label_koef == 1.0 && m_values.empty()) return wxString::Format("%d", value); + if (value >= m_values.size()) + return "ErrVal"; const wxString str = m_values.empty() ? wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) : @@ -1292,7 +1804,7 @@ void PrusaDoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wx void PrusaDoubleSlider::draw_ticks(wxDC& dc) { - dc.SetPen(DARK_GREY_PEN); + dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN ); int height, width; get_size(&width, &height); const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; @@ -1373,6 +1885,23 @@ bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) return false; } +int PrusaDoubleSlider::is_point_near_tick(const wxPoint& pt) +{ + for (auto tick : m_ticks) { + const wxCoord pos = get_position_from_value(tick); + + if (is_horizontal()) { + if (pos - 4 <= pt.x && pt.x <= pos + 4) + return tick; + } + else { + if (pos - 4 <= pt.y && pt.y <= pos + 4) + return tick; + } + } + return -1; +} + void PrusaDoubleSlider::ChangeOneLayerLock() { m_is_one_layer = !m_is_one_layer; @@ -1392,20 +1921,41 @@ void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event) this->CaptureMouse(); wxClientDC dc(this); wxPoint pos = event.GetLogicalPosition(dc); - if (is_point_in_rect(pos, m_rect_tick_action)) { + if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) { action_tick(taOnIcon); return; } m_is_left_down = true; - if (is_point_in_rect(pos, m_rect_one_layer_icon)){ + if (is_point_in_rect(pos, m_rect_one_layer_icon)) { m_is_one_layer = !m_is_one_layer; + if (!m_is_one_layer) { + SetLowerValue(m_min_value); + SetHigherValue(m_max_value); + } m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; } else detect_selected_slider(pos); + if (!m_selection && m_is_enabled_tick_manipulation) { + const auto tick = is_point_near_tick(pos); + if (tick >= 0) + { + if (abs(tick - m_lower_value) < abs(tick - m_higher_value)) { + SetLowerValue(tick); + correct_lower_value(); + m_selection = ssLower; + } + else { + SetHigherValue(tick); + correct_higher_value(); + m_selection = ssHigher; + } + } + } + Refresh(); Update(); event.Skip(); @@ -1435,29 +1985,36 @@ void PrusaDoubleSlider::correct_higher_value() void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) { + bool action = false; + const wxClientDC dc(this); const wxPoint pos = event.GetLogicalPosition(dc); m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); - if (!m_is_left_down && !m_is_one_layer){ + if (!m_is_left_down && !m_is_one_layer) { m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); } - else if (m_is_left_down || m_is_right_down){ + else if (m_is_left_down || m_is_right_down) { if (m_selection == ssLower) { m_lower_value = get_value_from_position(pos.x, pos.y); correct_lower_value(); + action = true; } else if (m_selection == ssHigher) { m_higher_value = get_value_from_position(pos.x, pos.y); correct_higher_value(); + action = true; } } Refresh(); Update(); event.Skip(); - wxCommandEvent e(wxEVT_SCROLL_CHANGED); - e.SetEventObject(this); - ProcessWindowEvent(e); + if (action) + { + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); + } } void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) @@ -1514,18 +2071,19 @@ void PrusaDoubleSlider::action_tick(const TicksAction action) const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - if (action == taOnIcon && !m_ticks.insert(tick).second) - m_ticks.erase(tick); + if (action == taOnIcon) { + if (!m_ticks.insert(tick).second) + m_ticks.erase(tick); + } else { const auto it = m_ticks.find(tick); if (it == m_ticks.end() && action == taAdd) m_ticks.insert(tick); else if (it != m_ticks.end() && action == taDel) m_ticks.erase(tick); - else - return; } + wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); Refresh(); Update(); } @@ -1553,7 +2111,7 @@ void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event) { if (key == WXK_LEFT || key == WXK_RIGHT) move_current_thumb(key == WXK_LEFT); - else if (key == WXK_UP || key == WXK_DOWN){ + else if (key == WXK_UP || key == WXK_DOWN) { m_selection = key == WXK_UP ? ssHigher : ssLower; Refresh(); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 51c02035c..e8fba1ea2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -12,6 +12,14 @@ #include #include +#include + +wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr); +wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr); + +wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = ""); class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { @@ -111,6 +119,7 @@ class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane wxButton* m_pDisclosureTriangleButton = nullptr; wxBitmap m_bmp_close; wxBitmap m_bmp_open; + wxString m_strLabel; public: PrusaCollapsiblePaneMSW() {} PrusaCollapsiblePaneMSW( wxWindow *parent, @@ -165,7 +174,7 @@ public: void SetText(const wxString &text) { m_text = text; } wxString GetText() const { return m_text; } - void SetBitmap(const wxIcon &icon) { m_bmp = icon; } + void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } const wxBitmap &GetBitmap() const { return m_bmp; } bool IsSameAs(const PrusaDataViewBitmapText& other) const { @@ -183,6 +192,8 @@ public: private: wxString m_text; wxBitmap m_bmp; + + wxDECLARE_DYNAMIC_CLASS(PrusaDataViewBitmapText); }; DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText) @@ -191,6 +202,15 @@ DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText) // PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel // ---------------------------------------------------------------------------- +enum ItemType { + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16 +}; + class PrusaObjectDataViewModelNode; WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); @@ -198,22 +218,22 @@ class PrusaObjectDataViewModelNode { PrusaObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; - wxIcon m_empty_icon; wxBitmap m_empty_bmp; + size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; public: - PrusaObjectDataViewModelNode(const wxString &name, const int instances_count=1) { + PrusaObjectDataViewModelNode(const wxString &name, + const wxString& extruder) { m_parent = NULL; m_name = name; - m_copy = wxString::Format("%d", instances_count); - m_type = "object"; - m_volume_id = -1; + m_type = itObject; #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container // in another case you couldn't to add subitem for this item // it will be produce "segmentation fault" m_container = true; #endif //__WXGTK__ + m_extruder = extruder; set_object_action_icon(); } @@ -221,13 +241,12 @@ public: const wxString& sub_obj_name, const wxBitmap& bmp, const wxString& extruder, - const int volume_id=-1) { + const int idx = -1 ) { m_parent = parent; m_name = sub_obj_name; - m_copy = wxEmptyString; m_bmp = bmp; - m_type = "volume"; - m_volume_id = volume_id; + m_type = itVolume; + m_idx = idx; m_extruder = extruder; #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container @@ -238,12 +257,25 @@ public: set_part_action_icon(); } - PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent) : + PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), - m_name("Settings to modified"), - m_copy(wxEmptyString), - m_type("settings"), - m_extruder(wxEmptyString) {} + m_type(type), + m_extruder(wxEmptyString) + { + if (type == itSettings) { + m_name = "Settings to modified"; + } + else if (type == itInstanceRoot) { + m_name = "Instances"; +#ifdef __WXGTK__ + m_container = true; +#endif //__WXGTK__ + } + else if (type == itInstance) { + m_idx = parent->GetChildCount(); + m_name = wxString::Format("Instance_%d", m_idx+1); + } + } ~PrusaObjectDataViewModelNode() { @@ -257,11 +289,9 @@ public: } wxString m_name; - wxIcon& m_icon = m_empty_icon; wxBitmap& m_bmp = m_empty_bmp; - wxString m_copy; - std::string m_type; - int m_volume_id = -2; + ItemType m_type; + int m_idx = -1; bool m_container = false; wxString m_extruder = "default"; wxBitmap m_action_icon; @@ -325,12 +355,9 @@ public: m_name = data.GetText(); return true;} case 1: - m_copy = variant.GetString(); - return true; - case 2: m_extruder = variant.GetString(); return true; - case 3: + case 2: m_action_icon << variant; return true; default: @@ -338,28 +365,25 @@ public: } return false; } - void SetIcon(const wxIcon &icon) - { - m_icon = icon; - } void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } - - void SetType(const std::string& type){ - m_type = type; - } - const std::string& GetType(){ - return m_type; + + ItemType GetType() const { + return m_type; + } + + void SetIdx(const int& idx) { + m_idx = idx; + // update name if this node is instance + if (m_type == itInstance) + m_name = wxString::Format("Instance_%d", m_idx + 1); } - void SetVolumeId(const int& volume_id){ - m_volume_id = volume_id; - } - const int& GetVolumeId(){ - return m_volume_id; + int GetIdx() const { + return m_idx; } // use this function only for childrens @@ -367,9 +391,10 @@ public: { // ! Don't overwrite other values because of equality of this values for all children -- m_name = from_node.m_name; - m_icon = from_node.m_icon; - m_volume_id = from_node.m_volume_id; - m_extruder = from_node.m_extruder; + m_bmp = from_node.m_bmp; + m_idx = from_node.m_idx; + m_extruder = from_node.m_extruder; + m_type = from_node.m_type; } bool SwapChildrens(int frst_id, int scnd_id) { @@ -381,8 +406,8 @@ public: PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); - new_scnd.m_volume_id = m_children.Item(scnd_id)->m_volume_id; - new_frst.m_volume_id = m_children.Item(frst_id)->m_volume_id; + new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; + new_frst.m_idx = m_children.Item(frst_id)->m_idx; m_children.Item(frst_id)->AssignAllVal(new_frst); m_children.Item(scnd_id)->AssignAllVal(new_scnd); @@ -393,41 +418,57 @@ public: void set_object_action_icon(); void set_part_action_icon(); bool update_settings_digest(const std::vector& categories); +private: + friend class PrusaObjectDataViewModel; }; // ---------------------------------------------------------------------------- // PrusaObjectDataViewModel // ---------------------------------------------------------------------------- +// custom message the model sends to associated control to notify a last volume deleted from the object: +wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); + class PrusaObjectDataViewModel :public wxDataViewModel { - std::vector m_objects; + std::vector m_objects; + std::vector m_volume_bmps; + + wxDataViewCtrl* m_ctrl{ nullptr }; + public: PrusaObjectDataViewModel(); ~PrusaObjectDataViewModel(); - wxDataViewItem Add(const wxString &name); - wxDataViewItem Add(const wxString &name, const int instances_count); - wxDataViewItem AddChild(const wxDataViewItem &parent_item, + wxDataViewItem Add(const wxString &name, const int extruder); + wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, - const wxBitmap& icon, + const int volume_type, const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem Delete(const wxDataViewItem &item); + wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); void DeleteChildren(wxDataViewItem& parent); + void DeleteVolumeChildren(wxDataViewItem& parent); + void DeleteSettings(const wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); - int GetIdByItem(wxDataViewItem& item); - int GetVolumeIdByItem(const wxDataViewItem& item); - bool IsEmpty() { return m_objects.empty(); } + wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + int GetIdByItem(const wxDataViewItem& item) const; + int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; + int GetObjectIdByItem(const wxDataViewItem& item) const; + int GetVolumeIdByItem(const wxDataViewItem& item) const; + int GetInstanceIdByItem(const wxDataViewItem& item) const; + void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); + int GetRowByItem(const wxDataViewItem& item) const; + bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog wxString GetName(const wxDataViewItem &item) const; - wxString GetCopy(const wxDataViewItem &item) const; - wxIcon& GetIcon(const wxDataViewItem &item) const; wxBitmap& GetBitmap(const wxDataViewItem &item) const; // helper methods to change the model @@ -441,8 +482,8 @@ public: const wxDataViewItem &item, unsigned int col) override; bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col); - wxDataViewItem MoveChildUp(const wxDataViewItem &item); - wxDataViewItem MoveChildDown(const wxDataViewItem &item); +// wxDataViewItem MoveChildUp(const wxDataViewItem &item); +// wxDataViewItem MoveChildDown(const wxDataViewItem &item); // For parent move child from cur_volume_id place to new_volume_id // Remaining items will moved up/down accordingly wxDataViewItem ReorganizeChildren(int cur_volume_id, @@ -452,6 +493,8 @@ public: virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + // get object item + wxDataViewItem GetTopParent(const wxDataViewItem &item) const; virtual bool IsContainer(const wxDataViewItem &item) const override; virtual unsigned int GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const override; @@ -460,33 +503,70 @@ public: // In our case it is an item with all columns virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } - wxDataViewItem HasSettings(const wxDataViewItem &item) const; + ItemType GetItemType(const wxDataViewItem &item) const ; + wxDataViewItem GetItemByType(const wxDataViewItem &parent_item, ItemType type) const; + wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; + wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; bool IsSettingsItem(const wxDataViewItem &item) const; void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector& categories); + + void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } + void SetVolumeType(const wxDataViewItem &item, const int type); + + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } }; // ---------------------------------------------------------------------------- // PrusaBitmapTextRenderer // ---------------------------------------------------------------------------- - +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +class PrusaBitmapTextRenderer : public wxDataViewRenderer +#else class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING { public: - PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, - int align = wxDVR_DEFAULT_ALIGNMENT): - wxDataViewCustomRenderer(wxT("wxObject"), mode, align) {} + PrusaBitmapTextRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + + ,int align = wxDVR_DEFAULT_ALIGNMENT +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ); +#else + ) : wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {} +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING bool SetValue(const wxVariant &value); bool GetValue(wxVariant &value) const; +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const override; +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING virtual bool Render(wxRect cell, wxDC *dc, int state); virtual wxSize GetSize() const; - virtual bool HasEditorCtrl() const { return false; } + bool HasEditorCtrl() const override + { +#ifdef __WXOSX__ + return false; +#else + return true; +#endif + } + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override; + bool GetValueFromEditorCtrl( wxWindow* ctrl, + wxVariant& value) override; + bool WasCanceled() const { return m_was_unusable_symbol; } private: -// wxDataViewIconText m_value; PrusaDataViewBitmapText m_value; + bool m_was_unusable_symbol {false}; }; @@ -586,6 +666,9 @@ private: // PrusaDoubleSlider // ---------------------------------------------------------------------------- +// custom message the slider sends to its parent to notify a tick-change: +wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); + enum SelectedSlider { ssUndef, ssLower, @@ -596,6 +679,7 @@ enum TicksAction{ taAdd, taDel }; + class PrusaDoubleSlider : public wxControl { public: @@ -611,7 +695,7 @@ public: long style = wxSL_VERTICAL, const wxValidator& val = wxDefaultValidator, const wxString& name = wxEmptyString); - ~PrusaDoubleSlider(){} + ~PrusaDoubleSlider() {} int GetLowerValue() const { return m_lower_value; @@ -620,8 +704,8 @@ public: return m_higher_value; } int GetActiveValue() const; - double GetLowerValueD() const { return get_double_value(ssLower); } - double GetHigherValueD() const { return get_double_value(ssHigher); } + double GetLowerValueD() { return get_double_value(ssLower); } + double GetHigherValueD() { return get_double_value(ssHigher); } wxSize DoGetBestSize() const override; void SetLowerValue(const int lower_val); void SetHigherValue(const int higher_val); @@ -633,13 +717,21 @@ public: m_values = values; } void ChangeOneLayerLock(); + std::vector GetTicksValues() const; + void SetTicksValues(const std::vector& heights); + void EnableTickManipulation(bool enable = true) { + m_is_enabled_tick_manipulation = enable; + } + void DisableTickManipulation() { + EnableTickManipulation(false); + } - void OnPaint(wxPaintEvent& ){ render();} + void OnPaint(wxPaintEvent& ) { render();} void OnLeftDown(wxMouseEvent& event); void OnMotion(wxMouseEvent& event); void OnLeftUp(wxMouseEvent& event); - void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); } - void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); } + void OnEnterWin(wxMouseEvent& event) { enter_window(event, true); } + void OnLeaveWin(wxMouseEvent& event) { enter_window(event, false); } void OnWheel(wxMouseEvent& event); void OnKeyDown(wxKeyEvent &event); void OnKeyUp(wxKeyEvent &event); @@ -669,6 +761,7 @@ protected: void enter_window(wxMouseEvent& event, const bool enter); bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); + int is_point_near_tick(const wxPoint& pt); bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } double get_scroll_step(); @@ -678,7 +771,7 @@ protected: wxCoord get_position_from_value(const int value); wxSize get_size(); void get_size(int *w, int *h); - double get_double_value(const SelectedSlider& selection) const; + double get_double_value(const SelectedSlider& selection); private: int m_min_value; @@ -702,6 +795,7 @@ private: bool m_is_focused = false; bool m_is_action_icon_focesed = false; bool m_is_one_layer_icon_focesed = false; + bool m_is_enabled_tick_manipulation = true; wxRect m_rect_lower_thumb; wxRect m_rect_higher_thumb; @@ -743,11 +837,11 @@ public: wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); - ~PrusaLockButton(){} + ~PrusaLockButton() {} void OnButton(wxCommandEvent& event); - void OnEnterBtn(wxMouseEvent& event){ enter_button(true); event.Skip(); } - void OnLeaveBtn(wxMouseEvent& event){ enter_button(false); event.Skip(); } + void OnEnterBtn(wxMouseEvent& event) { enter_button(true); event.Skip(); } + void OnLeaveBtn(wxMouseEvent& event) { enter_button(false); event.Skip(); } bool IsLocked() const { return m_is_pushed; } diff --git a/src/slic3r/Utils/Bonjour.cpp b/src/slic3r/Utils/Bonjour.cpp index 09d9b5873..bfd9d4828 100644 --- a/src/slic3r/Utils/Bonjour.cpp +++ b/src/slic3r/Utils/Bonjour.cpp @@ -677,7 +677,7 @@ void Bonjour::priv::lookup_perform() socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler); } } - } catch (std::exception& e) { + } catch (std::exception& /* e */) { } } diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index f25327161..4eda7bd46 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -1,5 +1,4 @@ #include "Duet.hpp" -#include "PrintHostSendDialog.hpp" #include #include @@ -19,7 +18,9 @@ #include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX #include "Http.hpp" namespace fs = boost::filesystem; @@ -61,10 +62,10 @@ bool Duet::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the Duet")); fs::path filepath(filename); - PrintHostSendDialog send_dialog(filepath.filename(), true); + PrintHostSendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } - const bool print = send_dialog.print(); + const bool print = send_dialog.start_print(); const auto upload_filepath = send_dialog.filename(); const auto upload_filename = upload_filepath.filename(); const auto upload_parent_path = upload_filepath.parent_path(); @@ -135,6 +136,11 @@ bool Duet::send_gcode(const std::string &filename) const return res; } +bool Duet::upload(PrintHostUpload upload_data) const +{ + throw "unimplemented"; +} + bool Duet::has_auto_discovery() const { return false; diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index bc210d7a4..db21fd0a1 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -24,6 +24,7 @@ public: wxString get_test_failed_msg (wxString &msg) const; // Send gcode file to duet, filename is expected to be in UTF-8 bool send_gcode(const std::string &filename) const; + bool upload(PrintHostUpload upload_data) const; bool has_auto_discovery() const; bool can_test() const; private: diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 556035a5b..4b487588a 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -4,6 +4,16 @@ # define NOMINMAX #endif +// Windows Runtime +#include +// for ComPtr +#include + +// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ +#include +#include +#include + #include "FixModelByWin10.hpp" #include @@ -18,18 +28,11 @@ #include #include -#include -// for ComPtr -#include -// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ -#include -#include -#include - #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Format/3mf.hpp" #include "../GUI/GUI.hpp" +#include "../GUI/I18N.hpp" #include "../GUI/PresetBundle.hpp" #include @@ -347,8 +350,9 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); path_src += ".3mf"; Model model; + DynamicPrintConfig config; model.add_object(model_object); - if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast(&print), false)) { + if (! Slic3r::store_3mf(path_src.string().c_str(), &model, &config)) { boost::filesystem::remove(path_src); throw std::runtime_error(L("Export of a temporary 3mf file failed")); } @@ -359,16 +363,17 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, [&canceled]() { if (canceled) throw RepairCanceledException(); }); boost::filesystem::remove(path_src); - PresetBundle bundle; + // PresetBundle bundle; on_progress(L("Loading the repaired model"), 80); - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result); - boost::filesystem::remove(path_dst); + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &result); + result.objects[0]->name = boost::filesystem::path(model_object.name).filename().stem().string() + "_fixed"; + boost::filesystem::remove(path_dst); if (! loaded) throw std::runtime_error(L("Import of the repaired 3mf file failed")); success = true; finished = true; on_progress(L("Model repair finished"), 100); - } catch (RepairCanceledException &ex) { + } catch (RepairCanceledException & /* ex */) { canceled = true; finished = true; on_progress(L("Model repair canceled"), 100); diff --git a/src/slic3r/Utils/HexFile.cpp b/src/slic3r/Utils/HexFile.cpp index 282c647bd..9e0803325 100644 --- a/src/slic3r/Utils/HexFile.cpp +++ b/src/slic3r/Utils/HexFile.cpp @@ -76,7 +76,7 @@ HexFile::HexFile(fs::path path) : pt::ptree ptree; try { pt::read_ini(header_ini, ptree); - } catch (std::exception &e) { + } catch (std::exception & /* e */) { return; } diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 9b67ceea8..67c24f3f4 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -10,7 +10,7 @@ #include -#include "../../libslic3r/libslic3r.h" +#include "libslic3r/libslic3r.h" namespace fs = boost::filesystem; diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index db86d7697..b2e2d4d45 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -1,13 +1,15 @@ #include "OctoPrint.hpp" -#include "PrintHostSendDialog.hpp" #include #include #include #include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX #include "Http.hpp" + namespace fs = boost::filesystem; @@ -64,10 +66,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the OctoPrint server")); fs::path filepath(filename); - PrintHostSendDialog send_dialog(filepath.filename(), true); + PrintHostSendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } - const bool print = send_dialog.print(); + const bool print = send_dialog.start_print(); const auto upload_filepath = send_dialog.filename(); const auto upload_filename = upload_filepath.filename(); const auto upload_parent_path = upload_filepath.parent_path(); @@ -99,7 +101,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const auto http = Http::post(std::move(url)); set_auth(http); http.form_add("print", print ? "true" : "false") - .form_add("path", upload_parent_path.string()) + .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? .form_add_file("file", filename, upload_filename.string()) .on_complete([&](std::string body, unsigned status) { BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; @@ -127,6 +129,11 @@ bool OctoPrint::send_gcode(const std::string &filename) const return res; } +bool OctoPrint::upload(PrintHostUpload upload_data) const +{ + throw "unimplemented"; +} + bool OctoPrint::has_auto_discovery() const { return true; diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index f6c4d58c8..314e4cfae 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -24,6 +24,7 @@ public: wxString get_test_failed_msg (wxString &msg) const; // Send gcode file to octoprint, filename is expected to be in UTF-8 bool send_gcode(const std::string &filename) const; + bool upload(PrintHostUpload upload_data) const; bool has_auto_discovery() const; bool can_test() const; private: diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 2e423dc5e..924cf382d 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -18,9 +18,11 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" #include "slic3r/GUI/ConfigWizard.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Config/Version.hpp" #include "slic3r/Config/Snapshot.hpp" @@ -90,7 +92,6 @@ struct Updates struct PresetUpdater::priv { - int version_online_event; std::vector index_db; bool enabled_version_check; @@ -105,7 +106,7 @@ struct PresetUpdater::priv bool cancel; std::thread thread; - priv(int version_online_event); + priv(); void set_download_prefs(AppConfig *app_config); bool get_file(const std::string &url, const fs::path &target_path) const; @@ -120,15 +121,14 @@ struct PresetUpdater::priv static void copy_file(const fs::path &from, const fs::path &to); }; -PresetUpdater::priv::priv(int version_online_event) : - version_online_event(version_online_event), +PresetUpdater::priv::priv() : had_config_update(false), cache_path(fs::path(Slic3r::data_dir()) / "cache"), rsrc_path(fs::path(resources_dir()) / "profiles"), vendor_path(fs::path(Slic3r::data_dir()) / "vendor"), cancel(false) { - set_download_prefs(GUI::get_app_config()); + set_download_prefs(GUI::wxGetApp().app_config); check_install_indices(); index_db = std::move(Index::load_db()); } @@ -209,9 +209,11 @@ void PresetUpdater::priv::sync_version() const .on_complete([&](std::string body, unsigned /* http_status */) { boost::trim(body); BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body; - wxCommandEvent* evt = new wxCommandEvent(version_online_event); - evt->SetString(body); - GUI::get_app()->QueueEvent(evt); +// wxCommandEvent* evt = new wxCommandEvent(version_online_event); +// evt->SetString(body); +// GUI::get_app()->QueueEvent(evt); + GUI::wxGetApp().app_config->set("version_online", body); + GUI::wxGetApp().app_config->save(); }) .perform_sync(); } @@ -395,7 +397,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons if (updates.incompats.size() > 0) { if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; - SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE); + SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE); } BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size(); @@ -408,7 +410,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons else if (updates.updates.size() > 0) { if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; - SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE); + SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE); } BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size(); @@ -447,7 +449,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); } for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); } - for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); } + for (const auto &name : bundle.obsolete_presets.sla_prints) { obsolete_remover("sla_print", name); } + for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } } } @@ -466,8 +469,8 @@ void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &targ } -PresetUpdater::PresetUpdater(int version_online_event) : - p(new priv(version_online_event)) +PresetUpdater::PresetUpdater() : + p(new priv()) {} @@ -485,7 +488,7 @@ PresetUpdater::~PresetUpdater() void PresetUpdater::sync(PresetBundle *preset_bundle) { - p->set_download_prefs(GUI::get_app_config()); + p->set_download_prefs(GUI::wxGetApp().app_config); if (!p->enabled_version_check && !p->enabled_config_update) { return; } // Copy the whole vendors data for use in the background thread @@ -509,7 +512,7 @@ void PresetUpdater::slic3r_update_notify() return; } - auto* app_config = GUI::get_app_config(); + auto* app_config = GUI::wxGetApp().app_config; const auto ver_slic3r = Semver::parse(SLIC3R_VERSION); const auto ver_online_str = app_config->get("version_online"); const auto ver_online = Semver::parse(ver_online_str); @@ -569,10 +572,10 @@ bool PresetUpdater::config_update() const BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; p->perform_updates(std::move(updates)); GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); - if (! wizard.run(GUI::get_preset_bundle(), this)) { + if (! wizard.run(GUI::wxGetApp().preset_bundle, this)) { return false; } - GUI::load_current_presets(); + GUI::wxGetApp().load_current_presets(); } else { BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye..."; return false; @@ -601,9 +604,9 @@ bool PresetUpdater::config_update() const p->perform_updates(std::move(updates)); // Reload global configuration - auto *app_config = GUI::get_app_config(); - GUI::get_preset_bundle()->load_presets(*app_config); - GUI::load_current_presets(); + auto *app_config = GUI::wxGetApp().app_config; + GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().load_current_presets(); } else { BOOST_LOG_TRIVIAL(info) << "User refused the update"; } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 6a53cca81..451e8b2cf 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -13,7 +13,7 @@ class PresetBundle; class PresetUpdater { public: - PresetUpdater(int version_online_event); + PresetUpdater(); PresetUpdater(PresetUpdater &&) = delete; PresetUpdater(const PresetUpdater &) = delete; PresetUpdater &operator=(PresetUpdater &&) = delete; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index dd72bae40..570d72f68 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -1,7 +1,15 @@ #include "OctoPrint.hpp" #include "Duet.hpp" +#include +#include +#include + #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Channel.hpp" + +using boost::optional; + namespace Slic3r { @@ -10,13 +18,42 @@ PrintHost::~PrintHost() {} PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) { - PrintHostType kind = config->option>("host_type")->value; - if (kind == htOctoPrint) { - return new OctoPrint(config); - } else if (kind == htDuet) { - return new Duet(config); - } - return nullptr; + PrintHostType kind = config->option>("host_type")->value; + if (kind == htOctoPrint) { + return new OctoPrint(config); + } else if (kind == htDuet) { + return new Duet(config); + } + return nullptr; +} + + +struct PrintHostJobQueue::priv +{ + std::vector jobs; + Channel channel; + + std::thread bg_thread; + optional bg_job; +}; + +PrintHostJobQueue::PrintHostJobQueue() + : p(new priv()) +{ + std::shared_ptr p2 = p; + p->bg_thread = std::thread([p2]() { + // Wait for commands on the channel: + auto cmd = p2->channel.pop(); + // TODO + }); +} + +PrintHostJobQueue::~PrintHostJobQueue() +{ + // TODO: stop the thread + // if (p && p->bg_thread.joinable()) { + // p->bg_thread.detach(); + // } } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index bc828ea46..53f7c43d3 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -3,31 +3,87 @@ #include #include +#include + #include namespace Slic3r { - class DynamicPrintConfig; + +struct PrintHostUpload +{ + boost::filesystem::path source_path; + boost::filesystem::path upload_path; + bool start_print = false; +}; + + class PrintHost { public: - virtual ~PrintHost(); + virtual ~PrintHost(); - virtual bool test(wxString &curl_msg) const = 0; - virtual wxString get_test_ok_msg () const = 0; - virtual wxString get_test_failed_msg (wxString &msg) const = 0; - // Send gcode file to print host, filename is expected to be in UTF-8 - virtual bool send_gcode(const std::string &filename) const = 0; - virtual bool has_auto_discovery() const = 0; - virtual bool can_test() const = 0; + virtual bool test(wxString &curl_msg) const = 0; + virtual wxString get_test_ok_msg () const = 0; + virtual wxString get_test_failed_msg (wxString &msg) const = 0; + // Send gcode file to print host, filename is expected to be in UTF-8 + virtual bool send_gcode(const std::string &filename) const = 0; // XXX: remove in favor of upload() + virtual bool upload(PrintHostUpload upload_data) const = 0; + virtual bool has_auto_discovery() const = 0; + virtual bool can_test() const = 0; - static PrintHost* get_print_host(DynamicPrintConfig *config); + static PrintHost* get_print_host(DynamicPrintConfig *config); }; +struct PrintHostJob +{ + PrintHostUpload upload_data; + std::unique_ptr printhost; + + PrintHostJob() {} + PrintHostJob(const PrintHostJob&) = delete; + PrintHostJob(PrintHostJob &&other) + : upload_data(std::move(other.upload_data)) + , printhost(std::move(other.printhost)) + {} + + PrintHostJob(DynamicPrintConfig *config) + : printhost(PrintHost::get_print_host(config)) + {} + + PrintHostJob& operator=(const PrintHostJob&) = delete; + PrintHostJob& operator=(PrintHostJob &&other) + { + upload_data = std::move(other.upload_data); + printhost = std::move(other.printhost); + return *this; + } + + bool empty() const { return !printhost; } + operator bool() const { return !!printhost; } +}; + + +class PrintHostJobQueue +{ +public: + PrintHostJobQueue(); + PrintHostJobQueue(const PrintHostJobQueue &) = delete; + PrintHostJobQueue(PrintHostJobQueue &&other) = delete; + ~PrintHostJobQueue(); + + PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete; + PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete; + +private: + struct priv; + std::shared_ptr p; +}; + } diff --git a/src/slic3r/Utils/PrintHostSendDialog.cpp b/src/slic3r/Utils/PrintHostSendDialog.cpp deleted file mode 100644 index c5d441f87..000000000 --- a/src/slic3r/Utils/PrintHostSendDialog.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "PrintHostSendDialog.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/MsgDialog.hpp" - - -namespace fs = boost::filesystem; - -namespace Slic3r { - -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) : - MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE), - txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())), - box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))), - can_start_print(can_start_print) -{ - auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); - label_dir_hint->Wrap(CONTENT_WIDTH); - - content_sizer->Add(txt_filename, 0, wxEXPAND); - content_sizer->Add(label_dir_hint); - content_sizer->AddSpacer(VERT_SPACING); - content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); - - btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); - - txt_filename->SetFocus(); - wxString stem(path.stem().wstring()); - txt_filename->SetSelection(0, stem.Length()); - - box_print->Enable(can_start_print); - - Fit(); -} - -fs::path PrintHostSendDialog::filename() const -{ - return fs::path(txt_filename->GetValue().wx_str()); -} - -bool PrintHostSendDialog::print() const -{ - return box_print->GetValue(); } -} diff --git a/src/slic3r/Utils/Semver.hpp b/src/slic3r/Utils/Semver.hpp index 736f9b891..2fb4e3f4b 100644 --- a/src/slic3r/Utils/Semver.hpp +++ b/src/slic3r/Utils/Semver.hpp @@ -111,8 +111,8 @@ public: bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; } // We're using '&' instead of the '~' operator here as '~' is unary-only: // Satisfies patch if Major and minor are equal. - bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); } - bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); } + bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver) != 0; } + bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver) != 0; } bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; } // Conversion diff --git a/src/slic3r/pchheader.cpp b/src/slic3r/pchheader.cpp new file mode 100644 index 000000000..9ab59c53d --- /dev/null +++ b/src/slic3r/pchheader.cpp @@ -0,0 +1 @@ +#include "pchheader.hpp" diff --git a/src/slic3r/pchheader.hpp b/src/slic3r/pchheader.hpp new file mode 100644 index 000000000..3b321d960 --- /dev/null +++ b/src/slic3r/pchheader.hpp @@ -0,0 +1,185 @@ +#ifdef WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #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 +#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 +#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 + +#ifdef _MSC_VER + // avoid some "macro redefinition" warnings + #include +#endif /* _MSC_VER */ + +#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 +#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 +#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 +#include +#include +#include +#include +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/libslic3r.h" diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp new file mode 100644 index 000000000..9823a325d --- /dev/null +++ b/src/slic3r_app_msvc.cpp @@ -0,0 +1,253 @@ +// Why? +#define _WIN32_WINNT 0x0502 +// The standard Windows includes. +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +// Let the NVIDIA and AMD know we want to use their graphics card +// on a dual graphics card system. +__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + +#include +#include +#include + +#include +#include + +#include +#include + +class OpenGLVersionCheck +{ +public: + std::string version; + std::string glsl_version; + std::string vendor; + std::string renderer; + + HINSTANCE hOpenGL = nullptr; + bool success = false; + + bool load_opengl_dll() + { + MSG msg = {0}; + WNDCLASS wc = {0}; + wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; + wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); + wc.lpszClassName = L"slic3r_opengl_version_check"; + wc.style = CS_OWNDC; + if (RegisterClass(&wc)) { + HWND hwnd = CreateWindowW(wc.lpszClassName, L"slic3r_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); + if (hwnd) { + this->message_pump_exit = false; + while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! this->message_pump_exit) + DispatchMessage(&msg); + } + } + return this->success; + } + + void unload_opengl_dll() + { + if (this->hOpenGL) { + FreeLibrary(this->hOpenGL); + this->hOpenGL = nullptr; + } + } + + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const + { + std::vector tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + if (tokens.empty()) + return false; + + std::vector numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; + } + +protected: + bool message_pump_exit = false; + + void check(HWND hWnd) + { + hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); + if (hOpenGL == nullptr) { + printf("Failed loading the system opengl32.dll\n"); + return; + } + + typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); + typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); + typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); + typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); + + Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); + Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); + Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); + Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); + + if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { + printf("Failed loading the system opengl32.dll: The library is invalid.\n"); + return; + } + + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette. + 32, // Color depth of the framebuffer. + 0, 0, 0, 0, 0, 0, + 0, + 0, + 0, + 0, 0, 0, 0, + 24, // Number of bits for the depthbuffer + 8, // Number of bits for the stencilbuffer + 0, // Number of Aux buffers in the framebuffer. + PFD_MAIN_PLANE, + 0, + 0, 0, 0 + }; + + HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd); + // Gdi32.dll + int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); + // Gdi32.dll + SetPixelFormat(ourWindowHandleToDeviceContext,letWindowsChooseThisPixelFormat, &pfd); + // Opengl32.dll + HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext); + wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext); + // Opengl32.dll + const char *data = (const char*)glGetString(GL_VERSION); + if (data != nullptr) + this->version = data; + data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION + if (data != nullptr) + this->glsl_version = data; + data = (const char*)glGetString(GL_VENDOR); + if (data != nullptr) + this->vendor = data; + data = (const char*)glGetString(GL_RENDERER); + if (data != nullptr) + this->renderer = data; + // Opengl32.dll + wglDeleteContext(glcontext); + this->success = true; + } + + static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + switch(message) + { + case WM_CREATE: + { + CREATESTRUCT *pCreate = reinterpret_cast(lParam); + OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); + ogl_data->check(hWnd); + DestroyWindow(hWnd); + ogl_data->message_pump_exit = true; + return 0; + } + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } +}; + +extern "C" { + typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); + Slic3rMainFunc slic3r_main = nullptr; +} + +#ifdef SLIC3R_WRAPPER_NOCONSOLE +int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) +{ + int argc; + wchar_t **argv = CommandLineToArgvW(lpCmdLine, &argc); +#else +int wmain(int argc, wchar_t **argv) +{ +#endif + + OpenGLVersionCheck opengl_version_check; + bool load_mesa = ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + + wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); + wchar_t drive[_MAX_DRIVE]; + wchar_t dir[_MAX_DIR]; + wchar_t fname[_MAX_FNAME]; + wchar_t ext[_MAX_EXT]; + _wsplitpath(path_to_exe, drive, dir, fname, ext); + _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); + +// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows +// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ + if (load_mesa) { + opengl_version_check.unload_opengl_dll(); + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) { + printf("MESA OpenGL library was not loaded\n"); + } + } + + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_slic3r, path_to_exe); + wcscat(path_to_slic3r, L"slic3r.dll"); +// printf("Loading Slic3r library: %S\n", path_to_slic3r); + HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); + if (hInstance_Slic3r == nullptr) { + printf("slic3r.dll was not loaded\n"); + return -1; + } + + // resolve function address here + slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, +#ifdef _WIN64 + // there is just a single calling conversion, therefore no mangling of the function name. + "slic3r_main" +#else // stdcall calling convention declaration + "_slic3r_main@8" +#endif + ); + if (slic3r_main == nullptr) { + printf("could not locate the function slic3r_main in slic3r.dll\n"); + return -1; + } + + std::vector argv_extended; + argv_extended.emplace_back(argv[0]); +#ifdef SLIC3R_WRAPPER_GUI + std::wstring cmd_gui = L"--gui"; + argv_extended.emplace_back(const_cast(cmd_gui.data())); +#endif + for (int i = 1; i < argc; ++ i) + argv_extended.emplace_back(argv[i]); + argv_extended.emplace_back(nullptr); + return slic3r_main(argc, argv_extended.data()); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..11bdc4b3d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +# TODO Add individual tests as executables in separate directories + +# add_subirectory() \ No newline at end of file diff --git a/utils/amf-to-stl.pl b/utils/amf-to-stl.pl deleted file mode 100755 index 802fd9a53..000000000 --- a/utils/amf-to-stl.pl +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/perl -# This script converts an AMF file to STL - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use File::Basename qw(basename); -use Getopt::Long qw(:config no_auto_abbrev); -use Slic3r; -$|++; - -# Convert all parameters from the local code page to utf8 on Windows. -@ARGV = map Slic3r::decode_path($_), @ARGV if $^O eq 'MSWin32'; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'ascii' => \$opt{ascii}, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -{ - my $model = Slic3r::Model->load_amf($ARGV[0]); - my $output_file = $ARGV[0]; - $output_file =~ s/\.[aA][mM][fF](?:\.[xX][mM][lL])?$/\.stl/; - - printf "Writing to %s\n", basename($output_file); - $model->store_stl($output_file, binary => !$opt{ascii}); -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: amf-to-stl.pl [ OPTIONS ] file.amf - - --help Output this usage screen and exit - --ascii Generate ASCII STL files (default: binary) - -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl deleted file mode 100644 index eee3c73dc..000000000 --- a/utils/dump-stl.pl +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/perl -# This script dumps a STL file into Perl syntax for writing tests -# or dumps a test model into a STL file - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; -use File::Basename qw(basename); -$|++; - -$ARGV[0] or usage(1); - -if (-e $ARGV[0]) { - my $model = Slic3r::Model->load_stl($ARGV[0], basename($ARGV[0])); - $model->objects->[0]->add_instance(offset => Slic3r::Pointf->new(0,0)); - my $mesh = $model->mesh; - $mesh->repair; - printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; - printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; - exit 0; -} elsif ((my $model = Slic3r::Test::model($ARGV[0]))) { - $ARGV[1] or die "Missing writeable destination as second argument\n"; - $model->store_stl($ARGV[1], 1); - printf "Model $ARGV[0] written to $ARGV[1]\n"; - exit 0; -} else { - die "No such model exists\n"; -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: dump-stl.pl file.stl - dump-stl.pl modelname file.stl -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/modifier_helpers/layer_generator.jscad b/utils/modifier_helpers/layer_generator.jscad deleted file mode 100644 index fc193a53f..000000000 --- a/utils/modifier_helpers/layer_generator.jscad +++ /dev/null @@ -1,19 +0,0 @@ -// title: Layer_generator -// written by: Joseph Lenox -// Used for generating cubes oriented about the center -// for making simple modifier meshes. - -var width = 100; -var layer_height = 0.3; -var z = 30; -function main() { - - return cube(size=[width,width,layer_height], center=true).translate([0,0,z]); -} -function getParameterDefinitions() { - return [ - { name: 'width', type: 'float', initial: 100, caption: "Width of the cube:" }, - { name: 'layer_height', type: 'float', initial: 0.3, caption: "Layer height used:" }, - { name: 'z', type: 'float', initial: 0, caption: "Z:" } - ]; -} diff --git a/utils/modifier_helpers/solid_layers.scad b/utils/modifier_helpers/solid_layers.scad deleted file mode 100644 index 378294949..000000000 --- a/utils/modifier_helpers/solid_layers.scad +++ /dev/null @@ -1,24 +0,0 @@ -// Used to generate a modifier mesh to do something every few layers. -// Load into OpenSCAD, tweak the variables below, export as STL and load as -// a modifier mesh. Then change settings for the modifier mesh. - -// Written by Joseph Lenox; in public domain. - -layer_height = 0.3; // set to layer height in slic3r for "best" results. -number_of_solid_layers = 2; -N = 4; // N > number_of_solid_layers or else the whole thing will be solid -model_height = 300.0; -model_width = 300.0; // these two should be at least as big as the model -model_depth = 300.0; // but bigger isn't a problem -initial_offset=0; // don't generate below this - -position_on_bed=[0,0,0]; // in case you need to move it around - -// don't touch below unless you know what you are doing. -simple_layers = round(model_height/layer_height); -translate(position_on_bed) - for (i = [initial_offset:N:simple_layers]) { - translate([0,0,i*layer_height]) - translate([0,0,(layer_height*number_of_solid_layers)/2]) - cube([model_width,model_depth,layer_height*number_of_solid_layers], center=true); - } diff --git a/utils/pdf-slices.pl b/utils/pdf-slices.pl deleted file mode 100755 index ca61da08e..000000000 --- a/utils/pdf-slices.pl +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/perl -# This script exports model slices to a PDF file as solid fills, one per page - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Getopt::Long qw(:config no_auto_abbrev); -use PDF::API2; -use Slic3r; -use Slic3r::Geometry qw(scale unscale X Y); - -use constant mm => 25.4 / 72; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'output|o=s' => \$opt{output_file}, - 'layer-height|h=f' => \$opt{layer_height}, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -{ - # prepare config - my $config = Slic3r::Config->new; - $config->set('layer_height', $opt{layer_height}) if $opt{layer_height}; - - # read model - my $model = Slic3r::Model->read_from_file(my $input_file = $ARGV[0]); - - # init print object - my $sprint = Slic3r::Print::Simple->new( - print_center => [0,0], - ); - $sprint->apply_config($config); - $sprint->set_model($model); - my $print = $sprint->_print; - - # compute sizes - my $bb = $print->bounding_box; - my $size = $bb->size; - my $mediabox = [ map unscale($_)/mm, @{$size} ]; - - # init PDF - my $pdf = PDF::API2->new(); - my $color = $pdf->colorspace_separation('RDG_GLOSS', 'darkblue'); - - # slice and build output geometry - $_->slice for @{$print->objects}; - foreach my $object (@{ $print->objects }) { - my $shift = $object->_shifted_copies->[0]; - $shift->translate(map $_/2, @$size); - - foreach my $layer (@{ $object->layers }) { - my $page = $pdf->page(); - $page->mediabox(@$mediabox); - my $content = $page->gfx; - $content->fillcolor($color, 1); - - foreach my $expolygon (@{$layer->slices}) { - $expolygon = $expolygon->clone; - $expolygon->translate(@$shift); - $content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @{$expolygon->contour}); #) - $content->close; - foreach my $hole (@{$expolygon->holes}) { - $content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @$hole); #) - $content->close; - } - $content->fill; # non-zero by default - } - } - } - - # write output file - my $output_file = $opt{output_file}; - if (!defined $output_file) { - $output_file = $input_file; - $output_file =~ s/\.(?:[sS][tT][lL])$/.pdf/; - } - $pdf->saveas($output_file); - printf "PDF file written to %s\n", $output_file; -} - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: pdf-slices.pl [ OPTIONS ] file.stl - - --help Output this usage screen and exit - --output, -o Write to the specified file - --layer-height, -h Use the specified layer height - -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/post-processing/decimate.pl b/utils/post-processing/decimate.pl deleted file mode 100755 index 9e2938c5f..000000000 --- a/utils/post-processing/decimate.pl +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/perl -i~ - -use strict; -use warnings; - -my %lastpos = (X => 10000, Y => 10000, Z => 10000, E => 10000, F => 10000); -my %pos = (X => 0, Y => 0, Z => 0, E => 0, F => 0); - -my $mindist = 0.33; - -my $mindistz = 0.005; - -my $mindistsq = $mindist * $mindist; - -sub dist { - my $sq = 0; - for (qw/X Y Z E/) { - $sq += ($pos{$_} - $lastpos{$_}) ** 2; - } - return $sq; -} - -while (<>) { - if (m#\bG[01]\b#) { - while (m#([XYZEF])(\d+(\.\d+)?)#gi) { - $pos{uc $1} = $2; - } - if ( - ( - /X/ && - /Y/ && - (dist() >= $mindistsq) - ) || - (abs($pos{Z} - $lastpos{Z}) > $mindistz) || - (!/X/ || !/Y/) - ) { - print; - %lastpos = %pos; - } - elsif (($pos{F} - $lastpos{F}) != 0) { - printf "G1 F%s\n", $pos{F}; - $lastpos{F} = $pos{F}; - } - } - else { - if (m#\bG92\b#) { - while (m#([XYZEF])(\d+(\.\d+)?)#gi) { - $lastpos{uc $1} = $2; - } - } - print; - } -} diff --git a/utils/post-processing/fan_kickstart.py b/utils/post-processing/fan_kickstart.py deleted file mode 100644 index 9ee1bc0a4..000000000 --- a/utils/post-processing/fan_kickstart.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python -import sys -import re - -sea = re.compile("M106 S[1-9]+[0-9]*") -rep = re.compile("M106 S255\n\g<0>") -out = open(sys.argv[1]+"_fixed", 'w') - with open(sys.argv[1]) as f: - for r in f: - if re.search(sea, r) is not None: - out.write(re.sub(sea,"M106 S255\n\g<0>",r)) - else: - out.write(r) diff --git a/utils/post-processing/filament-weight.pl b/utils/post-processing/filament-weight.pl deleted file mode 100755 index 5ed836461..000000000 --- a/utils/post-processing/filament-weight.pl +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/perl -i -# -# Post-processing script for adding weight and cost of required -# filament to G-code output. - -use strict; -use warnings; - -# example densities, adjust according to filament specifications -use constant PLA_P => 1.25; # g/cm3 -use constant ABS_P => 1.05; # g/cm3 - -# example costs, adjust according to filament prices -use constant PLA_PRICE => 0.05; # EUR/g -use constant ABS_PRICE => 0.02; # EUR/g -use constant CURRENCY => "EUR"; - -while (<>) { - if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) { - my $pla_weight = $2 * PLA_P; - my $abs_weight = $2 * ABS_P; - - my $pla_costs = $pla_weight * PLA_PRICE; - my $abs_costs = $abs_weight * ABS_PRICE; - - printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla_weight, $abs_weight; - printf "; costs = %s %.2f (PLA), %s %.2f (ABS)\n", CURRENCY, $pla_costs, CURRENCY, $abs_costs; - } else { - print; - } -} diff --git a/utils/post-processing/flowrate.pl b/utils/post-processing/flowrate.pl deleted file mode 100755 index f29d2312d..000000000 --- a/utils/post-processing/flowrate.pl +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/perl -i - -# -# Post-processing script for calculating flow rate for each move - -use strict; -use warnings; - -use constant PI => 3.141592653589793238; -my @filament_diameter = split /,/, $ENV{SLIC3R_FILAMENT_DIAMETER}; - -my $E = 0; -my $T = 0; -my ($X, $Y, $F); -while (<>) { - if (/^G1.*? F([0-9.]+)/) { - $F = $1; - } - if (/^G1 X([0-9.]+) Y([0-9.]+).*? E([0-9.]+)/) { - my ($x, $y, $e) = ($1, $2, $3); - my $e_length = $e - $E; - if ($e_length > 0 && defined $X && defined $Y) { - my $dist = sqrt( (($x-$X)**2) + (($y-$Y)**2) ); - if ($dist > 0) { - my $mm_per_mm = $e_length / $dist; # dE/dXY - my $mm3_per_mm = ($filament_diameter[$T] ** 2) * PI/4 * $mm_per_mm; - my $vol_speed = $F/60 * $mm3_per_mm; - my $comment = sprintf ' ; dXY = %.3fmm ; dE = %.5fmm ; dE/XY = %.5fmm/mm; volspeed = %.5fmm\x{00B3}/sec', - $dist, $e_length, $mm_per_mm, $vol_speed; - s/(\R+)/$comment$1/; - } - } - $E = $e; - $X = $x; - $Y = $y; - } - if (/^G1 X([0-9.]+) Y([0-9.]+)/) { - $X = $1; - $Y = $2; - } - if (/^G1.*? E([0-9.]+)/) { - $E = $1; - } - if (/^G92 E0/) { - $E = 0; - } - if (/^T(\d+)/) { - $T = $1; - } - print; -} - -__END__ diff --git a/utils/post-processing/prowl-notification.pl b/utils/post-processing/prowl-notification.pl deleted file mode 100755 index 5a5e1ca53..000000000 --- a/utils/post-processing/prowl-notification.pl +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/perl -# -# Example post-processing script for sending a Prowl notification upon -# completion. See http://www.prowlapp.com/ for more info. - -use strict; -use warnings; - -use File::Basename qw(basename); -use WebService::Prowl; - -# set your Prowl API key here -my $apikey = ''; - -my $file = basename $ARGV[0]; -my $prowl = WebService::Prowl->new(apikey => $apikey); -my %options = (application => 'Slic3r', - event =>'Slicing Done!', - description => "$file was successfully generated"); -printf STDERR "Error sending Prowl notification: %s\n", $prowl->error - unless $prowl->add(%options); diff --git a/utils/post-processing/z-every-line.pl b/utils/post-processing/z-every-line.pl deleted file mode 100755 index aaf57e172..000000000 --- a/utils/post-processing/z-every-line.pl +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/perl -i - -use strict; -use warnings; - -my $z = 0; - -# read stdin and any/all files passed as parameters one line at a time -while (<>) { - # if we find a Z word, save it - $z = $1 if /Z\s*(\d+(\.\d+)?)/; - - # if we don't have Z, but we do have X and Y - if (!/Z/ && /X/ && /Y/ && $z > 0) { - # chop off the end of the line (incl. comments), saving chopped section in $1 - s/\s*([\r\n\;\(].*)/" Z$z $1"/es; - # print start of line, insert our Z value then re-add the chopped end of line - # print "$_ Z$z $1"; - } - #else { - # nothing interesting, print line as-is - print or die $!; - #} -} diff --git a/utils/send-gcode.pl b/utils/send-gcode.pl deleted file mode 100644 index 0b803baa6..000000000 --- a/utils/send-gcode.pl +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; - -die "Usage: send-gcode.pl SERIALPORT BAUDRATE GCODE_FILE\n" - if @ARGV != 3; - -my $serial = Slic3r::GCode::Sender->new($ARGV[0], $ARGV[1]); -1 until $serial->is_connected; -print "Connected to printer\n"; - -{ - local $/ = "\n"; - Slic3r::open(\my $fh, '<', $ARGV[2]) - or die "Unable to open $ARGV[2]: $!\n"; - binmode $fh, ':utf8'; - while (<$fh>) { - $serial->send($_); - } - close $fh; -} - -while ((my $queue_size = $serial->queue_size) > 0) { - printf "Queue size: %d\n", $queue_size; -} -$serial->disconnect; - -__END__ diff --git a/utils/split_stl.pl b/utils/split_stl.pl deleted file mode 100755 index 56217de4b..000000000 --- a/utils/split_stl.pl +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/perl -# This script splits a STL plate into individual files - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use File::Basename qw(basename); -use Getopt::Long qw(:config no_auto_abbrev); -use Slic3r; -$|++; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'ascii' => \$opt{ascii}, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -{ - my $model = Slic3r::Model->load_stl($ARGV[0], basename($ARGV[0])); - my $basename = $ARGV[0]; - $basename =~ s/\.[sS][tT][lL]$//; - - my $part_count = 0; - my $mesh = $model->objects->[0]->volumes->[0]->mesh; - foreach my $new_mesh (@{$mesh->split}) { - $new_mesh->repair; - - my $new_model = Slic3r::Model->new; - $new_model - ->add_object() - ->add_volume(mesh => $new_mesh); - - $new_model->add_default_instances; - - my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count; - printf "Writing to %s\n", basename($output_file); - $new_model->store_stl($output_file, !$opt{ascii}); - } -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: split_stl.pl [ OPTIONS ] file.stl - - --help Output this usage screen and exit - --ascii Generate ASCII STL files (default: binary) - -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl deleted file mode 100755 index bb88b2161..000000000 --- a/utils/stl-to-amf.pl +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/perl -# This script converts a STL file to AMF - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use File::Basename qw(basename); -use Getopt::Long qw(:config no_auto_abbrev); -use Slic3r; -$|++; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'distinct-materials' => \$opt{distinct_materials}, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -{ - my @models = map Slic3r::Model->load_stl($_, basename($_)), @ARGV; - my $output_file = $ARGV[0]; - $output_file =~ s/\.[sS][tT][lL]$/.amf.xml/; - - my $new_model = Slic3r::Model->new; - - if ($opt{distinct_materials} && @models > 1) { - my $new_object = $new_model->add_object; - for my $m (0 .. $#models) { - my $model = $models[$m]; - $new_model->set_material($m, { Name => basename($ARGV[$m]) }); - $new_object->add_volume( - material_id => $m, - facets => $model->objects->[0]->volumes->[0]->facets, - vertices => $model->objects->[0]->vertices, - ); - } - } else { - foreach my $model (@models) { - $new_model->add_object( - vertices => $model->objects->[0]->vertices, - )->add_volume( - facets => $model->objects->[0]->volumes->[0]->facets, - ); - } - } - - printf "Writing to %s\n", basename($output_file); - $new_model->store_amf($output_file); -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] - - --help Output this usage screen and exit - --distinct-materials Assign each STL file to a different material - -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/zsh/README.markdown b/utils/zsh/README.markdown deleted file mode 100644 index e84b94310..000000000 --- a/utils/zsh/README.markdown +++ /dev/null @@ -1,21 +0,0 @@ -# ZSH Completions for Slic3r - -To enable zsh(1) completions for Slic3r, add the following to your -``~/.zshrc`` file, replacing ``/path/to/Slic3r/`` with the actual path -to your Slic3r directory: - - typeset -U fpath - - if [[ -d /path/to/Slic3r/utils/zsh/functions ]]; then - fpath=(/path/to/Slic3r/utils/zsh/functions $fpath) - fi - - autoload -Uz compinit - compinit - zstyle ':completion:*' verbose true - zstyle ':completion:*:descriptions' format '%B%d%b' - zstyle ':completion:*:messages' format '%d' - zstyle ':completion:*:warnings' format 'No matches for %d' - zstyle ':completion:*' group-name '%d' - -See the zshcompsys(1) man page for further details. diff --git a/utils/zsh/functions/_slic3r b/utils/zsh/functions/_slic3r deleted file mode 100644 index cea887cc6..000000000 --- a/utils/zsh/functions/_slic3r +++ /dev/null @@ -1,118 +0,0 @@ -#compdef -P slic3r(|.pl|.exe) -# -# Slic3r completions configuration for zsh(1). - -# Currently undocumented options: -# --debug, --gui, --ignore-nonexistent-config -# --acceleration, --perimeter-acceleration, --infill-acceleration - -_arguments -S \ - '(- *)--help[output usage screen and exit]' \ - '(- *)--version[output the version of Slic3r and exit]' \ - '--save[save configuration to file]:config output file:_files -g "*.(#i)ini(-.)"' \ - '*--load[load configuration from file]:config input file:_files -g "*.(#i)ini(-.)"' \ - '(--output -o)'{--output,-o}'[specify output file]:output file:_files -g "*.(#i)(gcode|svg)(-.)"' \ - '(--threads -j)'{--threads,-j}'[specify number of threads to use]:number of threads' \ - \ - '--output-filename-format[specify output filename format]:output filename format' \ - '*--post-process[specify post-processing script]:post-processing script file:_files' \ - '--export-svg[export SVG containing slices instead of G-code]' \ - '(--merge -m)'{--merge,-m}'[merge multiple input files into a single print]' \ - \ - '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ - '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ - '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ - '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup repetier makerware sailfish mach3 machinekit smoothie no-extrusion)' \ - '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ - '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ - '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ - '(--gcode-comments --no-gcode-comments)'--{no-,}gcode-comments'[disable/enable verbose G-code comments]' \ - \ - '*--filament-diameter[specify raw filament diameter]:raw filament diameter in mm' \ - '*--extrusion-multiplier[specify multiplier for amount of plastic extruded]:extrusion multiplier' \ - '*--temperature[specify extrusion temperature]:extrusion temperature in Celsius' \ - '*--first-layer-temperature[specify extrusion temperature for the first layer]:first layer extrusion temperature in Celsius' \ - '--bed-temperature[specify heated bed temperature]:heated bed temperature in Celsius' \ - '--first-layer-bed-temperature[specify heated bed temperature for the first layer]:first layer heated bed temperature in Celsius' \ - \ - '--perimeter-extruder[specify extruder to use for printing perimeters]:extruder number' \ - '--infill-extruder[specify extruder to use for printing infill]:extruder number' \ - '--support-material-extruder[specify extruder to use for printing support material]:extruder number' \ - \ - '--travel-speed[specify speed of non-print moves]:speed of non-print moves in mm/s' \ - '--perimeter-speed[specify speed of print moves for perimeters]:speed of print moves for perimeters in mm/s' \ - '--external-perimeter-speed[specify speed of print moves for external perimeters]:speed of print moves for external perimeters in mm/s or % of --perimeter-speed' \ - '--small-perimeter-speed[specify speed of print moves for small perimeters]:speed of print moves for small perimeters in mm/s or % of --perimeter-speed' \ - '--infill-speed[specify speed of infill print moves]:speed of infill print moves in mm/s' \ - '--solid-infill-speed[specify speed of solid surface print moves]:speed of solid surface print moves in mm/s or % of --infill-speed' \ - '--top-solid-infill-speed[specify speed of top surface print moves]:speed of top surface print moves in mm/s or % of --solid-infill-speed' \ - '--bridge-speed[specify speed of bridge print moves]:speed of bridge print moves in mm/s' \ - '--first-layer-speed[specify speed of bottom layer print moves]:speed of bottom layer print moves in mm/s or % of normal speeds' \ - \ - '--layer-height[specify layer height]:layer height in mm' \ - '--first-layer-height[specify layer height for bottom layer]:layer height for bottom layer in mm or % of --layer-height' \ - '--infill-every-layers[specify infill for every N layers]:N layers' \ - \ - '--perimeters[specify number of perimeters]:number of perimeters' \ - '--solid-layers[specify number of solid layers to do for top/bottom surfaces]:number of layers for top/bottom surfaces' \ - '--fill-density[specify infill density]:infill density in percent' \ - '--fill-angle[specify infill angle]:infill angle in degrees' \ - '--fill-pattern[specify pattern used for infill]:infill pattern:(rectilinear line concentric honeycomb hilbertcurve archimedeanchords octagramspiral)' \ - '--solid-fill-pattern[specify pattern used for solid layers]:solid fill pattern:(rectilinear concentric hilbertcurve archimedeanchords octagramspiral)' \ - '--start-gcode[load initial G-code from file]:start G-code file:_files -g "*.(#i)(gcode)(-.)"' \ - '--end-gcode[load final G-code from file]:end G-code file:_files -g "*.(#i)(gcode)(-.)"' \ - '--layer-gcode[load layer-change G-code from file]:layer-change G-code file:_files -g "*.(#i)(gcode)(-.)"' \ - '(--support-material --no-support-material)'--{no-,}support-material'[disable/enable generation of support material for overhangs]' \ - '--support-material-threshold[specify support material threshold]:maximum slope angle for generating support material' \ - '--support-material-pattern[specify pattern used for support material]:support material pattern:(rectilinear honeycomb)' \ - '--support-material-spacing[specify spacing between support material lines]:spacing between support material lines in mm' \ - '--support-material-angle[specify support material angle]:support material angle in degrees' \ - '(--randomize-start --no-randomize-start)'--{no-,}randomize-start'[disable/enable randomization of starting point across layers]' \ - '(--extra-perimeters --no-extra-perimeters)'--{no-,}extra-perimeters'[disable/enable generation of extra perimeters when needed]' \ - \ - '--retract-length[specify filament retraction length when pausing extrusion]:filament retraction length in mm' \ - '--retract-speed[specify filament retraction speed]:filament retraction speed in mm/s' \ - '--retract-restart-extra[specify filament length to extrude for compensating retraction]: filament lenght in mm' \ - '--retract-before-travel[specify minimum travel length for activating retraction]:minimum travel length for activating retraction in mm' \ - '--retract-lift[specify Z-axis lift for use when retracting]:Z-axis lift in mm' \ - \ - '(--cooling --no-cooling)'--{no-,}cooling'[disable/enable fan and cooling control]' \ - '--min-fan-speed[specify minimum fan speed]:minimum fan speed in percent' \ - '--max-fan-speed[specify maximum fan speed]:maximum fan speed in percent' \ - '--bridge-fan-speed[specify fan speed to use for bridging]:bridging fan speed in percent' \ - '--fan-below-layer-time[specify maximum layer print time before activating fan]:maximum layer print time in seconds' \ - '--slowdown-below-layer-time[specify maximum layer print time before slowing down printing]:maximum layer print time in seconds' \ - '--min-print-speed[specify minimum print speed]:minimum print speed in mm/s' \ - '--disable-fan-first-layers[specify number of bottom layers to print before activating fan]:number of bottom layers' \ - '(--fan-always-on --no-fan-always-on)'--{no-,}fan-always-on'[disable/enable deactivation of fan]' \ - \ - '--skirts[specify number of skirts]:number of skirts' \ - '--skirt-distance[specify distance between innermost skirt and object]:distance between innermost skirt and object in mm' \ - '--skirt-height[specify number of skirt layers]:number of skirt layers' \ - '--brim-width[specify brim width]:width of brim in mm' \ - \ - '--scale[specify object scaling factor]:object scaling factor in percent' \ - '--rotate[specify object rotation angle]:object rotation angle in degrees' \ - '(--duplicate-grid)--duplicate[specify number of duplicates for auto-arrange]:number of duplicates for auto-arrange' \ - '(--duplicate-grid)--bed-size[specify bed size for auto-arrange]:bed size for auto-arrange in mm,mm' \ - '(--duplicate --bed-size)--duplicate-grid[specify number of duplicates for grid arrangement]:number of duplicates for grid arrangement as x,y' \ - '--duplicate-distance[specify distance between duplicates]:distance between duplicates in mm' \ - \ - '(--complete-objects --no-complete-objects)'--{no-,}complete-objects'[disable/enable completion of each object before starting a new one]' \ - '--extruder-clearance-radius[specify radius above which extruder will not collide with anything]:radius in mm' \ - '--extruder-clearance-height[specify maximum vertical extruder depth]:maximum vertical extruder depth in mm' \ - \ - '--notes[specify notes to be added as comments to the output file]:notes' \ - \ - '--extrusion-width[specify extrusion width]:extrusion width in mm or % of --layer-height' \ - '--first-layer-extrusion-width[specify extrusion width for first layer]:first layer extrusion width in mm or % og --layer-height' \ - '--perimeters-extrusion-width[specify extrusion width for perimeters]:perimeter extrusion width in mm or % of --layer-height' \ - '--infill-extrusion-width[specify extrusion width for infill]:infill extrusion width in mm or % of --layer-height' \ - '--support-material-extrusion-width[specify extrusion width for support material]:support material extrusion width in mm or % of --layer-height' \ - '--bridge-flow-ratio[specify multiplier for extrusion when bridging]:bridge extrusion multiplier' \ - \ - '*:input file:_files -g "*.(#i)(stl|obj|amf|xml|prusa)(-.)"' - -# Local Variables: *** -# mode:sh *** -# End: *** diff --git a/version.inc b/version.inc new file mode 100644 index 000000000..95e2741dd --- /dev/null +++ b/version.inc @@ -0,0 +1,9 @@ +# Included by CMakeLists, edited by the build script +# (the version numbers are generated by the build script from the git current label) + +set(SLIC3R_FORK_NAME "Slic3r Prusa Edition") +set(SLIC3R_VERSION "1.42.0-alpha") +set(SLIC3R_BUILD "${SLIC3R_VERSION}+UNKNOWN") +set(SLIC3R_BUILD_ID "${SLIC3R_BUILD_ID}") +set(SLIC3R_RC_VERSION "1,42,0,0") +set(SLIC3R_RC_VERSION_DOTS "1.42.0.0") diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 92964c900..f14499bf9 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -1,5 +1,30 @@ project(XS) +# Find the Perl interpreter, add local-lib to PATH and PERL5LIB environment variables, +# so the locally installed modules (mainly the Alien::wxPerl) will be reached. +if (WIN32) + set(ENV_PATH_SEPARATOR ";") +else() + set(ENV_PATH_SEPARATOR ":") +endif() +set(ENV{PATH} "${PROJECT_SOURCE_DIR}/local-lib/bin${ENV_PATH_SEPARATOR}$ENV{PATH}") +set(PERL_INCLUDE "${PROJECT_SOURCE_DIR}/local-lib/lib/perl5${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}") +message("PATH: $ENV{PATH}") +message("PERL_INCLUDE: ${PERL_INCLUDE}") +find_package(Perl REQUIRED) +if (WIN32) + # On Windows passing the PERL5LIB variable causes various problems (such as with MAX_PATH and others), + # basically I've found no good way to do it on Windows. + set(PERL5LIB_ENV_CMD "") +else() + set(PERL5LIB_ENV_CMD ${CMAKE_COMMAND} -E env PERL5LIB=${PERL_INCLUDE}) +endif() + +# Perl specific stuff +find_package(PerlLibs REQUIRED) +set(PerlEmbed_DEBUG 1) +find_package(PerlEmbed REQUIRED) + # Generate the Slic3r Perl module (XS) typemap file. set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) add_custom_command( @@ -74,11 +99,7 @@ else() endif() add_library(XS ${XS_SHARED_LIBRARY_TYPE} ${XS_MAIN_CPP} -# ${LIBDIR}/libslic3r/utils.cpp -# ${LIBDIR}/slic3r/GUI/wxPerlIface.cpp src/perlglue.cpp -# src/callback.cpp -# src/callback.hpp src/ppport.h src/xsinit.h xsp/my.map @@ -92,24 +113,17 @@ if(APPLE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() -target_link_libraries(XS libslic3r admesh miniz clipper nowide polypartition poly2tri semver avrdude qhull) -if(SLIC3R_PROFILE) - target_link_libraries(XS Shiny) -endif() +target_link_libraries(XS libslic3r) -target_include_directories(XS PRIVATE src src/libslic3r) # Local include directories +target_include_directories(XS PRIVATE src ${LIBDIR}/libslic3r) target_compile_definitions(XS PRIVATE -DSLIC3RXS) set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so target_link_libraries(XS ${Boost_LIBRARIES}) -# target_link_libraries(XS ${wxWidgets_LIBRARIES}) -# target_link_libraries(XS ${CURL_LIBRARIES}) -# target_link_libraries(XS ${OPENSSL_LIBRARIES}) if (APPLE) -# add_compile_options(-stdlib=libc++) -# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) - target_link_libraries(XS "-framework IOKit" "-framework CoreFoundation" -lc++) + # -liconv: boost links to libiconv by default + target_link_libraries(XS "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) elseif (MSVC) target_link_libraries(XS ) else () @@ -142,14 +156,6 @@ if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() -# Find and configure boost -if(SLIC3R_STATIC) - # Use static boost libraries. - set(Boost_USE_STATIC_LIBS ON) - # Use boost libraries linked statically to the C++ runtime. - # set(Boost_USE_STATIC_RUNTIME ON) -endif() - target_link_libraries(XS ${Boost_LIBRARIES}) target_link_libraries(XS ${TBB_LIBRARIES}) # target_link_libraries(XS ${wxWidgets_LIBRARIES}) @@ -157,7 +163,7 @@ target_link_libraries(XS ${EXPAT_LIBRARIES}) # target_link_libraries(XS ${GLEW_LIBRARIES}) # Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. -set(PERL_LOCAL_LIB_DIR "../local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") +set(PERL_LOCAL_LIB_DIR "../../local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") add_custom_command( TARGET XS POST_BUILD @@ -175,8 +181,6 @@ if(APPLE) ) endif() -target_include_directories(XS PRIVATE src src/libslic3r) - if(SLIC3R_PROFILE) target_link_libraries(Shiny) endif() @@ -194,3 +198,14 @@ endif() # Installation install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS) install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r) + +# Unit / integration tests +enable_testing() +get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) +if (MSVC) + set(PERL_PROVE "${PERL_BIN_PATH}/prove.bat") +else () + set(PERL_PROVE "${PERL_BIN_PATH}/prove") +endif () +add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PROJECT_SOURCE_DIR}/../local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..) diff --git a/xs/src/callback.cpp b/xs/src/callback.cpp deleted file mode 100644 index 05b4f9e50..000000000 --- a/xs/src/callback.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "callback.hpp" - -#include - -void PerlCallback::register_callback(void *sv) -{ - if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV) - croak("Not a Callback %_ for PerlFunction", (SV*)sv); - if (m_callback) - SvSetSV((SV*)m_callback, (SV*)sv); - else - m_callback = newSVsv((SV*)sv); -} - -void PerlCallback::deregister_callback() -{ - if (m_callback) { - sv_2mortal((SV*)m_callback); - m_callback = nullptr; - } -} - -void PerlCallback::call() const -{ - if (! m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(int i) const -{ - if (! m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSViv(i))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(int i, int j) const -{ - if (! m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSViv(i))); - XPUSHs(sv_2mortal(newSViv(j))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(const std::vector& ints) const -{ - if (! m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - for (int i : ints) - { - XPUSHs(sv_2mortal(newSViv(i))); - } - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(double a) const -{ - if (!m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSVnv(a))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(double a, double b) const -{ - if (!m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSVnv(a))); - XPUSHs(sv_2mortal(newSVnv(b))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(double a, double b, double c) const -{ - if (!m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSVnv(a))); - XPUSHs(sv_2mortal(newSVnv(b))); - XPUSHs(sv_2mortal(newSVnv(c))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(double a, double b, double c, double d) const -{ - if (!m_callback) - return; - dSP; - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs(sv_2mortal(newSVnv(a))); - XPUSHs(sv_2mortal(newSVnv(b))); - XPUSHs(sv_2mortal(newSVnv(c))); - XPUSHs(sv_2mortal(newSVnv(d))); - PUTBACK; - perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); - FREETMPS; - LEAVE; -} - -void PerlCallback::call(bool b) const -{ - call(b ? 1 : 0); -} \ No newline at end of file diff --git a/xs/src/callback.hpp b/xs/src/callback.hpp deleted file mode 100644 index 9530829f8..000000000 --- a/xs/src/callback.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef slic3r_PerlCallback_hpp_ -#define slic3r_PerlCallback_hpp_ - -#include - -#include "libslic3r.h" - -namespace Slic3r { - -class PerlCallback { -public: - PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); } - PerlCallback() : m_callback(nullptr) {} - ~PerlCallback() { this->deregister_callback(); } - void register_callback(void *sv); - void deregister_callback(); - void call() const; - void call(int i) const; - void call(int i, int j) const; - void call(const std::vector& ints) const; - void call(double a) const; - void call(double a, double b) const; - void call(double a, double b, double c) const; - void call(double a, double b, double c, double d) const; - void call(bool b) const; -private: - void *m_callback; -}; - -} // namespace Slic3r - -#endif /* slic3r_PerlCallback_hpp_ */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 68fbcd612..98a124c3f 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -54,20 +54,6 @@ REGISTER_CLASS(Surface, "Surface"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); -REGISTER_CLASS(AppConfig, "GUI::AppConfig"); -REGISTER_CLASS(BackgroundSlicingProcess, "GUI::BackgroundSlicingProcess"); -REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); -REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); -REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); -REGISTER_CLASS(Preset, "GUI::Preset"); -REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); -REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); -REGISTER_CLASS(TabIface, "GUI::Tab"); -REGISTER_CLASS(ProgressStatusBar, "GUI::ProgressStatusBar"); -REGISTER_CLASS(PresetUpdater, "PresetUpdater"); -REGISTER_CLASS(AppController, "AppController"); -REGISTER_CLASS(PrintController, "PrintController"); -REGISTER_CLASS(PrintHost, "PrintHost"); SV* ConfigBase__as_hash(ConfigBase* THIS) { diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 47d71a09f..e36376bd1 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -89,7 +89,6 @@ extern "C" { #include #include #include -#include namespace Slic3r { diff --git a/xs/xsp/AppController.xsp b/xs/xsp/AppController.xsp deleted file mode 100644 index 8156b0ad2..000000000 --- a/xs/xsp/AppController.xsp +++ /dev/null @@ -1,29 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/AppController.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "slic3r/GUI/ProgressStatusBar.hpp" -%} - -%name{Slic3r::PrintController} class PrintController { - - PrintController(Print *print); - - void slice_to_png(); - void slice(); -}; - -%name{Slic3r::AppController} class AppController { - - AppController(); - - PrintController *print_ctl(); - void set_model(Model *model); - void set_print(Print *print); - void set_global_progress_indicator(ProgressStatusBar *prs); - - void arrange_model(); -}; \ No newline at end of file diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index b8ad84ba4..d5d295839 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -137,7 +137,7 @@ PROTOTYPES: DISABLE SV* print_config_def() CODE: - t_optiondef_map &def = Slic3r::print_config_def.options; + t_optiondef_map &def = *const_cast(&Slic3r::print_config_def.options); HV* options_hv = newHV(); for (t_optiondef_map::iterator oit = def.begin(); oit != def.end(); ++oit) { diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp deleted file mode 100644 index a4d656616..000000000 --- a/xs/xsp/GUI.xsp +++ /dev/null @@ -1,202 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/Utils/ASCIIFolding.hpp" -#include "slic3r/Utils/FixModelByWin10.hpp" -#include "slic3r/Utils/Serial.hpp" -%} - - -%package{Slic3r::GUI}; - -void about() - %code{% Slic3r::GUI::about(); %}; - -void disable_screensaver() - %code{% Slic3r::GUI::disable_screensaver(); %}; - -void enable_screensaver() - %code{% Slic3r::GUI::enable_screensaver(); %}; - -std::vector scan_serial_ports() - %code{% RETVAL=Slic3r::Utils::scan_serial_ports(); %}; - -bool debugged() - %code{% RETVAL=Slic3r::GUI::debugged(); %}; - -void break_to_debugger() - %code{% Slic3r::GUI::break_to_debugger(); %}; - -bool is_windows10() - %code{% RETVAL=Slic3r::is_windows10(); %}; - -void set_wxapp(SV *ui) - %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %}; - -void set_progress_status_bar(ProgressStatusBar *prs) - %code%{ Slic3r::GUI::set_progress_status_bar(prs); %}; - -void set_main_frame(SV *ui) - %code%{ Slic3r::GUI::set_main_frame((wxFrame*)wxPli_sv_2_object(aTHX_ ui, "Wx::Frame")); %}; - -void set_tab_panel(SV *ui) - %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; - -void set_plater(SV *ui) - %code%{ Slic3r::GUI::set_plater((wxPanel*)wxPli_sv_2_object(aTHX_ ui, "Wx::Panel")); %}; - -void add_menus(SV *ui, int event_preferences_changed, int event_language_change) - %code%{ Slic3r::GUI::add_menus((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_preferences_changed, event_language_change); %}; - -void create_preset_tabs(int event_value_change, int event_presets_changed) - %code%{ Slic3r::GUI::create_preset_tabs(event_value_change, event_presets_changed); %}; - -void show_error_id(int id, std::string msg) - %code%{ Slic3r::GUI::show_error_id(id, msg); %}; - -TabIface* get_preset_tab(char *name) - %code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %}; - -bool load_language() - %code%{ RETVAL=Slic3r::GUI::load_language(); %}; - -void create_combochecklist(SV *ui, std::string text, std::string items, bool initial_value) - %code%{ Slic3r::GUI::create_combochecklist((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl"), text, items, initial_value); %}; - -int combochecklist_get_flags(SV *ui) - %code%{ RETVAL=Slic3r::GUI::combochecklist_get_flags((wxComboCtrl*)wxPli_sv_2_object(aTHX_ ui, "Wx::ComboCtrl")); %}; - -void set_app_config(AppConfig *app_config) - %code%{ Slic3r::GUI::set_app_config(app_config); %}; - -bool check_unsaved_changes() - %code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %}; - -bool config_wizard_startup(int app_config_exists) - %code%{ - RETVAL=Slic3r::GUI::config_wizard_startup(app_config_exists != 0); - %}; - -void open_preferences_dialog(int preferences_event) - %code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %}; - -void set_preset_bundle(PresetBundle *preset_bundle) - %code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %}; - -void set_preset_updater(PresetUpdater* updater) - %code%{ Slic3r::GUI::set_preset_updater(updater); %}; - -void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_sizer) - %code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), - (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; - -void set_print_callback_event(Print *print, int id) - %code%{ Slic3r::GUI::set_print_callback_event(print, id); %}; - -void set_model_events_from_perl(Model *model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene) - %code%{ Slic3r::GUI::set_model_events_from_perl(*model, - event_object_selection_changed, - event_object_settings_changed, - event_remove_object, - event_update_scene); %}; - -void set_objects_from_perl( SV *ui_parent, - SV *frequently_changed_parameters_sizer, - SV *info_sizer, - SV *btn_export_gcode, - SV *btn_reslice, - SV *btn_print, - SV *btn_send_gcode, - SV *manifold_warning_icon) - %code%{ Slic3r::GUI::set_objects_from_perl( - (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), - (wxBoxSizer *)wxPli_sv_2_object(aTHX_ info_sizer, "Wx::BoxSizer"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), - (wxStaticBitmap *)wxPli_sv_2_object(aTHX_ manifold_warning_icon, "Wx::StaticBitmap")); %}; - -void set_show_print_info(bool show) - %code%{ Slic3r::GUI::set_show_print_info(show); %}; - -void set_show_manifold_warning_icon(bool show) - %code%{ Slic3r::GUI::set_show_manifold_warning_icon(show); %}; - -void update_mode() - %code%{ Slic3r::GUI::update_mode(); %}; - -void add_object_to_list(const char *name, SV *object_model) - %code%{ Slic3r::GUI::add_object_to_list( - name, - (ModelObject *)wxPli_sv_2_object(aTHX_ object_model, "Slic3r::Model::Object") ); %}; - -void delete_object_from_list() - %code%{ Slic3r::GUI::delete_object_from_list(); %}; - -void delete_all_objects_from_list() - %code%{ Slic3r::GUI::delete_all_objects_from_list(); %}; - -void set_object_count(int idx, int count) - %code%{ Slic3r::GUI::set_object_count(idx, count); %}; - -void unselect_objects() - %code%{ Slic3r::GUI::unselect_objects(); %}; - -void select_current_object(int idx) - %code%{ Slic3r::GUI::select_current_object(idx); %}; - -void select_current_volume(int idx, int vol_idx) - %code%{ Slic3r::GUI::select_current_volume(idx, vol_idx); %}; - -void remove_obj() - %code%{ Slic3r::GUI::remove(); %}; - -std::string fold_utf8_to_ascii(const char *src) - %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; - -void add_export_option(SV *ui, std::string format) - %code%{ Slic3r::GUI::add_export_option((wxFileDialog*)wxPli_sv_2_object(aTHX_ ui, "Wx::FileDialog"), format); %}; - -int get_export_option(SV *ui) - %code%{ RETVAL = Slic3r::GUI::get_export_option((wxFileDialog*)wxPli_sv_2_object(aTHX_ ui, "Wx::FileDialog")); %}; - -void desktop_open_datadir_folder() - %code%{ Slic3r::GUI::desktop_open_datadir_folder(); %}; - -void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst) - %code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %}; - -void register_on_request_update_callback(SV* callback) - %code%{ Slic3r::GUI::g_on_request_update_callback.register_callback(callback); %}; - -void deregister_on_request_update_callback() - %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %}; - -void create_double_slider(SV *ui_parent, SV *ui_sizer, SV *ui_canvas) - %code%{ Slic3r::GUI::create_double_slider( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), - (wxGLCanvas*)wxPli_sv_2_object(aTHX_ ui_canvas, "Wx::GLCanvas")); %}; - -void update_double_slider(bool force_sliders_full_range) - %code%{ Slic3r::GUI::update_double_slider(force_sliders_full_range); %}; - -void reset_double_slider() - %code%{ Slic3r::GUI::reset_double_slider(); %}; - -void enable_action_buttons(bool enable) - %code%{ Slic3r::GUI::enable_action_buttons(enable); %}; - -void save_window_size(SV *window, std::string name) - %code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; - -void restore_window_size(SV *window, std::string name) - %code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp deleted file mode 100644 index c3e4ba3b7..000000000 --- a/xs/xsp/GUI_3DScene.xsp +++ /dev/null @@ -1,811 +0,0 @@ -%module{Slic3r::XS}; - -#include -#include "slic3r/GUI/GLShader.hpp" -#include "slic3r/GUI/3DScene.hpp" - -%name{Slic3r::GUI::_3DScene::GLShader} class GLShader { - GLShader(); - ~GLShader(); - - bool load_from_text(const char *fragment_shader, const char *vertex_shader); - bool load_from_file(const char *fragment_shader, const char *vertex_shader); - void release(); - - int get_attrib_location(const char *name) const; - int get_uniform_location(const char *name) const; - - bool set_uniform(const char *name, float value) const; - - void enable() const; - void disable() const; - - int shader_program_id() const - %code%{ RETVAL = THIS->shader_program_id; %}; - - std::string last_error() const - %code%{ RETVAL = THIS->last_error; %}; -}; - -%name{Slic3r::GUI::_3DScene::GLVolume} class GLVolume { - GLVolume(); - ~GLVolume(); - - std::vector color() - %code%{ RETVAL.reserve(4); RETVAL.push_back(THIS->color[0]); RETVAL.push_back(THIS->color[1]); RETVAL.push_back(THIS->color[2]); RETVAL.push_back(THIS->color[3]); %}; - - int select_group_id() - %code%{ RETVAL = THIS->select_group_id; %}; - int drag_group_id() - %code%{ RETVAL = THIS->drag_group_id; %}; - int selected() - %code%{ RETVAL = THIS->selected; %}; - void set_selected(int i) - %code%{ THIS->selected = i; %}; - int hover() - %code%{ RETVAL = THIS->hover; %}; - void set_hover(int i) - %code%{ THIS->hover = i; %}; - int zoom_to_volumes() - %code%{ RETVAL = THIS->zoom_to_volumes; %}; - - void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, PrintObject* print_object, float z_cursor_relative, float edit_band_width); - void reset_layer_height_texture_data(); - - int object_idx() const; - int volume_idx() const; - int instance_idx() const; - Clone origin() const - %code%{ RETVAL = THIS->get_offset(); %}; - void translate(double x, double y, double z) - %code%{ THIS->set_offset(THIS->get_offset() + Vec3d(x, y, z)); %}; - Clone bounding_box() const - %code%{ RETVAL = THIS->bounding_box; %}; - - bool empty() const; - bool indexed() const; - - void render() const; - - bool has_layer_height_texture(); - int layer_height_texture_width(); - int layer_height_texture_height(); - int layer_height_texture_cells(); - void* layer_height_texture_data_ptr_level0(); - void* layer_height_texture_data_ptr_level1(); - double layer_height_texture_z_to_row_id() const; - void generate_layer_height_texture(PrintObject *print_object, bool force); -}; - - -%name{Slic3r::GUI::_3DScene::GLVolume::Collection} class GLVolumeCollection { - GLVolumeCollection(); - ~GLVolumeCollection(); - - std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); - - int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); - - void erase() - %code{% THIS->clear(); %}; - - int count() - %code{% RETVAL = THIS->volumes.size(); %}; - - void set_range(double low, double high); - - void render_VBOs() const; - void render_legacy() const; - void finalize_geometry(bool use_VBOs); - void release_geometry(); - - void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z); - - void reset_outside_state(); - void update_colors_by_extruder(DynamicPrintConfig* config); - - bool move_volume_up(int idx) - %code%{ - if (idx > 0 && idx < int(THIS->volumes.size())) { - std::swap(THIS->volumes[idx-1], THIS->volumes[idx]); - std::swap(THIS->volumes[idx-1]->composite_id, THIS->volumes[idx]->composite_id); - std::swap(THIS->volumes[idx-1]->select_group_id, THIS->volumes[idx]->select_group_id); - std::swap(THIS->volumes[idx-1]->drag_group_id, THIS->volumes[idx]->drag_group_id); - RETVAL = true; - } else - RETVAL = false; - %}; - bool move_volume_down(int idx) - %code%{ - if (idx >= 0 && idx + 1 < int(THIS->volumes.size())) { - std::swap(THIS->volumes[idx+1], THIS->volumes[idx]); - std::swap(THIS->volumes[idx+1]->composite_id, THIS->volumes[idx]->composite_id); - std::swap(THIS->volumes[idx+1]->select_group_id, THIS->volumes[idx]->select_group_id); - std::swap(THIS->volumes[idx+1]->drag_group_id, THIS->volumes[idx]->drag_group_id); - RETVAL = true; - } else - RETVAL = false; - %}; - -%{ - -SV* -GLVolumeCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->volumes.size()-1); - int i = 0; - for (GLVolume *v : THIS->volumes) { - av_store(av, i++, perl_to_SV_ref(*v)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::GUI::_3DScene}; -%{ - -std::string -get_gl_info(format_as_html, extensions) - bool format_as_html; - bool extensions; - CODE: - RETVAL = _3DScene::get_gl_info(format_as_html, extensions); - OUTPUT: - RETVAL - -bool -use_VBOs() - CODE: - RETVAL = _3DScene::use_VBOs(); - OUTPUT: - RETVAL - -bool -add_canvas(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::add_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -bool -remove_canvas(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::remove_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -void -remove_all_canvases() - CODE: - _3DScene::remove_all_canvases(); - -void -set_as_dirty(canvas) - SV *canvas; - CODE: - _3DScene::set_as_dirty((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -unsigned int -get_volumes_count(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::get_volumes_count((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -void -reset_volumes(canvas) - SV *canvas; - CODE: - _3DScene::reset_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -deselect_volumes(canvas) - SV *canvas; - CODE: - _3DScene::deselect_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -select_volume(canvas, id) - SV *canvas; - unsigned int id; - CODE: - _3DScene::select_volume((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); - -void -update_volumes_selection(canvas, selections) - SV *canvas; - std::vector selections; - CODE: - _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); - -int -check_volumes_outside_state(canvas, config) - SV *canvas; - DynamicPrintConfig *config; - CODE: - RETVAL = _3DScene::check_volumes_outside_state((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); - OUTPUT: - RETVAL - -bool -move_volume_up(canvas, id) - SV *canvas; - unsigned int id; - CODE: - RETVAL = _3DScene::move_volume_up((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); - OUTPUT: - RETVAL - -bool -move_volume_down(canvas, id) - SV *canvas; - unsigned int id; - CODE: - RETVAL = _3DScene::move_volume_down((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); - OUTPUT: - RETVAL - -void -set_objects_selections(canvas, selections) - SV *canvas; - std::vector selections; - CODE: - _3DScene::set_objects_selections((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); - -void -set_config(canvas, config) - SV *canvas; - DynamicPrintConfig *config; - CODE: - _3DScene::set_config((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); - -void -set_print(canvas, print) - SV *canvas; - Print *print; - CODE: - _3DScene::set_print((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print); - -void -set_model(canvas, model) - SV *canvas; - Model *model; - CODE: - _3DScene::set_model((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model); - -void -set_bed_shape(canvas, shape) - SV *canvas; - Pointfs shape; - CODE: - _3DScene::set_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), shape); - -void -set_auto_bed_shape(canvas) - SV *canvas; - CODE: - _3DScene::set_auto_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -Clone -get_volumes_bounding_box(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::get_volumes_bounding_box((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -void -set_axes_length(canvas, length) - SV *canvas; - float length; - CODE: - _3DScene::set_axes_length((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), length); - -void -set_cutting_plane(canvas, z, polygons) - SV *canvas; - float z; - ExPolygons polygons; - CODE: - _3DScene::set_cutting_plane((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), z, polygons); - -void -set_color_by(canvas, value) - SV *canvas; - std::string value; - CODE: - _3DScene::set_color_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); - -void -set_select_by(canvas, value) - SV *canvas; - std::string value; - CODE: - _3DScene::set_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); - -void -set_drag_by(canvas, value) - SV *canvas; - std::string value; - CODE: - _3DScene::set_drag_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); - -std::string -get_select_by(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::get_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -bool -is_layers_editing_enabled(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::is_layers_editing_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -bool -is_layers_editing_allowed(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::is_layers_editing_allowed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -bool -is_shader_enabled(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::is_shader_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -bool -is_reload_delayed(canvas) - SV *canvas; - CODE: - RETVAL = _3DScene::is_reload_delayed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - OUTPUT: - RETVAL - -void -enable_layers_editing(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_layers_editing((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_warning_texture(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_warning_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_legend_texture(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_legend_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_picking(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_picking((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_moving(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_moving((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_gizmos(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_gizmos((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_shader(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_toolbar(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_toolbar((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_force_zoom_to_bed(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -enable_dynamic_background(canvas, enable) - SV *canvas; - bool enable; - CODE: - _3DScene::enable_dynamic_background((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); - -void -allow_multisample(canvas, allow) - SV *canvas; - bool allow; - CODE: - _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow); - -void -enable_toolbar_item(canvas, item, enable) - SV *canvas; - std::string item; - bool enable; - CODE: - _3DScene::enable_toolbar_item((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item, enable); - -bool -is_toolbar_item_pressed(canvas, item) - SV *canvas; - std::string item; - CODE: - RETVAL = _3DScene::is_toolbar_item_pressed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item); - OUTPUT: - RETVAL - -void -zoom_to_bed(canvas) - SV *canvas; - CODE: - _3DScene::zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -zoom_to_volumes(canvas) - SV *canvas; - CODE: - _3DScene::zoom_to_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -select_view(canvas, direction) - SV *canvas; - std::string direction; - CODE: - _3DScene::select_view((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), direction); - -void -set_viewport_from_scene(canvas, other) - SV *canvas; - SV *other; - CODE: - _3DScene::set_viewport_from_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (wxGLCanvas*)wxPli_sv_2_object(aTHX_ other, "Wx::GLCanvas")); - -void -update_volumes_colors_by_extruder(canvas) - SV *canvas; - CODE: - _3DScene::update_volumes_colors_by_extruder((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -update_gizmos_data(canvas) - SV *canvas; - CODE: - _3DScene::update_gizmos_data((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -render(canvas) - SV *canvas; - CODE: - _3DScene::render((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -std::vector -get_current_print_zs(canvas, active_only) - SV *canvas; - bool active_only; - CODE: - RETVAL = _3DScene::get_current_print_zs((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active_only); - OUTPUT: - RETVAL - -void -set_toolpaths_range(canvas, low, high) - SV *canvas; - double low; - double high; - CODE: - _3DScene::set_toolpaths_range((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), low, high); - -void -register_on_viewport_changed_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_viewport_changed_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_double_click_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_double_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_right_click_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_right_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_select_object_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_select_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_model_update_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_model_update_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_remove_object_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_remove_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_arrange_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_rotate_object_left_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_rotate_object_left_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_rotate_object_right_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_rotate_object_right_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_scale_object_uniformly_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_scale_object_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_increase_objects_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_increase_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_decrease_objects_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_decrease_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_instance_moved_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_instance_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_wipe_tower_moved_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_wipe_tower_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_enable_action_buttons_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_enable_action_buttons_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_gizmo_scale_uniformly_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_gizmo_scale_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_gizmo_rotate_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_gizmo_rotate_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_gizmo_flatten_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_gizmo_flatten_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_on_update_geometry_info_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_on_update_geometry_info_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_add_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_add_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_delete_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_delete_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_deleteall_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_deleteall_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_arrange_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_more_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_more_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_fewer_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_fewer_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_split_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_split_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_cut_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_cut_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_settings_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_settings_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_layersediting_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_layersediting_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_selectbyparts_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_selectbyparts_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -reset_legend_texture() - CODE: - _3DScene::reset_legend_texture(); - -std::vector -load_model_object(canvas, model_object, obj_idx, instance_idxs) - SV *canvas; - ModelObject *model_object; - int obj_idx; - std::vector instance_idxs; - CODE: - RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs); - OUTPUT: - RETVAL - -int -get_first_volume_id(canvas, obj_idx) - SV *canvas; - int obj_idx; - CODE: - RETVAL = _3DScene::get_first_volume_id((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), obj_idx); - OUTPUT: - RETVAL - -int -get_in_object_volume_id(canvas, scene_vol_idx) - SV *canvas; - int scene_vol_idx; - CODE: - RETVAL = _3DScene::get_in_object_volume_id((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), scene_vol_idx); - OUTPUT: - RETVAL - -std::vector -load_model(canvas, model, obj_idx) - SV *canvas; - Model *model; - int obj_idx; - CODE: - RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx); - OUTPUT: - RETVAL - -void -reload_scene(canvas, force) - SV *canvas; - bool force; - CODE: - _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force); - -void -load_gcode_preview(canvas, preview_data, str_tool_colors) - SV *canvas; - GCodePreviewData *preview_data; - std::vector str_tool_colors; - CODE: - _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); - -void -load_preview(canvas, str_tool_colors) - SV *canvas; - std::vector str_tool_colors; - CODE: - _3DScene::load_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), str_tool_colors); - -%} diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp deleted file mode 100644 index 08a88883d..000000000 --- a/xs/xsp/GUI_AppConfig.xsp +++ /dev/null @@ -1,46 +0,0 @@ - -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/AppConfig.hpp" -%} - -%name{Slic3r::GUI::AppConfig} class AppConfig { - AppConfig(); - ~AppConfig(); - - void reset(); - void set_defaults(); - - void load() - %code%{ - try { - THIS->load(); - } catch (std::exception& e) { - croak("Loading an application config file failed:\n%s\n", e.what()); - } - %}; - void save() - %code%{ - try { - THIS->save(); - } catch (std::exception& e) { - croak("Saving an application config file failed:\n%s\n", e.what()); - } - %}; - bool exists(); - bool dirty(); - - std::string get(char *name); - void set(char *name, char *value); - bool has(char *section); - - std::string get_last_dir(); - void update_config_dir(char *dir); - void update_skein_dir(char *dir); - std::string get_last_output_dir(const char *alt = ""); - void update_last_output_dir(char *dir); - - void reset_selections(); -}; diff --git a/xs/xsp/GUI_BackgroundSlicingProcess.xsp b/xs/xsp/GUI_BackgroundSlicingProcess.xsp deleted file mode 100644 index 8452b8c31..000000000 --- a/xs/xsp/GUI_BackgroundSlicingProcess.xsp +++ /dev/null @@ -1,25 +0,0 @@ - -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/BackgroundSlicingProcess.hpp" -%} - -%name{Slic3r::GUI::BackgroundSlicingProcess} class BackgroundSlicingProcess { - BackgroundSlicingProcess(); - ~BackgroundSlicingProcess(); - - void set_print(Print *print); - void set_gcode_preview_data(GCodePreviewData *gpd); - void set_sliced_event(int event_id); - void set_finished_event(int event_id); - - void set_output_path(const char *path); - bool start(); - bool stop(); - bool apply_config(DynamicPrintConfig *config) - %code%{ RETVAL = THIS->apply_config(*config); %}; - - bool running(); -}; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp deleted file mode 100644 index 99d23a142..000000000 --- a/xs/xsp/GUI_Preset.xsp +++ /dev/null @@ -1,149 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/Preset.hpp" -#include "slic3r/GUI/PresetBundle.hpp" -#include "slic3r/GUI/PresetHints.hpp" -%} - -%name{Slic3r::GUI::Preset} class Preset { - // owned by PresetCollection, no constructor/destructor - - bool default() %code%{ RETVAL = THIS->is_default; %}; - bool external() %code%{ RETVAL = THIS->is_external; %}; - bool system() %code%{ RETVAL = THIS->is_system; %}; - bool visible() %code%{ RETVAL = THIS->is_visible; %}; - bool dirty() %code%{ RETVAL = THIS->is_dirty; %}; - bool compatible() %code%{ RETVAL = THIS->is_compatible; %}; - bool is_compatible_with_printer(Preset *active_printer) - %code%{ RETVAL = THIS->is_compatible_with_printer(*active_printer); %}; - - std::string name() %code%{ RETVAL = THIS->name; %}; - std::string file() %code%{ RETVAL = THIS->file; %}; - - bool loaded() %code%{ RETVAL = THIS->loaded; %}; - - Ref config() %code%{ RETVAL = &THIS->config; %}; - - void set_num_extruders(int num_extruders); -}; - -%name{Slic3r::GUI::PresetCollection} class PresetCollection { - - Ref preset(size_t idx) %code%{ RETVAL = &THIS->preset(idx); %}; - size_t size() const; - size_t num_visible() const; - std::string name() const; - - Ref get_selected_preset() %code%{ RETVAL = &THIS->get_selected_preset(); %}; - Ref get_current_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; - std::string get_current_preset_name() %code%{ RETVAL = THIS->get_selected_preset().name; %}; - Ref get_edited_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; - - Ref find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %}; - - void update_tab_ui(SV *ui, bool show_incompatible) - %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); - THIS->update_tab_ui(cb, show_incompatible); %}; - - void update_platter_ui(SV *ui) - %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); - THIS->update_platter_ui(cb); %}; - -%{ - -SV* -PresetCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->size()-1); - for (int i = 0; i < int(THIS->size()); ++ i) { - Preset &preset = THIS->preset(i); - av_store(av, i, perl_to_SV_ref(preset)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; - -%name{Slic3r::GUI::PresetBundle} class PresetBundle { - PresetBundle(); - ~PresetBundle(); - - void reset(bool delete_files); - - void setup_directories() - %code%{ - try { - THIS->setup_directories(); - } catch (std::exception& e) { - croak("Cannot create configuration directories:\n%s\n", e.what()); - } - %}; - void load_presets(AppConfig *config) - %code%{ - try { - THIS->load_presets(*config); - } catch (std::exception& e) { - croak("Loading of Slic3r presets from %s failed.\n\n%s\n", - Slic3r::data_dir().c_str(), e.what()); - } - %}; - void load_config(const char *name, DynamicPrintConfig *config) - %code%{ - try { - THIS->load_config(name, *config); - } catch (std::exception& e) { - croak("Loading a configuration %s failed:\n%s\n", name, e.what()); - } - %}; - void load_config_file(const char *path) - %code%{ - try { - THIS->load_config_file(path); - } catch (std::exception& e) { - croak("Loading a configuration file %s failed:\n%s\n", path, e.what()); - } - %}; - size_t load_configbundle(const char *path) - %code%{ - try { - RETVAL = THIS->load_configbundle(path, PresetBundle::LOAD_CFGBNDLE_SAVE); - } catch (std::exception& e) { - croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); - } - %}; - void export_configbundle(char *path) - %code%{ - try { - THIS->export_configbundle(path); - } catch (std::exception& e) { - croak("Export of a config bundle %s failed:\n%s\n", path, e.what()); - } - %}; - - void set_default_suppressed(bool default_suppressed); - - void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %}; - void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %}; - - Ref print() %code%{ RETVAL = &THIS->prints; %}; - Ref filament() %code%{ RETVAL = &THIS->filaments; %}; - Ref sla_material() %code%{ RETVAL = &THIS->sla_materials; %}; - Ref printer() %code%{ RETVAL = &THIS->printers; %}; - Ref project_config() %code%{ RETVAL = &THIS->project_config; %}; - - bool has_defauls_only(); - - std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; - void set_filament_preset(int idx, const char *name); - - Clone full_config() %code%{ RETVAL = THIS->full_config(); %}; - - void update_platter_filament_ui(int extruder_idx, SV *ui) - %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); - THIS->update_platter_filament_ui(extruder_idx, cb); %}; -}; diff --git a/xs/xsp/GUI_Tab.xsp b/xs/xsp/GUI_Tab.xsp deleted file mode 100644 index bcbdc0d9f..000000000 --- a/xs/xsp/GUI_Tab.xsp +++ /dev/null @@ -1,24 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/TabIface.hpp" -%} - -%name{Slic3r::GUI::Tab} class TabIface { - TabIface(); - ~TabIface(); - void load_current_preset(); - void update_tab_ui(); - void update_ui_from_settings(); - void select_preset(char* name); - void load_config(DynamicPrintConfig* config); - bool current_preset_is_dirty(); - void load_key_value(char* opt_key, char* value); - void OnActivate(); - size_t get_selected_preset_item(); - std::string title(); - Ref get_config(); - Ref get_presets(); - std::vector get_dependent_tabs(); -}; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 5e766f3ff..8f1d88c74 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -21,27 +21,18 @@ %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) %code%{ try { - RETVAL = new Model(Model::read_from_file(input_file, add_default_instances)); + RETVAL = new Model(Model::read_from_file(input_file, nullptr, add_default_instances)); } catch (std::exception& e) { croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); } %}; - %name{read_from_archive} Model(std::string input_file, PresetBundle* bundle, bool add_default_instances = true) - %code%{ - try { - RETVAL = new Model(Model::read_from_archive(input_file, bundle, add_default_instances)); - } catch (std::exception& e) { - croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); - } - %}; - Clone clone() %code%{ RETVAL = THIS; %}; %name{_add_object} Ref add_object(); Ref _add_object_clone(ModelObject* other, bool copy_volumes = true) - %code%{ RETVAL = THIS->add_object(*other, copy_volumes); %}; + %code%{ auto ptr = THIS->add_object(*other); if (! copy_volumes) ptr->clear_volumes(); RETVAL = ptr; %}; void delete_object(size_t idx); void clear_objects(); size_t objects_count() @@ -101,10 +92,6 @@ bool store_stl(char *path, bool binary) %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; - bool store_amf(char *path, Print* print, bool export_print_config) - %code%{ RETVAL = Slic3r::store_amf(path, THIS, print, export_print_config); %}; - bool store_3mf(char *path, Print* print, bool export_print_config) - %code%{ RETVAL = Slic3r::store_3mf(path, THIS, print, export_print_config); %}; %{ @@ -122,67 +109,7 @@ load_stl(CLASS, path, object_name) OUTPUT: RETVAL -Model* -load_obj(CLASS, path, object_name) - char* CLASS; - char* path; - char* object_name; - CODE: - RETVAL = new Model(); - if (! load_obj(path, RETVAL, object_name)) { - delete RETVAL; - RETVAL = NULL; - } - OUTPUT: - RETVAL - -Model* -load_amf(CLASS, bundle, path) - char* CLASS; - PresetBundle* bundle; - char* path; - CODE: - RETVAL = new Model(); - if (! load_amf(path, bundle, RETVAL)) { - delete RETVAL; - RETVAL = NULL; - } - OUTPUT: - RETVAL - -Model* -load_3mf(CLASS, bundle, path) - char* CLASS; - PresetBundle* bundle; - char* path; - CODE: - RETVAL = new Model(); - if (! load_3mf(path, bundle, RETVAL)) { - delete RETVAL; - RETVAL = NULL; - } - OUTPUT: - RETVAL - -Model* -load_prus(CLASS, path) - char* CLASS; - char* path; - CODE: -#ifdef SLIC3R_PRUS - RETVAL = new Model(); - if (! load_prus(path, RETVAL)) { - delete RETVAL; - RETVAL = NULL; - } -#else - RETVAL = nullptr; -#endif - OUTPUT: - RETVAL - %} - }; %name{Slic3r::Model::Material} class ModelMaterial { @@ -284,11 +211,6 @@ ModelMaterial::attributes() void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; - std::vector layer_height_profile() - %code%{ RETVAL = THIS->layer_height_profile_valid ? THIS->layer_height_profile : std::vector(); %}; - void set_layer_height_profile(std::vector profile) - %code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %}; - Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Vec3d* point) @@ -304,12 +226,6 @@ ModelMaterial::attributes() void rotate(float angle, Vec3d* axis) %code{% THIS->rotate(angle, *axis); %}; void mirror(Axis axis); - - Model* cut(double z) - %code%{ - RETVAL = new Model(); - THIS->cut(z, RETVAL); - %}; ModelObjectPtrs* split_object() %code%{ @@ -331,15 +247,13 @@ ModelMaterial::attributes() %code%{ THIS->name = value; %}; t_model_material_id material_id(); void set_material_id(t_model_material_id material_id) - %code%{ THIS->material_id(material_id); %}; + %code%{ THIS->set_material_id(material_id); %}; Ref material(); Ref config() %code%{ RETVAL = &THIS->config; %}; Ref mesh() %code%{ RETVAL = &THIS->mesh; %}; - Ref convex_hull() - %code%{ RETVAL = &THIS->get_convex_hull(); %}; bool modifier() %code%{ RETVAL = THIS->is_modifier(); %}; @@ -357,8 +271,6 @@ ModelMaterial::attributes() %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; size_t split(unsigned int max_extruders); - - ModelMaterial* assign_unique_material(); }; @@ -366,33 +278,33 @@ ModelMaterial::attributes() Ref object() %code%{ RETVAL = THIS->get_object(); %}; - double rotation() - %code%{ RETVAL = THIS->rotation; %}; - double scaling_factor() - %code%{ RETVAL = THIS->scaling_factor; %}; -#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec3d* rotation() + %code%{ RETVAL = new Vec3d(THIS->get_rotation(X), THIS->get_rotation(Y), THIS->get_rotation(Z)); %}; + + Vec3d* scaling_factor() + %code%{ RETVAL = new Vec3d(THIS->get_scaling_factor(X), THIS->get_scaling_factor(Y), THIS->get_scaling_factor(Z)); %}; + Vec2d* offset() %code%{ RETVAL = new Vec2d(THIS->get_offset(X), THIS->get_offset(Y)); %}; -#else - Ref offset() - %code%{ RETVAL = &THIS->offset; %}; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - + void set_rotation(double val) - %code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %}; + %code%{ THIS->set_rotation(Z, val); THIS->get_object()->invalidate_bounding_box(); %}; + + void set_rotations(Vec3d *rotation) + %code%{ THIS->set_rotation(*rotation); THIS->get_object()->invalidate_bounding_box(); %}; + void set_scaling_factor(double val) - %code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %}; -#if ENABLE_MODELINSTANCE_3D_OFFSET + %code%{ THIS->set_scaling_factor(X, val); THIS->set_scaling_factor(Y, val); THIS->set_scaling_factor(Z, val); THIS->get_object()->invalidate_bounding_box(); %}; + + void set_scaling_factors(Vec3d *scale) + %code%{ THIS->set_scaling_factor(*scale); THIS->get_object()->invalidate_bounding_box(); %}; + void set_offset(Vec2d *offset) %code%{ THIS->set_offset(X, (*offset)(0)); THIS->set_offset(Y, (*offset)(1)); %}; -#else - void set_offset(Vec2d *offset) - %code%{ THIS->offset = *offset; %}; -#endif // ENABLE_MODELINSTANCE_3D_OFFSET - + void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_polygon(Polygon* polygon) const; }; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 0424d8b7b..aa90a3ad9 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -44,15 +44,11 @@ _constant() int region_count() %code%{ RETVAL = THIS->print()->regions().size(); %}; - int region_volumes_count() - %code%{ RETVAL = THIS->region_volumes.size(); %}; Ref print(); Ref model_object(); Ref config() %code%{ RETVAL = &THIS->config(); %}; Points copies(); - t_layer_height_ranges layer_height_ranges() - %code%{ RETVAL = THIS->layer_height_ranges; %}; std::vector layer_height_profile() %code%{ RETVAL = THIS->layer_height_profile; %}; Clone bounding_box(); @@ -60,13 +56,6 @@ _constant() Points _shifted_copies() %code%{ RETVAL = THIS->copies(); %}; - bool add_copy(Vec2d* point) - %code%{ RETVAL = THIS->add_copy(*point); %}; - bool delete_last_copy(); - bool reload_model_instances(); - void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) - %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; - size_t layer_count(); Ref get_layer(int idx); @@ -111,12 +100,9 @@ _constant() %code%{ RETVAL = THIS->wipe_tower_data().number_of_toolchanges; %}; PrintObjectPtrs* objects() %code%{ RETVAL = const_cast(&THIS->objects()); %}; - void clear_objects(); Ref get_object(int idx) %code%{ RETVAL = THIS->objects()[idx]; %}; - void delete_object(int idx); void reload_object(int idx); - bool reload_model_instances(); size_t object_count() %code%{ RETVAL = THIS->objects().size(); %}; @@ -185,14 +171,6 @@ _constant() } %}; - void export_gcode_with_preview_data(char *path_template, GCodePreviewData *preview_data) %code%{ - try { - THIS->export_gcode(path_template, preview_data); - } catch (std::exception& e) { - croak(e.what()); - } - %}; - void export_gcode(char *path_template) %code%{ try { THIS->export_gcode(path_template, nullptr); @@ -201,11 +179,4 @@ _constant() } %}; - void export_png(char *path) %code%{ - try { - THIS->export_png(path); - } catch (std::exception& e) { - croak(e.what()); - } - %}; }; diff --git a/xs/xsp/ProgressStatusBar.xsp b/xs/xsp/ProgressStatusBar.xsp deleted file mode 100644 index 703a53b67..000000000 --- a/xs/xsp/ProgressStatusBar.xsp +++ /dev/null @@ -1,48 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/GUI/ProgressStatusBar.hpp" -#include "slic3r/GUI/GUI.hpp" -%} - -%name{Slic3r::GUI::ProgressStatusBar} class ProgressStatusBar { - ProgressStatusBar(); - ~ProgressStatusBar(); - - int GetProgress() const - %code%{ RETVAL=THIS->get_progress(); %}; - - void SetProgress(int val) - %code%{ THIS->set_progress(val); %}; - - void SetRange(int val = 100) - %code%{ THIS->set_range(val); %}; - - void ShowProgress(bool show) - %code%{ THIS->show_progress(show); %}; - - void StartBusy(int val = 100) - %code%{ THIS->start_busy(val); %}; - - void StopBusy() - %code%{ THIS->stop_busy(); %}; - - bool IsBusy() const - %code%{ RETVAL=THIS->is_busy(); %}; - - void Run(int rate) - %code%{ THIS->run(rate); %}; - - void Embed() - %code%{ THIS->embed(); %}; - - void SetStatusText(const char *txt) - %code%{ THIS->set_status_text(_(txt)); %}; - - void SetCancelCallback(SV* callback) - %code%{ THIS->m_perl_cancel_callback.register_callback(callback); THIS->show_cancel_button();%}; - void ResetCancelCallback() - %code%{ THIS->m_perl_cancel_callback.deregister_callback(); THIS->hide_cancel_button(); %}; - -}; \ No newline at end of file diff --git a/xs/xsp/Utils_PresetUpdater.xsp b/xs/xsp/Utils_PresetUpdater.xsp deleted file mode 100644 index dc874acab..000000000 --- a/xs/xsp/Utils_PresetUpdater.xsp +++ /dev/null @@ -1,13 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/Utils/PresetUpdater.hpp" -%} - -%name{Slic3r::PresetUpdater} class PresetUpdater { - PresetUpdater(int version_online_event); - void sync(PresetBundle* preset_bundle); - void slic3r_update_notify(); - bool config_update(); -}; diff --git a/xs/xsp/Utils_PrintHost.xsp b/xs/xsp/Utils_PrintHost.xsp deleted file mode 100644 index 59c09c431..000000000 --- a/xs/xsp/Utils_PrintHost.xsp +++ /dev/null @@ -1,12 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/Utils/PrintHost.hpp" -%} - -%name{Slic3r::PrintHost} class PrintHost { - bool send_gcode(std::string filename) const; - - static PrintHost* get_print_host(DynamicPrintConfig *config); -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 408966607..07e4a3799 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -215,36 +215,6 @@ PrintObjectSupportMaterial* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -AppConfig* O_OBJECT_SLIC3R -AppController* O_OBJECT_SLIC3R -PrintController* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -BackgroundSlicingProcess* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -GLShader* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -GLVolume* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -GLVolumeCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -Preset* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -PresetCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -PresetBundle* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -TabIface* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -ProgressStatusBar* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PresetUpdater* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PrintHost* O_OBJECT_SLIC3R - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 9dd3722b2..121033db4 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -194,35 +194,14 @@ %typemap{ModelInstancePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{AppConfig*}; -%typemap{Ref}{simple}; -%typemap{BackgroundSlicingProcess*}; -%typemap{Ref}{simple}; -%typemap{GLShader*}; -%typemap{Ref}{simple}; -%typemap{GLVolume*}; -%typemap{Ref}{simple}; -%typemap{GLVolumeCollection*}; -%typemap{Ref}{simple}; -%typemap{Preset*}; -%typemap{Ref}{simple}; -%typemap{PresetCollection*}; -%typemap{Ref}{simple}; -%typemap{PresetBundle*}; -%typemap{Ref}{simple}; -%typemap{PresetUpdater*}; -%typemap{Ref}{simple}; %typemap{PresetHints*}; %typemap{Ref}{simple}; -%typemap{TabIface*}; -%typemap{ProgressStatusBar*}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; %typemap{LayerPtrs*}; %typemap{SupportLayerPtrs*}; - %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{% @@ -271,6 +250,3 @@ $CVar = (PrintObjectStep)SvUV($PerlVar); %}; }; -%typemap{AppController*}; -%typemap{PrintController*}; -%typemap{PrintHost*};