Repository: patrikhuber/eos-model-viewer Branch: master Commit: bf3722a678f9 Files: 10 Total size: 83.7 KB Directory structure: gitextract_ir1lx7s3/ ├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake/ │ └── FindLIBIGL.cmake ├── cxxopts.hpp ├── eos-model-viewer.cpp └── initial_cache.cmake.template ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- BasedOnStyle: LLVM AccessModifierOffset: '-4' AlwaysBreakTemplateDeclarations: 'true' BreakBeforeBraces: Custom BraceWrapping: AfterClass: true AfterControlStatement: true AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false ColumnLimit: '110' Cpp11BracedListStyle: 'true' PointerAlignment: Left IndentWidth: '4' Language: Cpp NamespaceIndentation: None SortIncludes: false Standard: Cpp11 UseTab: Never # http://clang.llvm.org/docs/ClangFormatStyleOptions.html ... ================================================ FILE: .gitignore ================================================ # Ignore (optional) configuration files with user-specific paths: initial_cache.cmake ================================================ FILE: .gitmodules ================================================ [submodule "external/eos"] path = external/eos url = https://github.com/patrikhuber/eos.git [submodule "external/libigl"] path = external/libigl url = https://github.com/libigl/libigl.git ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1.3) project(eos-model-viewer) set(eos-model-viewer_VERSION 0.2.0.alpha) set_property(GLOBAL PROPERTY USE_FOLDERS ON) # This sets the C++ standard to c++14 and required for all the following targets that we define. # It has no effect on MSVC though - we thus define more specific requirements for each executable target respectively. # Also it will not apply to the eos library target, since it is an INTERFACE_LIBRARY, and these properties do not apply to interface libraries. set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # This makes CMake use -std=c++11 instead of -std=gnu++11 # This list is likely not complete, but it should be sufficient to error out on old compilers that we cannot build on: set(eos-model-viewer_CXX_COMPILE_FEATURES cxx_defaulted_functions cxx_generalized_initializers cxx_generic_lambdas cxx_lambdas cxx_nonstatic_member_init cxx_range_for cxx_right_angle_brackets cxx_strong_enums) # Build a CPack driven installer package: include(InstallRequiredSystemLibraries) # This module will include any runtime libraries that are needed by the project for the current platform set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_PACKAGE_VERSION_MAJOR "${eos-model-viewer_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MINOR "${eos-model-viewer_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_PATCH "${eos-model-viewer_VERSION_PATCH}") include(CPack) # Find dependencies: find_package(OpenCV REQUIRED core) #check installed version in order to include the correct OpenCV libraries #version variable is defined from project root's CMakeLists if("${OpenCV_VERSION_MAJOR}$" EQUAL 2) message(STATUS "OpenCV 2.x detected") find_package(OpenCV 2.4.3 REQUIRED core) elseif("${OpenCV_VERSION_MAJOR}$" EQUAL 3) message(STATUS "OpenCV 3.x detected - including imgcodecs for compatibility") find_package(OpenCV 3 REQUIRED core) endif() # This allows us to compile in RelWithDebInfo. It'll use the Release-version of OpenCV: set_target_properties(${OpenCV_LIBS} PROPERTIES MAP_IMPORTED_CONFIG_RELWITHDEBINFO RELEASE) # Include eos. Todo: We should really rather use an import target approach rather than running add_subdirectory here. set(eos_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/eos") set(EOS_BUILD_EXAMPLES OFF) add_subdirectory(${eos_DIR}) ######### The following will find libigl, and build everything that's required for the viewer - e.g. GLFW: set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(LIBIGL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/libigl/include") option(LIBIGL_USE_STATIC_LIBRARY "Use libigl as static library" OFF) option(LIBIGL_WITH_ANTTWEAKBAR "Use AntTweakBar" OFF) option(LIBIGL_WITH_CGAL "Use CGAL" OFF) option(LIBIGL_WITH_COMISO "Use CoMiso" OFF) option(LIBIGL_WITH_CORK "Use Cork" OFF) option(LIBIGL_WITH_EMBREE "Use Embree" OFF) option(LIBIGL_WITH_LIM "Use LIM" OFF) option(LIBIGL_WITH_MATLAB "Use Matlab" OFF) option(LIBIGL_WITH_MOSEK "Use MOSEK" OFF) option(LIBIGL_WITH_OPENGL "Use OpenGL" ON) option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" ON) option(LIBIGL_WITH_OPENGL_GLFW_IMGUI "Use ImGui" ON) option(LIBIGL_WITH_PNG "Use PNG" OFF) option(LIBIGL_WITH_PYTHON "Use Python" OFF) option(LIBIGL_WITH_TETGEN "Use Tetgen" OFF) option(LIBIGL_WITH_TRIANGLE "Use Triangle" OFF) option(LIBIGL_WITH_VIEWER "Use OpenGL viewer" ON) option(LIBIGL_WITH_XML "Use XML" OFF) find_package(LIBIGL REQUIRED) ######### libigl should be set up by now. # Set up the eos-model-viewer target: add_executable(eos-model-viewer eos-model-viewer.cpp cxxopts.hpp) #target_include_directories(eos-model-viewer PRIVATE ${LIBIGL_INCLUDE_DIRS}) #add_definitions(${LIBIGL_DEFINITIONS}) target_compile_features(eos-model-viewer PRIVATE ${eos-model-viewer_CXX_COMPILE_FEATURES}) target_link_libraries(eos-model-viewer eos ${OpenCV_LIBS} igl::core igl::opengl_glfw igl::opengl_glfw_imgui) target_link_libraries(eos-model-viewer "$<$:-pthread>$<$:-pthreads>") # Install the binary: install(TARGETS eos-model-viewer DESTINATION bin) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: README.md ================================================ # eos-model-viewer 3D model viewer for the eos Morphable Model library This is a viewer that displays Morphable Models from the eos Morphable Model library ([github.com/patrikhuber/eos](https://github.com/patrikhuber/eos)). It allows to play around with the shape and colour PCA models as well as the blendshapes. ![Screenshot of the viewer](https://github.com/patrikhuber/eos-model-viewer/blob/master/doc/viewer_screenshot.png) ## Build & installation It uses libigl's 3D viewer. **Note:** The viewer works well, but the code is not very polished and will crash if you do unexpected things (e.g. cancel the loading dialogues). The CMake scripts are in serious alpha-stage - You are on your own compiling it! Make sure to clone the repository with `--recursive`, or, if already cloned, run `git submodule update --init --recursive`. ## Running the viewer The viewer can be given a `-m` and `-b` options to open a specific model and blendshapes. If you don't specify these options, a GUI file dialog will pop up, asking you to first select the model, and then the blendshapes file. ================================================ FILE: cmake/FindLIBIGL.cmake ================================================ # - Try to find the LIBIGL library # Once done this will define # # LIBIGL_FOUND - system has LIBIGL # LIBIGL_INCLUDE_DIR - **the** LIBIGL include directory if(LIBIGL_FOUND) return() endif() find_path(LIBIGL_INCLUDE_DIR igl/readOBJ.h HINTS ENV LIBIGL ENV LIBIGLROOT ENV LIBIGL_ROOT ENV LIBIGL_DIR PATHS ${CMAKE_SOURCE_DIR}/../.. ${CMAKE_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/libigl ${CMAKE_SOURCE_DIR}/../libigl ${CMAKE_SOURCE_DIR}/../../libigl /usr /usr/local /usr/local/igl/libigl PATH_SUFFIXES include ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LIBIGL "\nlibigl not found --- You can download it using:\n\tgit clone --recursive https://github.com/libigl/libigl.git ${CMAKE_SOURCE_DIR}/../libigl" LIBIGL_INCLUDE_DIR) mark_as_advanced(LIBIGL_INCLUDE_DIR) list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") include(libigl) ================================================ FILE: cxxopts.hpp ================================================ /* Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck 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. */ #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cpp_lib_optional #include #define CXXOPTS_HAS_OPTIONAL #endif namespace cxxopts { static constexpr struct { uint8_t major, minor, patch; } version = {2, 1, 0}; } //when we ask cxxopts to use Unicode, help strings are processed using ICU, //which results in the correct lengths being computed for strings when they //are formatted for the help output //it is necessary to make sure that can be found by the //compiler, and that icu-uc is linked in to the binary. #ifdef CXXOPTS_USE_UNICODE #include namespace cxxopts { typedef icu::UnicodeString String; inline String toLocalString(std::string s) { return icu::UnicodeString::fromUTF8(std::move(s)); } class UnicodeStringIterator : public std::iterator { public: UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) : s(string) , i(pos) { } value_type operator*() const { return s->char32At(i); } bool operator==(const UnicodeStringIterator& rhs) const { return s == rhs.s && i == rhs.i; } bool operator!=(const UnicodeStringIterator& rhs) const { return !(*this == rhs); } UnicodeStringIterator& operator++() { ++i; return *this; } UnicodeStringIterator operator+(int32_t v) { return UnicodeStringIterator(s, i + v); } private: const icu::UnicodeString* s; int32_t i; }; inline String& stringAppend(String&s, String a) { return s.append(std::move(a)); } inline String& stringAppend(String& s, int n, UChar32 c) { for (int i = 0; i != n; ++i) { s.append(c); } return s; } template String& stringAppend(String& s, Iterator begin, Iterator end) { while (begin != end) { s.append(*begin); ++begin; } return s; } inline size_t stringLength(const String& s) { return s.length(); } inline std::string toUTF8String(const String& s) { std::string result; s.toUTF8String(result); return result; } inline bool empty(const String& s) { return s.isEmpty(); } } namespace std { inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) { return cxxopts::UnicodeStringIterator(&s, 0); } inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s) { return cxxopts::UnicodeStringIterator(&s, s.length()); } } //ifdef CXXOPTS_USE_UNICODE #else namespace cxxopts { typedef std::string String; template T toLocalString(T&& t) { return t; } inline size_t stringLength(const String& s) { return s.length(); } inline String& stringAppend(String&s, String a) { return s.append(std::move(a)); } inline String& stringAppend(String& s, size_t n, char c) { return s.append(n, c); } template String& stringAppend(String& s, Iterator begin, Iterator end) { return s.append(begin, end); } template std::string toUTF8String(T&& t) { return std::forward(t); } inline bool empty(const std::string& s) { return s.empty(); } } //ifdef CXXOPTS_USE_UNICODE #endif namespace cxxopts { namespace { #ifdef _WIN32 const std::string LQUOTE("\'"); const std::string RQUOTE("\'"); #else const std::string LQUOTE("‘"); const std::string RQUOTE("’"); #endif } class Value : public std::enable_shared_from_this { public: virtual ~Value() = default; virtual std::shared_ptr clone() const = 0; virtual void parse(const std::string& text) const = 0; virtual void parse() const = 0; virtual bool has_default() const = 0; virtual bool is_container() const = 0; virtual bool has_implicit() const = 0; virtual std::string get_default_value() const = 0; virtual std::string get_implicit_value() const = 0; virtual std::shared_ptr default_value(const std::string& value) = 0; virtual std::shared_ptr implicit_value(const std::string& value) = 0; virtual bool is_boolean() const = 0; }; class OptionException : public std::exception { public: OptionException(const std::string& message) : m_message(message) { } virtual const char* what() const noexcept { return m_message.c_str(); } private: std::string m_message; }; class OptionSpecException : public OptionException { public: OptionSpecException(const std::string& message) : OptionException(message) { } }; class OptionParseException : public OptionException { public: OptionParseException(const std::string& message) : OptionException(message) { } }; class option_exists_error : public OptionSpecException { public: option_exists_error(const std::string& option) : OptionSpecException(u8"Option " + LQUOTE + option + RQUOTE + u8" already exists") { } }; class invalid_option_format_error : public OptionSpecException { public: invalid_option_format_error(const std::string& format) : OptionSpecException(u8"Invalid option format " + LQUOTE + format + RQUOTE) { } }; class option_not_exists_exception : public OptionParseException { public: option_not_exists_exception(const std::string& option) : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" does not exist") { } }; class missing_argument_exception : public OptionParseException { public: missing_argument_exception(const std::string& option) : OptionParseException( u8"Option " + LQUOTE + option + RQUOTE + u8" is missing an argument" ) { } }; class option_requires_argument_exception : public OptionParseException { public: option_requires_argument_exception(const std::string& option) : OptionParseException( u8"Option " + LQUOTE + option + RQUOTE + u8" requires an argument" ) { } }; class option_not_has_argument_exception : public OptionParseException { public: option_not_has_argument_exception ( const std::string& option, const std::string& arg ) : OptionParseException( u8"Option " + LQUOTE + option + RQUOTE + u8" does not take an argument, but argument " + LQUOTE + arg + RQUOTE + " given" ) { } }; class option_not_present_exception : public OptionParseException { public: option_not_present_exception(const std::string& option) : OptionParseException(u8"Option " + LQUOTE + option + RQUOTE + u8" not present") { } }; class argument_incorrect_type : public OptionParseException { public: argument_incorrect_type ( const std::string& arg ) : OptionParseException( u8"Argument " + LQUOTE + arg + RQUOTE + u8" failed to parse" ) { } }; class option_required_exception : public OptionParseException { public: option_required_exception(const std::string& option) : OptionParseException( u8"Option " + LQUOTE + option + RQUOTE + u8" is required but not present" ) { } }; namespace values { namespace { std::basic_regex integer_pattern ("(-)?(0x)?([1-9a-zA-Z][0-9a-zA-Z]*)|((0x)?0)"); std::basic_regex truthy_pattern ("(t|T)(rue)?"); std::basic_regex falsy_pattern ("((f|F)(alse)?)?"); } namespace detail { template struct SignedCheck; template struct SignedCheck { template void operator()(bool negative, U u, const std::string& text) { if (negative) { if (u > static_cast(-std::numeric_limits::min())) { throw argument_incorrect_type(text); } } else { if (u > static_cast(std::numeric_limits::max())) { throw argument_incorrect_type(text); } } } }; template struct SignedCheck { template void operator()(bool, U, const std::string&) {} }; template void check_signed_range(bool negative, U value, const std::string& text) { SignedCheck::is_signed>()(negative, value, text); } } template R checked_negate(T&& t, const std::string&, std::true_type) { // if we got to here, then `t` is a positive number that fits into // `R`. So to avoid MSVC C4146, we first cast it to `R`. // See https://github.com/jarro2783/cxxopts/issues/62 for more details. return -static_cast(t); } template T checked_negate(T&&, const std::string& text, std::false_type) { throw argument_incorrect_type(text); } template void integer_parser(const std::string& text, T& value) { std::smatch match; std::regex_match(text, match, integer_pattern); if (match.length() == 0) { throw argument_incorrect_type(text); } if (match.length(4) > 0) { value = 0; return; } using US = typename std::make_unsigned::type; constexpr auto umax = std::numeric_limits::max(); constexpr bool is_signed = std::numeric_limits::is_signed; const bool negative = match.length(1) > 0; const uint8_t base = match.length(2) > 0 ? 16 : 10; auto value_match = match[3]; US result = 0; for (auto iter = value_match.first; iter != value_match.second; ++iter) { size_t digit = 0; if (*iter >= '0' && *iter <= '9') { digit = *iter - '0'; } else if (base == 16 && *iter >= 'a' && *iter <= 'f') { digit = *iter - 'a' + 10; } else if (base == 16 && *iter >= 'A' && *iter <= 'F') { digit = *iter - 'A' + 10; } else { throw argument_incorrect_type(text); } if (umax - digit < result * base) { throw argument_incorrect_type(text); } result = result * base + digit; } detail::check_signed_range(negative, result, text); if (negative) { value = checked_negate(result, text, std::integral_constant()); } else { value = result; } } template void stringstream_parser(const std::string& text, T& value) { std::stringstream in(text); in >> value; if (!in) { throw argument_incorrect_type(text); } } inline void parse_value(const std::string& text, uint8_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, int8_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, uint16_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, int16_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, uint32_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, int32_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, uint64_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, int64_t& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, bool& value) { std::smatch result; std::regex_match(text, result, truthy_pattern); if (!result.empty()) { value = true; return; } std::regex_match(text, result, falsy_pattern); if (!result.empty()) { value = false; return; } throw argument_incorrect_type(text); } inline void parse_value(const std::string& text, std::string& value) { value = text; } // The fallback parser. It uses the stringstream parser to parse all types // that have not been overloaded explicitly. It has to be placed in the // source code before all other more specialized templates. template void parse_value(const std::string& text, T& value) { stringstream_parser(text, value); } template void parse_value(const std::string& text, std::vector& value) { T v; parse_value(text, v); value.push_back(v); } #ifdef CXXOPTS_HAS_OPTIONAL template void parse_value(const std::string& text, std::optional& value) { T result; parse_value(text, result); value = std::move(result); } #endif template struct type_is_container { static constexpr bool value = false; }; template struct type_is_container> { static constexpr bool value = true; }; template class abstract_value : public Value { using Self = abstract_value; public: abstract_value() : m_result(std::make_shared()) , m_store(m_result.get()) { } abstract_value(T* t) : m_store(t) { } virtual ~abstract_value() = default; abstract_value(const abstract_value& rhs) { if (rhs.m_result) { m_result = std::make_shared(); m_store = m_result.get(); } else { m_store = rhs.m_store; } m_default = rhs.m_default; m_implicit = rhs.m_implicit; m_default_value = rhs.m_default_value; m_implicit_value = rhs.m_implicit_value; } void parse(const std::string& text) const { parse_value(text, *m_store); } bool is_container() const { return type_is_container::value; } void parse() const { parse_value(m_default_value, *m_store); } bool has_default() const { return m_default; } bool has_implicit() const { return m_implicit; } std::shared_ptr default_value(const std::string& value) { m_default = true; m_default_value = value; return shared_from_this(); } std::shared_ptr implicit_value(const std::string& value) { m_implicit = true; m_implicit_value = value; return shared_from_this(); } std::string get_default_value() const { return m_default_value; } std::string get_implicit_value() const { return m_implicit_value; } bool is_boolean() const { return std::is_same::value; } const T& get() const { if (m_store == nullptr) { return *m_result; } else { return *m_store; } } protected: std::shared_ptr m_result; T* m_store; bool m_default = false; bool m_implicit = false; std::string m_default_value; std::string m_implicit_value; }; template class standard_value : public abstract_value { public: using abstract_value::abstract_value; std::shared_ptr clone() const { return std::make_shared>(*this); } }; template <> class standard_value : public abstract_value { public: ~standard_value() = default; standard_value() { set_default_and_implicit(); } standard_value(bool* b) : abstract_value(b) { set_default_and_implicit(); } std::shared_ptr clone() const { return std::make_shared>(*this); } private: void set_default_and_implicit() { m_default = true; m_default_value = "false"; m_implicit = true; m_implicit_value = "true"; } }; } template std::shared_ptr value() { return std::make_shared>(); } template std::shared_ptr value(T& t) { return std::make_shared>(&t); } class OptionAdder; class OptionDetails { public: OptionDetails ( const std::string& short_, const std::string& long_, const String& desc, std::shared_ptr val ) : m_short(short_) , m_long(long_) , m_desc(desc) , m_value(val) , m_count(0) { } OptionDetails(const OptionDetails& rhs) : m_desc(rhs.m_desc) , m_count(rhs.m_count) { m_value = rhs.m_value->clone(); } OptionDetails(OptionDetails&& rhs) = default; const String& description() const { return m_desc; } const Value& value() const { return *m_value; } std::shared_ptr make_storage() const { return m_value->clone(); } const std::string& short_name() const { return m_short; } const std::string& long_name() const { return m_long; } private: std::string m_short; std::string m_long; String m_desc; std::shared_ptr m_value; int m_count; }; struct HelpOptionDetails { std::string s; std::string l; String desc; bool has_default; std::string default_value; bool has_implicit; std::string implicit_value; std::string arg_help; bool is_container; bool is_boolean; }; struct HelpGroupDetails { std::string name; std::string description; std::vector options; }; class OptionValue { public: void parse ( std::shared_ptr details, const std::string& text ) { ensure_value(details); ++m_count; m_value->parse(text); } void parse_default(std::shared_ptr details) { ensure_value(details); m_value->parse(); } size_t count() const { return m_count; } template const T& as() const { #ifdef CXXOPTS_NO_RTTI return static_cast&>(*m_value).get(); #else return dynamic_cast&>(*m_value).get(); #endif } private: void ensure_value(std::shared_ptr details) { if (m_value == nullptr) { m_value = details->make_storage(); } } std::shared_ptr m_value; size_t m_count = 0; }; class KeyValue { public: KeyValue(std::string key_, std::string value_) : m_key(std::move(key_)) , m_value(std::move(value_)) { } const std::string& key() const { return m_key; } const std::string value() const { return m_value; } template T as() const { T result; values::parse_value(m_value, result); return result; } private: std::string m_key; std::string m_value; }; class ParseResult { public: ParseResult( const std::unordered_map>&, std::vector, int&, const char**&); size_t count(const std::string& o) const { auto iter = m_options.find(o); if (iter == m_options.end()) { return 0; } auto riter = m_results.find(iter->second); return riter->second.count(); } const OptionValue& operator[](const std::string& option) const { auto iter = m_options.find(option); if (iter == m_options.end()) { throw option_not_present_exception(option); } auto riter = m_results.find(iter->second); return riter->second; } const std::vector& arguments() const { return m_sequential; } private: OptionValue& get_option(std::shared_ptr); void parse(int& argc, const char**& argv); void add_to_option(const std::string& option, const std::string& arg); bool consume_positional(std::string a); void parse_option ( std::shared_ptr value, const std::string& name, const std::string& arg = "" ); void parse_default(std::shared_ptr details); void checked_parse_arg ( int argc, const char* argv[], int& current, std::shared_ptr value, const std::string& name ); const std::unordered_map> &m_options; std::vector m_positional; std::vector::iterator m_next_positional; std::unordered_set m_positional_set; std::unordered_map, OptionValue> m_results; std::vector m_sequential; }; class Options { public: Options(std::string program, std::string help_string = "") : m_program(std::move(program)) , m_help_string(toLocalString(std::move(help_string))) , m_custom_help("[OPTION...]") , m_positional_help("positional parameters") , m_show_positional(false) , m_next_positional(m_positional.end()) { } Options& positional_help(std::string help_text) { m_positional_help = std::move(help_text); return *this; } Options& custom_help(std::string help_text) { m_custom_help = std::move(help_text); return *this; } Options& show_positional_help() { m_show_positional = true; return *this; } ParseResult parse(int& argc, const char**& argv); OptionAdder add_options(std::string group = ""); void add_option ( const std::string& group, const std::string& s, const std::string& l, std::string desc, std::shared_ptr value, std::string arg_help ); //parse positional arguments into the given option void parse_positional(std::string option); void parse_positional(std::vector options); void parse_positional(std::initializer_list options); std::string help(const std::vector& groups = {""}) const; const std::vector groups() const; const HelpGroupDetails& group_help(const std::string& group) const; private: void add_one_option ( const std::string& option, std::shared_ptr details ); String help_one_group(const std::string& group) const; void generate_group_help ( String& result, const std::vector& groups ) const; void generate_all_groups_help(String& result) const; std::string m_program; String m_help_string; std::string m_custom_help; std::string m_positional_help; bool m_show_positional; std::unordered_map> m_options; std::vector m_positional; std::vector::iterator m_next_positional; std::unordered_set m_positional_set; //mapping from groups to help options std::map m_help; }; class OptionAdder { public: OptionAdder(Options& options, std::string group) : m_options(options), m_group(std::move(group)) { } OptionAdder& operator() ( const std::string& opts, const std::string& desc, std::shared_ptr value = ::cxxopts::value(), std::string arg_help = "" ); private: Options& m_options; std::string m_group; }; namespace { constexpr int OPTION_LONGEST = 30; constexpr int OPTION_DESC_GAP = 2; std::basic_regex option_matcher ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); std::basic_regex option_specifier ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); String format_option ( const HelpOptionDetails& o ) { auto& s = o.s; auto& l = o.l; String result = " "; if (s.size() > 0) { result += "-" + toLocalString(s) + ","; } else { result += " "; } if (l.size() > 0) { result += " --" + toLocalString(l); } auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; if (!o.is_boolean) { if (o.has_implicit) { result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; } else { result += " " + arg; } } return result; } String format_description ( const HelpOptionDetails& o, size_t start, size_t width ) { auto desc = o.desc; if (o.has_default && (!o.is_boolean || o.default_value != "false")) { desc += toLocalString(" (default: " + o.default_value + ")"); } String result; auto current = std::begin(desc); auto startLine = current; auto lastSpace = current; auto size = size_t{}; while (current != std::end(desc)) { if (*current == ' ') { lastSpace = current; } if (size > width) { if (lastSpace == startLine) { stringAppend(result, startLine, current + 1); stringAppend(result, "\n"); stringAppend(result, start, ' '); startLine = current + 1; lastSpace = startLine; } else { stringAppend(result, startLine, lastSpace); stringAppend(result, "\n"); stringAppend(result, start, ' '); startLine = lastSpace + 1; } size = 0; } else { ++size; } ++current; } //append whatever is left stringAppend(result, startLine, current); return result; } } inline ParseResult::ParseResult ( const std::unordered_map>& options, std::vector positional, int& argc, const char**& argv ) : m_options(options) , m_positional(std::move(positional)) , m_next_positional(m_positional.begin()) { parse(argc, argv); } inline OptionAdder Options::add_options(std::string group) { return OptionAdder(*this, std::move(group)); } inline OptionAdder& OptionAdder::operator() ( const std::string& opts, const std::string& desc, std::shared_ptr value, std::string arg_help ) { std::match_results result; std::regex_match(opts.c_str(), result, option_specifier); if (result.empty()) { throw invalid_option_format_error(opts); } const auto& short_match = result[2]; const auto& long_match = result[3]; if (!short_match.length() && !long_match.length()) { throw invalid_option_format_error(opts); } else if (long_match.length() == 1 && short_match.length()) { throw invalid_option_format_error(opts); } auto option_names = [] ( const std::sub_match& short_, const std::sub_match& long_ ) { if (long_.length() == 1) { return std::make_tuple(long_.str(), short_.str()); } else { return std::make_tuple(short_.str(), long_.str()); } }(short_match, long_match); m_options.add_option ( m_group, std::get<0>(option_names), std::get<1>(option_names), desc, value, std::move(arg_help) ); return *this; } inline void ParseResult::parse_default(std::shared_ptr details) { m_results[details].parse_default(details); } inline void ParseResult::parse_option ( std::shared_ptr value, const std::string& /*name*/, const std::string& arg ) { auto& result = m_results[value]; result.parse(value, arg); m_sequential.emplace_back(value->long_name(), arg); } inline void ParseResult::checked_parse_arg ( int argc, const char* argv[], int& current, std::shared_ptr value, const std::string& name ) { if (current + 1 >= argc) { if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); } else { throw missing_argument_exception(name); } } else { if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); } else { parse_option(value, name, argv[current + 1]); ++current; } } } inline void ParseResult::add_to_option(const std::string& option, const std::string& arg) { auto iter = m_options.find(option); if (iter == m_options.end()) { throw option_not_exists_exception(option); } parse_option(iter->second, option, arg); } inline bool ParseResult::consume_positional(std::string a) { while (m_next_positional != m_positional.end()) { auto iter = m_options.find(*m_next_positional); if (iter != m_options.end()) { auto& result = m_results[iter->second]; if (!iter->second->value().is_container()) { if (result.count() == 0) { add_to_option(*m_next_positional, a); ++m_next_positional; return true; } else { ++m_next_positional; continue; } } else { add_to_option(*m_next_positional, a); return true; } } ++m_next_positional; } return false; } inline void Options::parse_positional(std::string option) { parse_positional(std::vector{std::move(option)}); } inline void Options::parse_positional(std::vector options) { m_positional = std::move(options); m_next_positional = m_positional.begin(); m_positional_set.insert(m_positional.begin(), m_positional.end()); } inline void Options::parse_positional(std::initializer_list options) { parse_positional(std::vector(std::move(options))); } inline ParseResult Options::parse(int& argc, const char**& argv) { ParseResult result(m_options, m_positional, argc, argv); return result; } inline void ParseResult::parse(int& argc, const char**& argv) { int current = 1; int nextKeep = 1; bool consume_remaining = false; while (current != argc) { if (strcmp(argv[current], "--") == 0) { consume_remaining = true; ++current; break; } std::match_results result; std::regex_match(argv[current], result, option_matcher); if (result.empty()) { //not a flag //if true is returned here then it was consumed, otherwise it is //ignored if (consume_positional(argv[current])) { } else { argv[nextKeep] = argv[current]; ++nextKeep; } //if we return from here then it was parsed successfully, so continue } else { //short or long option? if (result[4].length() != 0) { const std::string& s = result[4]; for (std::size_t i = 0; i != s.size(); ++i) { std::string name(1, s[i]); auto iter = m_options.find(name); if (iter == m_options.end()) { throw option_not_exists_exception(name); } auto value = iter->second; if (i + 1 == s.size()) { //it must be the last argument checked_parse_arg(argc, argv, current, value, name); } else if (value->value().has_implicit()) { parse_option(value, name, value->value().get_implicit_value()); } else { //error throw option_requires_argument_exception(name); } } } else if (result[1].length() != 0) { const std::string& name = result[1]; auto iter = m_options.find(name); if (iter == m_options.end()) { throw option_not_exists_exception(name); } auto opt = iter->second; //equals provided for long option? if (result[2].length() != 0) { //parse the option given parse_option(opt, name, result[3]); } else { //parse the next argument checked_parse_arg(argc, argv, current, opt, name); } } } ++current; } for (auto& opt : m_options) { auto& detail = opt.second; auto& value = detail->value(); auto& store = m_results[detail]; if(!store.count() && value.has_default()){ parse_default(detail); } } if (consume_remaining) { while (current < argc) { if (!consume_positional(argv[current])) { break; } ++current; } //adjust argv for any that couldn't be swallowed while (current != argc) { argv[nextKeep] = argv[current]; ++nextKeep; ++current; } } argc = nextKeep; } inline void Options::add_option ( const std::string& group, const std::string& s, const std::string& l, std::string desc, std::shared_ptr value, std::string arg_help ) { auto stringDesc = toLocalString(std::move(desc)); auto option = std::make_shared(s, l, stringDesc, value); if (s.size() > 0) { add_one_option(s, option); } if (l.size() > 0) { add_one_option(l, option); } //add the help details auto& options = m_help[group]; options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, value->has_default(), value->get_default_value(), value->has_implicit(), value->get_implicit_value(), std::move(arg_help), value->is_container(), value->is_boolean()}); } inline void Options::add_one_option ( const std::string& option, std::shared_ptr details ) { auto in = m_options.emplace(option, details); if (!in.second) { throw option_exists_error(option); } } inline String Options::help_one_group(const std::string& g) const { typedef std::vector> OptionHelp; auto group = m_help.find(g); if (group == m_help.end()) { return ""; } OptionHelp format; size_t longest = 0; String result; if (!g.empty()) { result += toLocalString(" " + g + " options:\n"); } for (const auto& o : group->second.options) { if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end() && !m_show_positional) { continue; } auto s = format_option(o); longest = std::max(longest, stringLength(s)); format.push_back(std::make_pair(s, String())); } longest = std::min(longest, static_cast(OPTION_LONGEST)); //widest allowed description auto allowed = size_t{76} - longest - OPTION_DESC_GAP; auto fiter = format.begin(); for (const auto& o : group->second.options) { if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end() && !m_show_positional) { continue; } auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); result += fiter->first; if (stringLength(fiter->first) > longest) { result += '\n'; result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); } else { result += toLocalString(std::string(longest + OPTION_DESC_GAP - stringLength(fiter->first), ' ')); } result += d; result += '\n'; ++fiter; } return result; } inline void Options::generate_group_help ( String& result, const std::vector& print_groups ) const { for (size_t i = 0; i != print_groups.size(); ++i) { const String& group_help_text = help_one_group(print_groups[i]); if (empty(group_help_text)) { continue; } result += group_help_text; if (i < print_groups.size() - 1) { result += '\n'; } } } inline void Options::generate_all_groups_help(String& result) const { std::vector all_groups; all_groups.reserve(m_help.size()); for (auto& group : m_help) { all_groups.push_back(group.first); } generate_group_help(result, all_groups); } inline std::string Options::help(const std::vector& help_groups) const { String result = m_help_string + "\nUsage:\n " + toLocalString(m_program) + " " + toLocalString(m_custom_help); if (m_positional.size() > 0 && m_positional_help.size() > 0) { result += " " + toLocalString(m_positional_help); } result += "\n\n"; if (help_groups.size() == 0) { generate_all_groups_help(result); } else { generate_group_help(result, help_groups); } return toUTF8String(result); } inline const std::vector Options::groups() const { std::vector g; std::transform( m_help.begin(), m_help.end(), std::back_inserter(g), [] (const std::map::value_type& pair) { return pair.first; } ); return g; } inline const HelpGroupDetails& Options::group_help(const std::string& group) const { return m_help.at(group); } } #endif //CXXOPTS_HPP_INCLUDED ================================================ FILE: eos-model-viewer.cpp ================================================ /* * eos - A 3D Morphable Model fitting library written in modern C++11/14. * * File: eos-model-viewer.cpp * * Copyright 2017, 2018 Patrik Huber * * 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. */ #include "cxxopts.hpp" #include "eos/core/Mesh.hpp" #include "eos/morphablemodel/MorphableModel.hpp" #include "eos/morphablemodel/io/cvssp.hpp" #include "eos/morphablemodel/Blendshape.hpp" #include "eos/cpp17/variant.hpp" #include "igl/opengl/glfw/Viewer.h" #include "igl/opengl/glfw/imgui/ImGuiMenu.h" #include "igl/opengl/glfw/imgui/ImGuiHelpers.h" #include "imgui/imgui.h" #include #include #include #include #include template std::string to_string(const T a_value, const int n = 6) { std::ostringstream out; out << std::setprecision(n) << a_value; return out.str(); }; // From: https://stackoverflow.com/a/1493195/1345959 template void tokenize(const std::string& str, ContainerType& tokens, const std::string& delimiters = " ", bool trim_empty = false) { std::string::size_type pos, last_pos = 0; const auto length = str.length(); using value_type = typename ContainerType::value_type; using size_type = typename ContainerType::size_type; while (last_pos < length + 1) { pos = str.find_first_of(delimiters, last_pos); if (pos == std::string::npos) { pos = length; } if (pos != last_pos || !trim_empty) tokens.push_back(value_type(str.data() + last_pos, (size_type)pos - last_pos)); last_pos = pos + 1; } }; eos::morphablemodel::MorphableModel load_bin_or_scm_model(std::string model_file) { using namespace eos; using eos::morphablemodel::MorphableModel; MorphableModel morphable_model; std::vector tokens; tokenize(model_file, tokens, "."); const auto model_file_extension = tokens.back(); // Todo: Add try-catch to all these? if (model_file_extension == "scm") { morphable_model = morphablemodel::load_scm_model(model_file); } else if (model_file_extension == "bin") { morphable_model = morphablemodel::load_model(model_file); } else { throw std::runtime_error("Error: Please load a model with .bin or .scm extension."); } return morphable_model; } /** * */ eos::morphablemodel::MorphableModel load_model(std::string model_file, std::string blendshapes_file) { using namespace eos; using eos::morphablemodel::MorphableModel; MorphableModel morphable_model; // Todo: Add try-catch to all these? morphable_model = load_bin_or_scm_model(model_file); // If separate blendshapes are given, load them, and construct a model with expressions: if (!blendshapes_file.empty()) { const auto blendshapes = morphablemodel::load_blendshapes(blendshapes_file); morphable_model = MorphableModel( morphable_model.get_shape_model(), blendshapes, morphable_model.get_color_model(), morphable_model.get_landmark_definitions(), morphable_model.get_texture_coordinates()); } return morphable_model; }; /** * Model viewer for 3D Morphable Models. */ int main(int argc, const char* argv[]) { using namespace eos; using Eigen::VectorXf; using std::begin; using std::cout; using std::end; using std::endl; using std::for_each; using std::string; using std::vector; string model_file, blendshapes_file; try { cxxopts::Options options("eos-model-viewer", "OpenGL viewer for eos's 3D morphable models."); // clang-format off options.add_options() ("h,help", "display the help message") ("m,model", "an eos 3D Morphable Model stored as cereal BinaryArchive (.bin)", cxxopts::value(model_file)) ("b,blendshapes", "an eos file with blendshapes (.bin)", cxxopts::value(blendshapes_file)); // clang-format on const auto result = options.parse(argc, argv); if (result.count("help")) { cout << options.help() << endl; return EXIT_SUCCESS; } } catch (const cxxopts::OptionException& e) { cout << "Error parsing options: " << e.what() << endl; return EXIT_FAILURE; } // Note: Take the vertices here directly, so we can maybe avoid ever generating a Mesh instance auto get_V = [](const core::Mesh& mesh) { Eigen::MatrixXd V(mesh.vertices.size(), 3); for (int i = 0; i < mesh.vertices.size(); ++i) { V(i, 0) = mesh.vertices[i](0); V(i, 1) = mesh.vertices[i](1); V(i, 2) = mesh.vertices[i](2); } return V; }; auto get_F = [](const core::Mesh& mesh) { Eigen::MatrixXi F(mesh.tvi.size(), 3); for (int i = 0; i < mesh.tvi.size(); ++i) { F(i, 0) = mesh.tvi[i][0]; F(i, 1) = mesh.tvi[i][1]; F(i, 2) = mesh.tvi[i][2]; } return F; }; auto get_C = [](const core::Mesh& mesh) { Eigen::MatrixXd C(mesh.colors.size(), 3); for (int i = 0; i < mesh.colors.size(); ++i) { C(i, 0) = mesh.colors[i](0); C(i, 1) = mesh.colors[i](1); C(i, 2) = mesh.colors[i](2); } return C; }; // Init the viewer: igl::opengl::glfw::Viewer viewer; // Attach a menu plugin: igl::opengl::glfw::imgui::ImGuiMenu menu; viewer.plugins.push_back(&menu); morphablemodel::MorphableModel morphable_model; // Load the model right away on start up, if it was given via command-line parameters: if (!model_file.empty()) { try { // Loads a .bin or .scm model, with or without blendshapes: morphable_model = load_model(model_file, blendshapes_file); const auto& mean = morphable_model.get_mean(); viewer.data().set_mesh(get_V(mean), get_F(mean)); viewer.core.align_camera_center(viewer.data().V, viewer.data().F); if (!mean.colors.empty()) { viewer.data().set_colors(get_C(mean)); } } catch (const std::runtime_error& e) { cout << "Error loading the given model: " << e.what() << endl; return EXIT_FAILURE; } } // These are the coefficients of the currently active mesh instance: vector shape_coefficients; vector color_coefficients; vector expression_coefficients; bool display_identity_model_only = true; std::default_random_engine rng; std::array random_sample_sdev = {1.0f, 1.0f, 1.0f}; // shp, exp, col // Draw our viewers windows: menu.callback_draw_custom_window = [&]() { // Load model & draw sample options: ImGui::SetNextWindowPos(ImVec2(0.f * menu.menu_scaling(), 585), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(240, 280), ImGuiSetCond_FirstUseEver); ImGui::Begin("Morphable Model", nullptr, ImGuiWindowFlags_NoSavedSettings); if (ImGui::Button("Load Morphable Model", ImVec2(-1, 0))) { const string mm_fn = igl::file_dialog_open(); cout << "Loading Morphable Model " << mm_fn << "..." << endl; try { morphable_model = load_bin_or_scm_model(mm_fn); const auto& mean = morphable_model.get_mean(); viewer.data().clear(); viewer.data().set_mesh(get_V(mean), get_F(mean)); viewer.core.align_camera_center(viewer.data().V, viewer.data().F); if (!mean.colors.empty()) { viewer.data().set_colors(get_C(mean)); } } catch (const std::runtime_error& e) // Todo: I think we have to catch more errors here, like cereal exceptions { cout << "Error loading the given model: " << e.what() << endl; } if (morphable_model.has_separate_expression_model()) { // Just a sensible default - if the loaded model has expressions, use them by default: display_identity_model_only = false; } } if (ImGui::Button("Load Blendshapes", ImVec2(-1, 0))) { const string bs_fn = igl::file_dialog_open(); cout << "Loading Blendshapes " << bs_fn << "..." << endl; morphablemodel::Blendshapes blendshapes; try { blendshapes = morphablemodel::load_blendshapes(bs_fn); cout << "Blendshapes loaded. Constructing a new model consisting of the loaded identity and " "colour PCA models, and the loaded blendshapes..." << endl; morphable_model = morphablemodel::MorphableModel( morphable_model.get_shape_model(), blendshapes, morphable_model.get_color_model(), morphable_model.get_landmark_definitions(), morphable_model.get_texture_coordinates()); const auto& mean = morphable_model.get_mean(); viewer.data().clear(); viewer.data().set_mesh(get_V(mean), get_F(mean)); viewer.core.align_camera_center(viewer.data().V, viewer.data().F); if (!mean.colors.empty()) { viewer.data().set_colors(get_C(mean)); } } catch (const std::runtime_error& e) // Todo: I think we have to catch more errors here, like cereal exceptions { cout << "Error loading the given blendshapes: " << e.what() << endl; } display_identity_model_only = false; } ImGui::Separator(); if (ImGui::Button("Mean (id)", ImVec2(-1, 0))) { Eigen::VectorXf mean = morphable_model.get_shape_model().get_mean(); // Take 3 at a piece, then transpose: const auto num_vertices = mean.rows() / 3; Eigen::Map mean_reshaped(mean.data(), 3, num_vertices); viewer.data().set_vertices(mean_reshaped.transpose().cast()); if (morphable_model.get_color_model().get_mean().size() > 0) { Eigen::VectorXf color_mean = morphable_model.get_color_model().get_mean(); Eigen::Map color_mean_reshaped( color_mean.data(), 3, color_mean.rows() / 3); // Todo: This will fail for gray-level models viewer.data().set_colors(color_mean_reshaped.transpose().cast()); } for_each(begin(shape_coefficients), end(shape_coefficients), [](auto& coeff) { coeff = 0.0f; }); for_each(begin(color_coefficients), end(color_coefficients), [](auto& coeff) { coeff = 0.0f; }); for_each(begin(expression_coefficients), end(expression_coefficients), [](auto& coeff) { coeff = 0.0f; }); display_identity_model_only = true; } if (ImGui::Button("Mean (id+exp)", ImVec2(-1, 0))) { const auto mean = morphable_model.get_mean(); viewer.data().set_vertices(get_V(mean)); if (!mean.colors.empty()) { viewer.data().set_colors(get_C(mean)); } for_each(begin(shape_coefficients), end(shape_coefficients), [](auto& coeff) { coeff = 0.0f; }); for_each(begin(color_coefficients), end(color_coefficients), [](auto& coeff) { coeff = 0.0f; }); for_each(begin(expression_coefficients), end(expression_coefficients), [](auto& coeff) { coeff = 0.0f; }); display_identity_model_only = false; } ImGui::Separator(); if (ImGui::Button("Random face sample", ImVec2(-1, 0))) { // Shape sample: std::normal_distribution shape_coeffs_dist(0.0f, random_sample_sdev[0]); // c'tor takes stddev shape_coefficients = vector(morphable_model.get_shape_model().get_num_principal_components()); for_each(begin(shape_coefficients), end(shape_coefficients), [&rng, &shape_coeffs_dist](auto& coeff) { coeff = shape_coeffs_dist(rng); }); // Expression sample: if (morphable_model.has_separate_expression_model()) { if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { std::uniform_real_distribution expression_coeffs_dist( 0.0f, random_sample_sdev[1]); // using the interval [0, sdev] (so it's not really an sdev // here!) const auto expression_model_num_coeffs = eos::cpp17::get( morphable_model.get_expression_model().value()) .size(); expression_coefficients = vector(expression_model_num_coeffs); for_each(begin(expression_coefficients), end(expression_coefficients), [&rng, &expression_coeffs_dist](auto& coeff) { coeff = expression_coeffs_dist(rng); }); } else if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { std::normal_distribution expression_coeffs_dist(0.0f, random_sample_sdev[1]); const auto expression_model_num_coeffs = eos::cpp17::get( morphable_model.get_expression_model().value()) .get_num_principal_components(); expression_coefficients = vector(expression_model_num_coeffs); for_each(begin(expression_coefficients), end(expression_coefficients), [&rng, &expression_coeffs_dist](auto& coeff) { coeff = expression_coeffs_dist(rng); }); } } // Colour sample: std::normal_distribution color_coeffs_dist(0.0f, random_sample_sdev[2]); color_coefficients = vector(morphable_model.get_color_model().get_num_principal_components()); for_each(begin(color_coefficients), end(color_coefficients), [&rng, &color_coeffs_dist](auto& coeff) { coeff = color_coeffs_dist(rng); }); // Now generate the sample: core::Mesh sample; // Note: Can it happen that we pass in colour-coeffs, but it's a model without colour? if (morphable_model.has_separate_expression_model()) { sample = morphable_model.draw_sample(shape_coefficients, expression_coefficients, color_coefficients); } else { sample = morphable_model.draw_sample(shape_coefficients, color_coefficients); } viewer.data().set_vertices(get_V(sample)); if (!sample.colors.empty()) { viewer.data().set_colors(get_C(sample)); } } /* // Not yet implemented: if (ImGui::Button("Random identity sample", ImVec2(-1, 0))) { } if (ImGui::Button("Random expression sample", ImVec2(-1, 0))) { } if (ImGui::Button("Random color sample", ImVec2(-1, 0))) { } */ ImGui::InputFloat3("sdev [shp, exp, col]", &random_sample_sdev[0], 2); ImGui::Checkbox("Identity model only", &display_identity_model_only); ImGui::End(); // end "Morphable Model" window // PCA shape coefficients: ImGui::SetNextWindowPos(ImVec2(180.f * menu.menu_scaling(), 0), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiSetCond_FirstUseEver); ImGui::Begin("Shape PCA", nullptr, ImGuiWindowFlags_NoSavedSettings); ImGui::Text("Coefficients"); if (morphable_model.get_shape_model().get_num_principal_components() > 0) // a model was loaded { int num_shape_coeffs_to_display = std::min(morphable_model.get_shape_model().get_num_principal_components(), 30); if (shape_coefficients.empty() || shape_coefficients.size() != num_shape_coeffs_to_display) { // no coeffs yet, or number of coeffs has changed for some reason: shape_coefficients.resize(num_shape_coeffs_to_display); } // shape_coefficients are always initialised now. ImGui::BeginGroup(); for (int i = 0; i < num_shape_coeffs_to_display; ++i) { const string label = std::to_string(i); ImGui::SliderFloat(label.c_str(), &shape_coefficients[i], -3.0, 3.0); } ImGui::EndGroup(); string coeffs_displayed = "Displaying " + std::to_string(num_shape_coeffs_to_display) + "/" + std::to_string(morphable_model.get_shape_model().get_num_principal_components()) + " coefficients."; ImGui::Text(coeffs_displayed.c_str()); // We've got a shape model. So update the mesh that's been drawn with the value of the // coefficients. Note that we are currently doing this every draw call, not only when the // slider changes. See eos-model-viewer/issues/5. VectorXf shape_instance = morphable_model.get_shape_model().draw_sample(shape_coefficients); const auto num_vertices = morphable_model.get_shape_model().get_data_dimension() / 3; // If expression coeffs are set, add the expression part: if (!expression_coefficients.empty() && morphable_model.has_separate_expression_model() && !display_identity_model_only) { if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { const auto& expression_model = eos::cpp17::get( morphable_model.get_expression_model().value()); const VectorXf expression_instance = expression_model.draw_sample(expression_coefficients); shape_instance += expression_instance; } else if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { const auto& blendshapes = eos::cpp17::get( morphable_model.get_expression_model().value()); for (int i = 0; i < blendshapes.size(); ++i) { shape_instance += blendshapes[i].deformation * expression_coefficients[i]; } } } Eigen::Map shape_instance_reshaped( shape_instance.data(), 3, num_vertices); // Take 3 at a piece, then transpose below viewer.data().set_vertices(shape_instance_reshaped.transpose().cast()); } ImGui::End(); // end "Shape PCA" window // PCA colour coefficients: ImGui::SetNextWindowPos(ImVec2(380.f * menu.menu_scaling(), 0), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiSetCond_FirstUseEver); ImGui::Begin("Colour PCA", nullptr, ImGuiWindowFlags_NoSavedSettings); ImGui::Text("Coefficients"); if (morphable_model.get_color_model().get_num_principal_components() > 0) // a model was loaded { int num_color_coeffs_to_display = std::min(morphable_model.get_color_model().get_num_principal_components(), 30); if (color_coefficients.empty() || color_coefficients.size() != num_color_coeffs_to_display) { color_coefficients.resize(num_color_coeffs_to_display); } // color_coefficients are always initialised now. ImGui::BeginGroup(); for (int i = 0; i < num_color_coeffs_to_display; ++i) { const string label = std::to_string(i); ImGui::SliderFloat(label.c_str(), &color_coefficients[i], -3.0, 3.0); } ImGui::EndGroup(); string coeffs_displayed = "Displaying " + std::to_string(num_color_coeffs_to_display) + "/" + std::to_string(morphable_model.get_color_model().get_num_principal_components()) + " coefficients."; ImGui::Text(coeffs_displayed.c_str()); // We've got a colour model. So update the mesh that's been drawn with the value of the // coefficients. Note that we are currently doing this every draw call, not only when the // slider changes. See eos-model-viewer/issues/5. VectorXf color_instance = morphable_model.get_color_model().draw_sample(color_coefficients); const auto num_vertices = morphable_model.get_color_model().get_data_dimension() / 3; // will break for gray-level models! Eigen::Map color_instance_reshaped( color_instance.data(), 3, num_vertices); // Take 3 at a piece, then transpose below viewer.data().set_colors(color_instance_reshaped.transpose().cast()); } ImGui::End(); // end "Colour PCA" window // PCA expression coefficients: ImGui::SetNextWindowPos(ImVec2(580.f * menu.menu_scaling(), 0), ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiSetCond_FirstUseEver); ImGui::Begin("Expression PCA", nullptr, ImGuiWindowFlags_NoSavedSettings); ImGui::Text("Coefficients"); if (morphable_model.has_separate_expression_model()) // a model was loaded { int expression_model_num_coeffs = 0; float slider_min_range = -3.0f; float slider_max_range = +3.0f; if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { expression_model_num_coeffs = eos::cpp17::get( morphable_model.get_expression_model().value()) .size(); slider_min_range = -1.0f; } else if (eos::cpp17::holds_alternative( morphable_model.get_expression_model().value())) { expression_model_num_coeffs = eos::cpp17::get(morphable_model.get_expression_model().value()) .get_num_principal_components(); } int num_expression_coeffs_to_display = std::min(expression_model_num_coeffs, 30); if (expression_coefficients.empty() || expression_coefficients.size() != num_expression_coeffs_to_display) { expression_coefficients.resize(num_expression_coeffs_to_display); } // expression_coefficients are always initialised now. ImGui::BeginGroup(); for (int i = 0; i < num_expression_coeffs_to_display; ++i) { const string label = std::to_string(i); ImGui::SliderFloat(label.c_str(), &expression_coefficients[i], slider_min_range, slider_max_range); } ImGui::EndGroup(); string coeffs_displayed = "Displaying " + std::to_string(num_expression_coeffs_to_display) + "/" + std::to_string(expression_model_num_coeffs) + " coefficients."; ImGui::Text(coeffs_displayed.c_str()); // We're not updating the mesh here. The identity-update above already updates it, since we update // in each frame, and not just when a slider has moved. As long as we always have an identity // model, this is fine. } ImGui::End(); // end "Expression PCA" window }; viewer.launch(); return EXIT_SUCCESS; } ================================================ FILE: initial_cache.cmake.template ================================================ # Mechanism via FindLIB.cmake # ============================== # Boost: # ------- # Windows: Download the pre-built binaries from http://sourceforge.net/projects/boost/files/boost-binaries/ for VS2015 (msvc-14 64bit). # Either set the windows PATH variable to "\lib64-msvc-14.0" and CMake will find it, or, set: #set(BOOST_ROOT "C:/boost" CACHE PATH "Boost search location" FORCE) # Linux: Boost can usually be installed via a package manager (e.g. apt-get install boost-all-dev) and this variable can be left uncommented. #set(BOOST_ROOT "/home/user/boost/install" CACHE PATH "Boost search location" FORCE) # Mechanism via ConfigLIB.cmake in 3rd party library directory # ============================== # OpenCV: # ------- # Windows: Download the package from opencv.org, use 2.4.7.2 or never. It includes binaries for VS2013. Set this path accordingly. #set(OpenCV_DIR "C:/opencv/install" CACHE PATH "Location of OpenCVConfig.cmake" FORCE) # Linux: Usually can be left blank but it can be used if OpenCV is not found. #set(OpenCV_DIR "/home/user/opencv/install/share/OpenCV" CACHE PATH "Location of OpenCVConfig.cmake" FORCE)